fix(engine): prevent directScene hang + enforce segment ID uniqueness in prod
Two defensive fixes surfaced by the PR #95 review (PR-Agent), applied on top of the staging sync: 1. directScene: routeTaggedStream rejecting BEFORE onPlan fires would leave planPromise unsettled, hanging `await planPromise` — and thus the whole /api/start and /api/scene request — forever. Add a .catch that settles the plan with a minimal fallback and resolves routing to a degraded result, so the pipeline produces a playable fallback scene (graceful degradation) instead of hanging. 2. prompts/registry: the duplicate-segment-ID guard only ran under NODE_ENV=development, so a bad merge introducing a duplicate ID would silently shadow a segment in production. Run the check in all environments (once at module load; negligible cost).
This commit is contained in:
@@ -13,6 +13,7 @@ import type {
|
|||||||
Session,
|
Session,
|
||||||
StoryState,
|
StoryState,
|
||||||
StoryStatePatch,
|
StoryStatePatch,
|
||||||
|
StreamRouterResult,
|
||||||
WriterScenePlan,
|
WriterScenePlan,
|
||||||
} from "@infiplot/types";
|
} from "@infiplot/types";
|
||||||
import type { CharacterCard } from "./agents/characterDesigner";
|
import type { CharacterCard } from "./agents/characterDesigner";
|
||||||
@@ -259,6 +260,25 @@ export async function directScene(
|
|||||||
resolvePlan(extracted);
|
resolvePlan(extracted);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
}).catch((err): StreamRouterResult => {
|
||||||
|
// routeTaggedStream rejected (stream read / network failure) BEFORE onPlan
|
||||||
|
// fired. Without this, planPromise would never settle and `await
|
||||||
|
// planPromise` below would hang the whole request FOREVER. Settle the plan
|
||||||
|
// with a minimal fallback and resolve routing to a degraded result so the
|
||||||
|
// pipeline produces a playable fallback scene (graceful degradation) rather
|
||||||
|
// than hanging or hard-crashing.
|
||||||
|
console.warn("[directScene] routeTaggedStream rejected, degrading:", err);
|
||||||
|
if (!planSettled) {
|
||||||
|
planSettled = true;
|
||||||
|
resolvePlan(minimalFallbackPlan());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
plan: undefined,
|
||||||
|
beats: [],
|
||||||
|
choices: undefined,
|
||||||
|
rawStorySegment: undefined,
|
||||||
|
degraded: true,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Step 2 — await plan (settles at </plan> close — EARLY) ────────
|
// ── Step 2 — await plan (settles at </plan> close — EARLY) ────────
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ export const WRITER_SEGMENTS: PromptSegment[] = [
|
|||||||
WRITER_FORMAT,
|
WRITER_FORMAT,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
// Validate unique segment IDs in ALL environments (not just development).
|
||||||
|
// A duplicate ID — e.g. introduced by a bad merge — would otherwise silently
|
||||||
|
// shadow a segment in production. This runs once at module load; the cost is
|
||||||
|
// negligible. Throwing fast surfaces the misconfiguration at startup.
|
||||||
|
{
|
||||||
const ids = WRITER_SEGMENTS.map((s) => s.id);
|
const ids = WRITER_SEGMENTS.map((s) => s.id);
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
|
|||||||
Reference in New Issue
Block a user