@aicopycoders/exodus 2026.9.2000-beta.4 → 2026.9.2000-beta.5
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.
|
@@ -115,6 +115,11 @@ under it:
|
|
|
115
115
|
`exodus workflow checkpoint <runId>` is the verb for that one.
|
|
116
116
|
- failed: `status` shows how far it got, and a `--wait` that was running exits
|
|
117
117
|
1 with the error. Tell the user; do not start another run on your own.
|
|
118
|
+
When the storyboard is the step that failed, `status` adds a "What happened
|
|
119
|
+
to the storyboard" block: which parts of the plan were accepted, which part
|
|
120
|
+
was turned down, and why (#1902). Relay it as written. On an admin key it may
|
|
121
|
+
also offer `status <runId> --rejected-draft`. That prints the draft the system
|
|
122
|
+
threw away. It is a record only: never build on it, edit it, or re-run it.
|
|
118
123
|
- finished: pull what it made.
|
|
119
124
|
|
|
120
125
|
**`--wait` stops at both video parks** (#1818). `workflow run --wait` ends at
|
package/dist/commands/video.d.ts
CHANGED
|
@@ -69,12 +69,23 @@ export type ArtifactSubset = {
|
|
|
69
69
|
} | {
|
|
70
70
|
type: "text" | "primer" | "session" | "document";
|
|
71
71
|
};
|
|
72
|
+
export interface PlanFailureRecord {
|
|
73
|
+
kind: string;
|
|
74
|
+
partsTotal?: number;
|
|
75
|
+
acceptedParts?: number;
|
|
76
|
+
acceptedScenes?: number;
|
|
77
|
+
failedPart?: number;
|
|
78
|
+
reasons?: string[];
|
|
79
|
+
lines: string[];
|
|
80
|
+
}
|
|
72
81
|
export interface VideoRunNode {
|
|
73
82
|
nodeId: string;
|
|
74
83
|
kind: string;
|
|
75
84
|
status: "idle" | "running" | "done" | "failed" | "skipped" | "out-of-scope";
|
|
76
85
|
error?: string;
|
|
77
86
|
warning?: string;
|
|
87
|
+
planFailure?: PlanFailureRecord;
|
|
88
|
+
hasRejectedDraft?: boolean;
|
|
78
89
|
outputs?: ArtifactSubset[];
|
|
79
90
|
}
|
|
80
91
|
export type BuilderPauseReason = "taste" | "repair" | "slots" | "call" | "checkpoint";
|
|
@@ -205,6 +216,9 @@ export declare function stepName(kind: string | undefined): string;
|
|
|
205
216
|
export declare function isShowAd(run: Pick<VideoRun, "moduleOwned">): boolean;
|
|
206
217
|
export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
|
|
207
218
|
export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
|
|
219
|
+
export declare function failedStoryboardNode(run: VideoRun): VideoRunNode | undefined;
|
|
220
|
+
export declare function errorEchoesReasons(error: string | undefined, record: PlanFailureRecord | undefined): boolean;
|
|
221
|
+
export declare function planFailureLines(node: VideoRunNode | undefined, runId: string): string[];
|
|
208
222
|
export interface PullDownload {
|
|
209
223
|
file: string;
|
|
210
224
|
url: string;
|
|
@@ -294,6 +308,7 @@ export declare function waitFlow(runId: string, opts: {
|
|
|
294
308
|
maxPolls?: number;
|
|
295
309
|
}, deps: VideoDeps): Promise<FlowResult>;
|
|
296
310
|
export declare function statusFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
311
|
+
export declare function rejectedDraftFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
297
312
|
export declare function storyboardFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
298
313
|
export declare function approveFlow(runId: string, opts: {
|
|
299
314
|
json: boolean;
|
package/dist/commands/video.js
CHANGED
|
@@ -41,7 +41,13 @@ The whole loop, in order:
|
|
|
41
41
|
Redo ONE still at the pixel gate. Neighbours stay. The gate holds.
|
|
42
42
|
|
|
43
43
|
7. exodus video status <runId>
|
|
44
|
-
Where the run is and how each scene's clip turned out.
|
|
44
|
+
Where the run is and how each scene's clip turned out. If the storyboard
|
|
45
|
+
failed, it also says which parts of it were accepted and why the next one
|
|
46
|
+
was turned down.
|
|
47
|
+
|
|
48
|
+
exodus video status <runId> --rejected-draft
|
|
49
|
+
Prints the storyboard draft that was turned down. A record of what
|
|
50
|
+
happened, thrown away by the system — never something to act on.
|
|
45
51
|
|
|
46
52
|
8. exodus video pull <runId> --out ./ad
|
|
47
53
|
Writes every piece to that folder plus a manifest.json index.
|
|
@@ -67,7 +73,7 @@ The whole loop, in order:
|
|
|
67
73
|
or add --approve-stale-cut to deliver the cut you already uploaded.
|
|
68
74
|
|
|
69
75
|
Usage:
|
|
70
|
-
exodus video status <runId> [--json]
|
|
76
|
+
exodus video status <runId> [--rejected-draft] [--json]
|
|
71
77
|
exodus video storyboard <runId> [--json]
|
|
72
78
|
exodus video approve <runId> [--approve-stale-cut] [--json]
|
|
73
79
|
exodus video retry-frame <runId> --node <nodeId> --scene <n> [--note "..."] [--json]
|
|
@@ -99,6 +105,8 @@ Options:
|
|
|
99
105
|
--clear <who> Take a character's voice off again (voices). Repeatable
|
|
100
106
|
--from <file.json> A file of characters and voice IDs (voices). --set and
|
|
101
107
|
--clear win over the same name in the file
|
|
108
|
+
--rejected-draft Print the storyboard draft the system turned down
|
|
109
|
+
(status). It was thrown away — a record only
|
|
102
110
|
--json Machine-readable output
|
|
103
111
|
--help, -h Print this help
|
|
104
112
|
|
|
@@ -124,6 +132,9 @@ const SHOWS_PATH = "/api/v2/shows";
|
|
|
124
132
|
const RUNS_PATH = "/api/v2/video/runs";
|
|
125
133
|
const RUN_PATH = "/api/v2/workflow";
|
|
126
134
|
const ITEMS_PATH = "/api/v2/workflow/items";
|
|
135
|
+
const REJECTED_DRAFT_PATH = "/api/v2/workflow/rejected-draft";
|
|
136
|
+
const REJECTED_DRAFT_NOT_ON_THIS_SERVER = "This Exodus server does not keep rejected drafts yet, so there is nothing to read. It arrives " +
|
|
137
|
+
"with the next server update.";
|
|
127
138
|
const STORYBOARD_PATH = "/api/v2/video/storyboard";
|
|
128
139
|
const FLAG_PATH = "/api/v2/video/storyboard/flag";
|
|
129
140
|
const APPROVE_PATH = "/api/v2/workflow/approve";
|
|
@@ -377,6 +388,30 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
377
388
|
}
|
|
378
389
|
return [`Working: ${stageWord(stop.stage)}.`];
|
|
379
390
|
}
|
|
391
|
+
export function failedStoryboardNode(run) {
|
|
392
|
+
return run.nodes.find((n) => n.kind === "storyboard" && n.status === "failed");
|
|
393
|
+
}
|
|
394
|
+
export function errorEchoesReasons(error, record) {
|
|
395
|
+
if (!error || record?.kind !== "rejected")
|
|
396
|
+
return false;
|
|
397
|
+
const reasons = record.reasons ?? [];
|
|
398
|
+
return reasons.length > 0 && reasons.every((reason) => error.includes(reason));
|
|
399
|
+
}
|
|
400
|
+
export function planFailureLines(node, runId) {
|
|
401
|
+
const record = node?.planFailure;
|
|
402
|
+
if (!record || !Array.isArray(record.lines) || !node)
|
|
403
|
+
return [];
|
|
404
|
+
const reasons = record.kind === "rejected" ? (record.reasons ?? []) : [];
|
|
405
|
+
const bodyCount = Math.max(0, record.lines.length - reasons.length);
|
|
406
|
+
const lines = [`What happened to the storyboard step (${node.nodeId}):`];
|
|
407
|
+
record.lines.forEach((line, i) => {
|
|
408
|
+
lines.push(i < bodyCount ? ` ${line}` : ` - ${line}`);
|
|
409
|
+
});
|
|
410
|
+
if (node?.hasRejectedDraft === true) {
|
|
411
|
+
lines.push(" A rejected draft is on file. The system threw it away, so it is a record of what happened and nothing to act on.", ` Read it: exodus video status ${runId} --rejected-draft`);
|
|
412
|
+
}
|
|
413
|
+
return lines;
|
|
414
|
+
}
|
|
380
415
|
export const CAST_LEDGER_BASE = 910000;
|
|
381
416
|
const FALLBACK_EXT = {
|
|
382
417
|
image: "png",
|
|
@@ -983,7 +1018,10 @@ export async function statusFlow(runId, json, deps) {
|
|
|
983
1018
|
const items = itemsRes.data.items ?? [];
|
|
984
1019
|
const hasFinal = hasAttachedCut(items);
|
|
985
1020
|
const stop = resolveStop(classifyRun(run), hasFinal);
|
|
986
|
-
const
|
|
1021
|
+
const failedStoryboard = failedStoryboardNode(run);
|
|
1022
|
+
const guidance = stopLines(stop.at === "failed" && errorEchoesReasons(stop.error, failedStoryboard?.planFailure)
|
|
1023
|
+
? { ...stop, error: undefined }
|
|
1024
|
+
: stop, runId, reviewUrl(deps.dashboardUrl, run));
|
|
987
1025
|
const warnings = run.nodes
|
|
988
1026
|
.filter((n) => n.warning)
|
|
989
1027
|
.map((n) => ({ nodeId: n.nodeId, step: n.kind, warning: n.warning }));
|
|
@@ -991,7 +1029,20 @@ export async function statusFlow(runId, json, deps) {
|
|
|
991
1029
|
return {
|
|
992
1030
|
code: 0,
|
|
993
1031
|
lines: [
|
|
994
|
-
JSON.stringify({
|
|
1032
|
+
JSON.stringify({
|
|
1033
|
+
runId,
|
|
1034
|
+
status: run.status,
|
|
1035
|
+
stop,
|
|
1036
|
+
items,
|
|
1037
|
+
hasFinal,
|
|
1038
|
+
warnings,
|
|
1039
|
+
guidance,
|
|
1040
|
+
...(failedStoryboard?.planFailure ? { planFailure: failedStoryboard.planFailure } : {}),
|
|
1041
|
+
...(failedStoryboard?.planFailure
|
|
1042
|
+
? { storyboardNodeId: failedStoryboard.nodeId }
|
|
1043
|
+
: {}),
|
|
1044
|
+
...(failedStoryboard?.hasRejectedDraft === true ? { hasRejectedDraft: true } : {}),
|
|
1045
|
+
}),
|
|
995
1046
|
],
|
|
996
1047
|
};
|
|
997
1048
|
}
|
|
@@ -1026,6 +1077,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1026
1077
|
...(carryingOn(run) ? [CARRYING_ON_LINE] : []),
|
|
1027
1078
|
...guidance,
|
|
1028
1079
|
...(stop.at === "running" && run.pauseAhead ? [pauseAheadLine(run.pauseAhead)] : []),
|
|
1080
|
+
...planFailureLines(failedStoryboard, runId),
|
|
1029
1081
|
...warnings.map((w) => `Heads-up (${w.step}): ${w.warning}`),
|
|
1030
1082
|
];
|
|
1031
1083
|
if (byScene.size === 0) {
|
|
@@ -1074,6 +1126,45 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1074
1126
|
: "Final cut: not uploaded yet.");
|
|
1075
1127
|
return { code: 0, lines };
|
|
1076
1128
|
}
|
|
1129
|
+
export async function rejectedDraftFlow(runId, json, deps) {
|
|
1130
|
+
const runRes = await deps.get(`${RUN_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
1131
|
+
if (!runRes.ok)
|
|
1132
|
+
return errorResult(runRes, json);
|
|
1133
|
+
const node = failedStoryboardNode(asVideoRun(runRes.data));
|
|
1134
|
+
if (!node) {
|
|
1135
|
+
const note = "The storyboard on this run didn't fail, so there is no rejected draft to read.";
|
|
1136
|
+
return {
|
|
1137
|
+
code: 0,
|
|
1138
|
+
lines: json ? [JSON.stringify({ runId, nodeId: null, draft: null })] : [note],
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
const res = await deps.get(`${REJECTED_DRAFT_PATH}?runId=${encodeURIComponent(runId)}&nodeId=${encodeURIComponent(node.nodeId)}`);
|
|
1142
|
+
if (missingRouteLine(res, "exodus video status --rejected-draft")) {
|
|
1143
|
+
return {
|
|
1144
|
+
code: 1,
|
|
1145
|
+
lines: json
|
|
1146
|
+
? [JSON.stringify({ ok: false, status: 404, error: REJECTED_DRAFT_NOT_ON_THIS_SERVER })]
|
|
1147
|
+
: [REJECTED_DRAFT_NOT_ON_THIS_SERVER],
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (!res.ok)
|
|
1151
|
+
return errorResult(res, json);
|
|
1152
|
+
const draft = res.data.draft ?? null;
|
|
1153
|
+
if (json)
|
|
1154
|
+
return { code: 0, lines: [JSON.stringify({ runId, nodeId: node.nodeId, draft })] };
|
|
1155
|
+
if (!draft)
|
|
1156
|
+
return { code: 0, lines: ["No rejected draft was kept for this attempt."] };
|
|
1157
|
+
return {
|
|
1158
|
+
code: 0,
|
|
1159
|
+
lines: [
|
|
1160
|
+
`REJECTED DRAFT — ad run ${runId}, step ${node.nodeId}`,
|
|
1161
|
+
"The system turned this draft down and threw it away. It is kept only as a record of what happened.",
|
|
1162
|
+
"Nothing in it is part of your ad. There is nothing here to act on or edit.",
|
|
1163
|
+
"",
|
|
1164
|
+
...draft.split("\n"),
|
|
1165
|
+
],
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1077
1168
|
export async function storyboardFlow(runId, json, deps) {
|
|
1078
1169
|
const res = await deps.get(`${STORYBOARD_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
1079
1170
|
if (!res.ok)
|
|
@@ -2066,8 +2157,12 @@ export async function run(flags, occurrences) {
|
|
|
2066
2157
|
if (needsRunId.includes(sub) && !runId) {
|
|
2067
2158
|
usage(`video ${sub} needs a run id: exodus video ${sub} <runId>`);
|
|
2068
2159
|
}
|
|
2069
|
-
if (sub === "status")
|
|
2160
|
+
if (sub === "status") {
|
|
2161
|
+
if (flags["rejected-draft"] !== undefined && flags["rejected-draft"] !== false) {
|
|
2162
|
+
return printResult(await rejectedDraftFlow(runId, json, defaultDeps));
|
|
2163
|
+
}
|
|
2070
2164
|
return printResult(await statusFlow(runId, json, defaultDeps));
|
|
2165
|
+
}
|
|
2071
2166
|
if (sub === "storyboard")
|
|
2072
2167
|
return printResult(await storyboardFlow(runId, json, defaultDeps));
|
|
2073
2168
|
if (sub === "approve") {
|