@aicopycoders/exodus 2026.9.2000-beta.11 → 2026.9.2000-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -138,7 +138,11 @@ under it:
|
|
|
138
138
|
lines under `guidance`). It is also
|
|
139
139
|
the one park where a finished clip can be redone, with `retry-clip`. A run
|
|
140
140
|
only reaches this park if its video node sets `finalWatch: true` — otherwise
|
|
141
|
-
it skips straight to finished.
|
|
141
|
+
it skips straight to finished. If a scene has no clip, the stop block names
|
|
142
|
+
it instead (`stop.missingScenes` in `--json`) and approval is refused until
|
|
143
|
+
it has one. A scene whose picture the video service refused needs
|
|
144
|
+
`retry-frame --note` before `retry-clip`; the clip redo alone resends the
|
|
145
|
+
refused picture.
|
|
142
146
|
- paused: a builder checkpoint neither video verb resolves —
|
|
143
147
|
`exodus workflow checkpoint <runId>` is the verb for that one.
|
|
144
148
|
- failed: `status` shows how far it got, and a `--wait` that was running exits
|
package/dist/commands/video.d.ts
CHANGED
|
@@ -115,6 +115,11 @@ export interface VideoRunNode {
|
|
|
115
115
|
planFailure?: PlanFailureRecord;
|
|
116
116
|
hasRejectedDraft?: boolean;
|
|
117
117
|
outputs?: ArtifactSubset[];
|
|
118
|
+
missingScenes?: MissingScene[];
|
|
119
|
+
}
|
|
120
|
+
export interface MissingScene {
|
|
121
|
+
sceneIndex: number;
|
|
122
|
+
pictureRefused: boolean;
|
|
118
123
|
}
|
|
119
124
|
export type BuilderPauseReason = "taste" | "repair" | "slots" | "call" | "checkpoint";
|
|
120
125
|
export interface PullCastLockMember {
|
|
@@ -162,6 +167,7 @@ export interface NodeItem {
|
|
|
162
167
|
take: number;
|
|
163
168
|
outcome: string;
|
|
164
169
|
label: string;
|
|
170
|
+
reason?: string;
|
|
165
171
|
displacedHistory?: {
|
|
166
172
|
title: string;
|
|
167
173
|
lines: string[];
|
|
@@ -221,6 +227,8 @@ export type RunStop = {
|
|
|
221
227
|
showAd?: true;
|
|
222
228
|
} | {
|
|
223
229
|
at: "final-watch";
|
|
230
|
+
missingScenes?: MissingScene[];
|
|
231
|
+
framesNodeId?: string;
|
|
224
232
|
} | {
|
|
225
233
|
at: "paused";
|
|
226
234
|
nodeId?: string;
|
|
@@ -237,10 +245,11 @@ export type RunStop = {
|
|
|
237
245
|
};
|
|
238
246
|
export type ResolvedStop = Exclude<RunStop, {
|
|
239
247
|
at: "final-watch";
|
|
240
|
-
}> | {
|
|
248
|
+
}> | (Extract<RunStop, {
|
|
241
249
|
at: "final-watch";
|
|
250
|
+
}> & {
|
|
242
251
|
cutAttached: boolean | null;
|
|
243
|
-
};
|
|
252
|
+
});
|
|
244
253
|
export declare function classifyRun(run: VideoRun): RunStop;
|
|
245
254
|
export declare function hasAttachedCut(items: NodeItem[]): boolean;
|
|
246
255
|
export declare function resolveStop(stop: RunStop, cutAttached: boolean | null): ResolvedStop;
|
package/dist/commands/video.js
CHANGED
|
@@ -223,6 +223,9 @@ export function renderQcTakeHistory(takes) {
|
|
|
223
223
|
});
|
|
224
224
|
return lines;
|
|
225
225
|
}
|
|
226
|
+
function redoLine(lastRedo) {
|
|
227
|
+
return lastRedo.reason ? `${lastRedo.label}: ${lastRedo.reason}` : lastRedo.label;
|
|
228
|
+
}
|
|
226
229
|
function printDisplacedHistory(lines, lastRedo) {
|
|
227
230
|
const history = lastRedo.displacedHistory;
|
|
228
231
|
if (!history)
|
|
@@ -346,8 +349,17 @@ export function classifyRun(run) {
|
|
|
346
349
|
...(isShowAd(run) ? { showAd: true } : {}),
|
|
347
350
|
};
|
|
348
351
|
}
|
|
349
|
-
if (parkedAtFinalWatch(run))
|
|
350
|
-
|
|
352
|
+
if (parkedAtFinalWatch(run)) {
|
|
353
|
+
const missingScenes = run.nodes
|
|
354
|
+
.filter((n) => n.kind === "video")
|
|
355
|
+
.flatMap((n) => n.missingScenes ?? []);
|
|
356
|
+
const framesNodeId = run.nodes.find((n) => n.kind === "scene-frames")?.nodeId;
|
|
357
|
+
return {
|
|
358
|
+
at: "final-watch",
|
|
359
|
+
...(missingScenes.length > 0 ? { missingScenes } : {}),
|
|
360
|
+
...(missingScenes.length > 0 && framesNodeId ? { framesNodeId } : {}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
351
363
|
return { at: "paused", nodeId: run.pausedNodeId, reason: run.pauseReason };
|
|
352
364
|
}
|
|
353
365
|
const active = run.nodes.find((n) => n.status === "running");
|
|
@@ -359,7 +371,7 @@ export function hasAttachedCut(items) {
|
|
|
359
371
|
export function resolveStop(stop, cutAttached) {
|
|
360
372
|
if (stop.at !== "final-watch")
|
|
361
373
|
return stop;
|
|
362
|
-
return {
|
|
374
|
+
return { ...stop, cutAttached };
|
|
363
375
|
}
|
|
364
376
|
export async function resolveStopAtPark(stop, runId, deps) {
|
|
365
377
|
if (stop.at !== "final-watch")
|
|
@@ -404,6 +416,30 @@ export function reviewUrl(dashboardUrl, run) {
|
|
|
404
416
|
return `${dashboardUrl}/workflows/${run.workflowId}/runs/${run._id}`;
|
|
405
417
|
return `${dashboardUrl}/runs/${run._id}`;
|
|
406
418
|
}
|
|
419
|
+
function scenesPhrase(scenes) {
|
|
420
|
+
if (scenes.length === 1)
|
|
421
|
+
return `scene ${scenes[0]}`;
|
|
422
|
+
return `scenes ${scenes.slice(0, -1).join(", ")} and ${scenes[scenes.length - 1]}`;
|
|
423
|
+
}
|
|
424
|
+
function missingSceneLines(missing, framesNodeId, runId, runUrl) {
|
|
425
|
+
const scenes = missing.map((m) => m.sceneIndex);
|
|
426
|
+
const lines = [
|
|
427
|
+
`Parked: ${scenesPhrase(scenes)} ${scenes.length === 1 ? "has" : "have"} no clip yet, so the ad isn't complete.`,
|
|
428
|
+
];
|
|
429
|
+
for (const { sceneIndex, pictureRefused } of missing) {
|
|
430
|
+
if (!pictureRefused) {
|
|
431
|
+
lines.push(`Redo clip ${sceneIndex}: exodus video retry-clip ${runId} --scene ${sceneIndex}`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
lines.push(`Scene ${sceneIndex}'s picture was refused by the video service as too close to an existing character, so redraw it before redoing the clip.`);
|
|
435
|
+
lines.push(framesNodeId
|
|
436
|
+
? `Redraw frame ${sceneIndex}: exodus video retry-frame ${runId} --node ${framesNodeId} --scene ${sceneIndex} --note "<what to change>"`
|
|
437
|
+
: `Redraw frame ${sceneIndex}: on the run page, ${runUrl}`);
|
|
438
|
+
lines.push(`Then redo clip ${sceneIndex}: exodus video retry-clip ${runId} --scene ${sceneIndex}`);
|
|
439
|
+
}
|
|
440
|
+
lines.push("The ad can't be approved until every scene has a clip.", `Watch it here: ${runUrl}`);
|
|
441
|
+
return lines;
|
|
442
|
+
}
|
|
407
443
|
export function stopLines(stop, runId, runUrl) {
|
|
408
444
|
if (stop.at === "storyboard-gate") {
|
|
409
445
|
const lines = [
|
|
@@ -420,6 +456,9 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
420
456
|
return lines;
|
|
421
457
|
}
|
|
422
458
|
if (stop.at === "final-watch") {
|
|
459
|
+
if (stop.missingScenes?.length) {
|
|
460
|
+
return missingSceneLines(stop.missingScenes, stop.framesNodeId, runId, runUrl);
|
|
461
|
+
}
|
|
423
462
|
if (stop.cutAttached === null) {
|
|
424
463
|
return [
|
|
425
464
|
"Parked: every piece is made.",
|
|
@@ -1479,7 +1518,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1479
1518
|
const row = byScene.get(sceneIndex);
|
|
1480
1519
|
lines.push(`${String(sceneIndex).padEnd(5)} ${itemWord(row.clip).padEnd(8)} ${itemWord(row.voiceover).padEnd(8)} ${itemWord(row.frame)}`);
|
|
1481
1520
|
if (row.clip?.lastRedo) {
|
|
1482
|
-
lines.push(` clip: ${row.clip.lastRedo
|
|
1521
|
+
lines.push(` clip: ${redoLine(row.clip.lastRedo)}`);
|
|
1483
1522
|
printDisplacedHistory(lines, row.clip.lastRedo);
|
|
1484
1523
|
}
|
|
1485
1524
|
if (row.clip?.lastRedo || row.clip?.flagged) {
|
|
@@ -1488,7 +1527,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1488
1527
|
}
|
|
1489
1528
|
}
|
|
1490
1529
|
if (row.voiceover?.lastRedo) {
|
|
1491
|
-
lines.push(` voice: ${row.voiceover.lastRedo
|
|
1530
|
+
lines.push(` voice: ${redoLine(row.voiceover.lastRedo)}`);
|
|
1492
1531
|
printDisplacedHistory(lines, row.voiceover.lastRedo);
|
|
1493
1532
|
}
|
|
1494
1533
|
if (row.frame?.lastRedo)
|
|
@@ -1870,7 +1909,12 @@ export async function retryClipFlow(runId, target, { note, voiceFirst = false },
|
|
|
1870
1909
|
"recorded in the character's own voice, then the clip is made to match it."
|
|
1871
1910
|
: `Redoing scene ${plan.sceneIndex}'s clip on ${plan.nodeId}.`,
|
|
1872
1911
|
`triggerRunId: ${triggerRunId ?? "-"}`,
|
|
1873
|
-
|
|
1912
|
+
...(voiceFirst
|
|
1913
|
+
? [
|
|
1914
|
+
"Every other scene stays as it is, and so do the pictures and the script.",
|
|
1915
|
+
"A speaking character with no voice yet gets one picked and saved for the whole run.",
|
|
1916
|
+
]
|
|
1917
|
+
: ["Every other scene stays as it is, and so do the pictures, the voices and the script."]),
|
|
1874
1918
|
...(note
|
|
1875
1919
|
? ["Your note steers how this clip moves. It never changes the words that are spoken."]
|
|
1876
1920
|
: []),
|