@aicopycoders/exodus 2026.9.2000-beta.4 → 2026.9.2000-beta.6
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
|
@@ -8,10 +8,31 @@ export interface ClipWord {
|
|
|
8
8
|
s: number;
|
|
9
9
|
e: number;
|
|
10
10
|
}
|
|
11
|
+
export type ClipQcTakeNeighbours = {
|
|
12
|
+
state: "not-applicable";
|
|
13
|
+
} | {
|
|
14
|
+
state: "not-attached";
|
|
15
|
+
} | {
|
|
16
|
+
state: "none-accepted";
|
|
17
|
+
} | {
|
|
18
|
+
state: "attached";
|
|
19
|
+
scenes: number[];
|
|
20
|
+
};
|
|
21
|
+
export interface ClipQcTake {
|
|
22
|
+
kind: "take" | "soft-retry";
|
|
23
|
+
failCodes: string[];
|
|
24
|
+
warnCodes: string[];
|
|
25
|
+
neighbours: ClipQcTakeNeighbours;
|
|
26
|
+
judgeWording?: {
|
|
27
|
+
code: string;
|
|
28
|
+
wording: string;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
11
31
|
export interface ClipQc {
|
|
12
32
|
verdict: "pass" | "fail";
|
|
13
33
|
attempts: number;
|
|
14
34
|
neighbours?: number[];
|
|
35
|
+
takes?: ClipQcTake[];
|
|
15
36
|
}
|
|
16
37
|
export interface ClipFinding {
|
|
17
38
|
check: string;
|
|
@@ -21,6 +42,9 @@ export interface ClipFinding {
|
|
|
21
42
|
judgeDetail?: string;
|
|
22
43
|
judgeSeverity?: "fail" | "warn";
|
|
23
44
|
}
|
|
45
|
+
export declare function qcWithoutJudgeWording(qc: ClipQc): ClipQc;
|
|
46
|
+
export declare const NO_TAKE_HISTORY_LINE = "no history recorded for this clip";
|
|
47
|
+
export declare function renderQcTakeHistory(takes: ClipQcTake[] | undefined): string[];
|
|
24
48
|
export type ArtifactSubset = {
|
|
25
49
|
type: "storyboard";
|
|
26
50
|
storyboard?: unknown;
|
|
@@ -69,12 +93,23 @@ export type ArtifactSubset = {
|
|
|
69
93
|
} | {
|
|
70
94
|
type: "text" | "primer" | "session" | "document";
|
|
71
95
|
};
|
|
96
|
+
export interface PlanFailureRecord {
|
|
97
|
+
kind: string;
|
|
98
|
+
partsTotal?: number;
|
|
99
|
+
acceptedParts?: number;
|
|
100
|
+
acceptedScenes?: number;
|
|
101
|
+
failedPart?: number;
|
|
102
|
+
reasons?: string[];
|
|
103
|
+
lines: string[];
|
|
104
|
+
}
|
|
72
105
|
export interface VideoRunNode {
|
|
73
106
|
nodeId: string;
|
|
74
107
|
kind: string;
|
|
75
108
|
status: "idle" | "running" | "done" | "failed" | "skipped" | "out-of-scope";
|
|
76
109
|
error?: string;
|
|
77
110
|
warning?: string;
|
|
111
|
+
planFailure?: PlanFailureRecord;
|
|
112
|
+
hasRejectedDraft?: boolean;
|
|
78
113
|
outputs?: ArtifactSubset[];
|
|
79
114
|
}
|
|
80
115
|
export type BuilderPauseReason = "taste" | "repair" | "slots" | "call" | "checkpoint";
|
|
@@ -205,6 +240,9 @@ export declare function stepName(kind: string | undefined): string;
|
|
|
205
240
|
export declare function isShowAd(run: Pick<VideoRun, "moduleOwned">): boolean;
|
|
206
241
|
export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
|
|
207
242
|
export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
|
|
243
|
+
export declare function failedStoryboardNode(run: VideoRun): VideoRunNode | undefined;
|
|
244
|
+
export declare function errorEchoesReasons(error: string | undefined, record: PlanFailureRecord | undefined): boolean;
|
|
245
|
+
export declare function planFailureLines(node: VideoRunNode | undefined, runId: string): string[];
|
|
208
246
|
export interface PullDownload {
|
|
209
247
|
file: string;
|
|
210
248
|
url: string;
|
|
@@ -263,7 +301,7 @@ export interface VideoManifest {
|
|
|
263
301
|
failed: PullFailure[];
|
|
264
302
|
provenance?: RunProvenance;
|
|
265
303
|
}
|
|
266
|
-
export declare const CAST_LEDGER_BASE
|
|
304
|
+
export declare const CAST_LEDGER_BASE: number;
|
|
267
305
|
export interface PullPlan {
|
|
268
306
|
downloads: PullDownload[];
|
|
269
307
|
texts: PullTextFile[];
|
|
@@ -294,6 +332,7 @@ export declare function waitFlow(runId: string, opts: {
|
|
|
294
332
|
maxPolls?: number;
|
|
295
333
|
}, deps: VideoDeps): Promise<FlowResult>;
|
|
296
334
|
export declare function statusFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
335
|
+
export declare function rejectedDraftFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
297
336
|
export declare function storyboardFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
298
337
|
export declare function approveFlow(runId: string, opts: {
|
|
299
338
|
json: boolean;
|
package/dist/commands/video.js
CHANGED
|
@@ -6,6 +6,7 @@ import { displayRunStatus, formatApiError } from "../lib/format.js";
|
|
|
6
6
|
import { missingRouteLine } from "../lib/route-support.js";
|
|
7
7
|
import { hasBinary } from "../lib/preflight.js";
|
|
8
8
|
import { ASSET_UPLOAD_POLICY, pauseAheadLine, } from "./workflow.js";
|
|
9
|
+
import { HELPER_LEDGER_BASE } from "../lib/helperLedgerBase.js";
|
|
9
10
|
export const helpText = `
|
|
10
11
|
exodus video — make a video ad from a saved workflow, pull every piece, upload your cut
|
|
11
12
|
|
|
@@ -41,7 +42,13 @@ The whole loop, in order:
|
|
|
41
42
|
Redo ONE still at the pixel gate. Neighbours stay. The gate holds.
|
|
42
43
|
|
|
43
44
|
7. exodus video status <runId>
|
|
44
|
-
Where the run is and how each scene's clip turned out.
|
|
45
|
+
Where the run is and how each scene's clip turned out. If the storyboard
|
|
46
|
+
failed, it also says which parts of it were accepted and why the next one
|
|
47
|
+
was turned down.
|
|
48
|
+
|
|
49
|
+
exodus video status <runId> --rejected-draft
|
|
50
|
+
Prints the storyboard draft that was turned down. A record of what
|
|
51
|
+
happened, thrown away by the system — never something to act on.
|
|
45
52
|
|
|
46
53
|
8. exodus video pull <runId> --out ./ad
|
|
47
54
|
Writes every piece to that folder plus a manifest.json index.
|
|
@@ -67,7 +74,7 @@ The whole loop, in order:
|
|
|
67
74
|
or add --approve-stale-cut to deliver the cut you already uploaded.
|
|
68
75
|
|
|
69
76
|
Usage:
|
|
70
|
-
exodus video status <runId> [--json]
|
|
77
|
+
exodus video status <runId> [--rejected-draft] [--json]
|
|
71
78
|
exodus video storyboard <runId> [--json]
|
|
72
79
|
exodus video approve <runId> [--approve-stale-cut] [--json]
|
|
73
80
|
exodus video retry-frame <runId> --node <nodeId> --scene <n> [--note "..."] [--json]
|
|
@@ -99,6 +106,8 @@ Options:
|
|
|
99
106
|
--clear <who> Take a character's voice off again (voices). Repeatable
|
|
100
107
|
--from <file.json> A file of characters and voice IDs (voices). --set and
|
|
101
108
|
--clear win over the same name in the file
|
|
109
|
+
--rejected-draft Print the storyboard draft the system turned down
|
|
110
|
+
(status). It was thrown away — a record only
|
|
102
111
|
--json Machine-readable output
|
|
103
112
|
--help, -h Print this help
|
|
104
113
|
|
|
@@ -124,6 +133,9 @@ const SHOWS_PATH = "/api/v2/shows";
|
|
|
124
133
|
const RUNS_PATH = "/api/v2/video/runs";
|
|
125
134
|
const RUN_PATH = "/api/v2/workflow";
|
|
126
135
|
const ITEMS_PATH = "/api/v2/workflow/items";
|
|
136
|
+
const REJECTED_DRAFT_PATH = "/api/v2/workflow/rejected-draft";
|
|
137
|
+
const REJECTED_DRAFT_NOT_ON_THIS_SERVER = "This Exodus server does not keep rejected drafts yet, so there is nothing to read. It arrives " +
|
|
138
|
+
"with the next server update.";
|
|
127
139
|
const STORYBOARD_PATH = "/api/v2/video/storyboard";
|
|
128
140
|
const FLAG_PATH = "/api/v2/video/storyboard/flag";
|
|
129
141
|
const APPROVE_PATH = "/api/v2/workflow/approve";
|
|
@@ -138,6 +150,40 @@ const ASSETS_PATH = "/api/v2/workflows/assets";
|
|
|
138
150
|
function withoutJudgeFields({ judgeDetail: _d, judgeSeverity: _s, ...rest }) {
|
|
139
151
|
return rest;
|
|
140
152
|
}
|
|
153
|
+
export function qcWithoutJudgeWording(qc) {
|
|
154
|
+
if (!qc.takes)
|
|
155
|
+
return qc;
|
|
156
|
+
return {
|
|
157
|
+
...qc,
|
|
158
|
+
takes: qc.takes.map(({ judgeWording: _w, ...rest }) => rest),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export const NO_TAKE_HISTORY_LINE = "no history recorded for this clip";
|
|
162
|
+
const TAKE_NEIGHBOUR_SENTENCE = {
|
|
163
|
+
"not-applicable": () => "never compared",
|
|
164
|
+
"not-attached": () => "no neighbours attached",
|
|
165
|
+
"none-accepted": () => "no accepted neighbours yet",
|
|
166
|
+
attached: ({ scenes }) => `compared against scene${scenes.length === 1 ? "" : "s"} ${scenes.join(", ")}`,
|
|
167
|
+
};
|
|
168
|
+
export function renderQcTakeHistory(takes) {
|
|
169
|
+
if (!takes || takes.length === 0)
|
|
170
|
+
return [NO_TAKE_HISTORY_LINE];
|
|
171
|
+
const lines = [];
|
|
172
|
+
takes.forEach((take, index) => {
|
|
173
|
+
const say = TAKE_NEIGHBOUR_SENTENCE[take.neighbours.state];
|
|
174
|
+
const parts = [
|
|
175
|
+
take.failCodes.length ? `failed ${take.failCodes.join(", ")}` : "passed",
|
|
176
|
+
...(take.warnCodes.length ? [`warned ${take.warnCodes.join(", ")}`] : []),
|
|
177
|
+
say(take.neighbours),
|
|
178
|
+
];
|
|
179
|
+
const bonus = take.kind === "soft-retry" ? " (bonus take)" : "";
|
|
180
|
+
lines.push(`take ${index + 1}${bonus}: ${parts.join("; ")}`);
|
|
181
|
+
for (const { code, wording } of take.judgeWording ?? []) {
|
|
182
|
+
lines.push(` checker said (${code}): ${wording}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
141
187
|
export function asVideoRun(data) {
|
|
142
188
|
const run = (data ?? {});
|
|
143
189
|
return { ...run, nodes: Array.isArray(run.nodes) ? run.nodes : [] };
|
|
@@ -352,6 +398,13 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
352
398
|
}
|
|
353
399
|
if (stop.at === "failed") {
|
|
354
400
|
if (stop.repair) {
|
|
401
|
+
if (!stop.step) {
|
|
402
|
+
return [
|
|
403
|
+
"This run stopped at a step that can't be picked back up from here.",
|
|
404
|
+
"Start a fresh run to get this ad made.",
|
|
405
|
+
`Open the run: ${runUrl}`,
|
|
406
|
+
];
|
|
407
|
+
}
|
|
355
408
|
return [
|
|
356
409
|
`${stepName(stop.step)} failed${stop.error ? `: ${stop.error}` : "."}`,
|
|
357
410
|
`Try that step again: exodus workflow repair ${runId} retry`,
|
|
@@ -377,7 +430,34 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
377
430
|
}
|
|
378
431
|
return [`Working: ${stageWord(stop.stage)}.`];
|
|
379
432
|
}
|
|
380
|
-
export
|
|
433
|
+
export function failedStoryboardNode(run) {
|
|
434
|
+
return run.nodes.find((n) => n.kind === "storyboard" && n.status === "failed");
|
|
435
|
+
}
|
|
436
|
+
export function errorEchoesReasons(error, record) {
|
|
437
|
+
if (!error || record?.kind !== "rejected")
|
|
438
|
+
return false;
|
|
439
|
+
const reasons = record.reasons ?? [];
|
|
440
|
+
return reasons.length > 0 && reasons.every((reason) => error.includes(reason));
|
|
441
|
+
}
|
|
442
|
+
export function planFailureLines(node, runId) {
|
|
443
|
+
const record = node?.planFailure;
|
|
444
|
+
if (!record || !Array.isArray(record.lines) || !node)
|
|
445
|
+
return [];
|
|
446
|
+
const reasons = record.kind === "rejected" ? (record.reasons ?? []) : [];
|
|
447
|
+
const bodyCount = Math.max(0, record.lines.length - reasons.length);
|
|
448
|
+
const lines = [`What happened to the storyboard step (${node.nodeId}):`];
|
|
449
|
+
record.lines.forEach((line, i) => {
|
|
450
|
+
lines.push(i < bodyCount ? ` ${line}` : ` - ${line}`);
|
|
451
|
+
});
|
|
452
|
+
if (node?.hasRejectedDraft === true) {
|
|
453
|
+
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`);
|
|
454
|
+
}
|
|
455
|
+
return lines;
|
|
456
|
+
}
|
|
457
|
+
export const CAST_LEDGER_BASE = HELPER_LEDGER_BASE + 10000;
|
|
458
|
+
function isPullSceneIndex(sceneIndex) {
|
|
459
|
+
return sceneIndex >= 0 && sceneIndex < HELPER_LEDGER_BASE;
|
|
460
|
+
}
|
|
381
461
|
const FALLBACK_EXT = {
|
|
382
462
|
image: "png",
|
|
383
463
|
video: "mp4",
|
|
@@ -394,6 +474,10 @@ function extFor(url, family) {
|
|
|
394
474
|
export function scenePrefix(sceneIndex) {
|
|
395
475
|
return `scene-${String(sceneIndex).padStart(2, "0")}`;
|
|
396
476
|
}
|
|
477
|
+
function clipQcOf(item) {
|
|
478
|
+
const artifact = item?.artifact;
|
|
479
|
+
return artifact && artifact.type === "video" ? artifact.qc?.takes : undefined;
|
|
480
|
+
}
|
|
397
481
|
function outputsOfNodeKind(run, kind) {
|
|
398
482
|
return run.nodes.filter((n) => n.kind === kind).flatMap((n) => n.outputs ?? []);
|
|
399
483
|
}
|
|
@@ -576,6 +660,8 @@ export function planPull(run, items, opts) {
|
|
|
576
660
|
for (const frame of artifact.frames ?? []) {
|
|
577
661
|
if (!frame.imageUrl)
|
|
578
662
|
continue;
|
|
663
|
+
if (!isPullSceneIndex(frame.sceneIndex))
|
|
664
|
+
continue;
|
|
579
665
|
keyframeByScene.set(frame.sceneIndex, keyframeDownload(frame.sceneIndex, frame.imageUrl));
|
|
580
666
|
}
|
|
581
667
|
}
|
|
@@ -593,7 +679,7 @@ export function planPull(run, items, opts) {
|
|
|
593
679
|
};
|
|
594
680
|
continue;
|
|
595
681
|
}
|
|
596
|
-
if (typeof artifact.sceneIndex !== "number" || artifact.sceneIndex
|
|
682
|
+
if (typeof artifact.sceneIndex !== "number" || !isPullSceneIndex(artifact.sceneIndex))
|
|
597
683
|
continue;
|
|
598
684
|
voiceByScene.set(artifact.sceneIndex, {
|
|
599
685
|
download: {
|
|
@@ -634,6 +720,8 @@ export function planPull(run, items, opts) {
|
|
|
634
720
|
}
|
|
635
721
|
if (artifact.type !== "video" || typeof artifact.sceneIndex !== "number")
|
|
636
722
|
continue;
|
|
723
|
+
if (!isPullSceneIndex(artifact.sceneIndex))
|
|
724
|
+
continue;
|
|
637
725
|
const clip = clipFromArtifact(artifact.sceneIndex, artifact);
|
|
638
726
|
if (!clip)
|
|
639
727
|
continue;
|
|
@@ -643,6 +731,8 @@ export function planPull(run, items, opts) {
|
|
|
643
731
|
const frameItemByScene = new Map();
|
|
644
732
|
const sceneFrameNodeIds = new Set(run.nodes.filter((n) => n.kind === "scene-frames").map((n) => n.nodeId));
|
|
645
733
|
for (const item of items) {
|
|
734
|
+
if (!isPullSceneIndex(item.sceneIndex))
|
|
735
|
+
continue;
|
|
646
736
|
if (item.itemKind === "clip")
|
|
647
737
|
clipItemByScene.set(item.sceneIndex, item);
|
|
648
738
|
if (item.itemKind === "frame" && sceneFrameNodeIds.has(item.nodeId)) {
|
|
@@ -674,7 +764,7 @@ export function planPull(run, items, opts) {
|
|
|
674
764
|
...frameItemByScene.keys(),
|
|
675
765
|
]),
|
|
676
766
|
]
|
|
677
|
-
.filter((sceneIndex) => sceneIndex
|
|
767
|
+
.filter((sceneIndex) => isPullSceneIndex(sceneIndex))
|
|
678
768
|
.sort((a, b) => a - b);
|
|
679
769
|
const scenes = sceneIndexes.map((sceneIndex) => {
|
|
680
770
|
const clip = clipByScene.get(sceneIndex);
|
|
@@ -709,7 +799,7 @@ export function planPull(run, items, opts) {
|
|
|
709
799
|
: "this-file",
|
|
710
800
|
voice: voice?.download.file ?? null,
|
|
711
801
|
keyframe: keyframeByScene.get(sceneIndex)?.file ?? null,
|
|
712
|
-
qc: clip?.qc
|
|
802
|
+
qc: clip?.qc ? qcWithoutJudgeWording(clip.qc) : null,
|
|
713
803
|
revoiced: clip ? clip.revoiced : null,
|
|
714
804
|
speechTrimmed: clip ? clip.speechTrimmed : null,
|
|
715
805
|
rawStorageId: clip ? clip.rawStorageId : null,
|
|
@@ -983,7 +1073,10 @@ export async function statusFlow(runId, json, deps) {
|
|
|
983
1073
|
const items = itemsRes.data.items ?? [];
|
|
984
1074
|
const hasFinal = hasAttachedCut(items);
|
|
985
1075
|
const stop = resolveStop(classifyRun(run), hasFinal);
|
|
986
|
-
const
|
|
1076
|
+
const failedStoryboard = failedStoryboardNode(run);
|
|
1077
|
+
const guidance = stopLines(stop.at === "failed" && errorEchoesReasons(stop.error, failedStoryboard?.planFailure)
|
|
1078
|
+
? { ...stop, error: undefined }
|
|
1079
|
+
: stop, runId, reviewUrl(deps.dashboardUrl, run));
|
|
987
1080
|
const warnings = run.nodes
|
|
988
1081
|
.filter((n) => n.warning)
|
|
989
1082
|
.map((n) => ({ nodeId: n.nodeId, step: n.kind, warning: n.warning }));
|
|
@@ -991,7 +1084,20 @@ export async function statusFlow(runId, json, deps) {
|
|
|
991
1084
|
return {
|
|
992
1085
|
code: 0,
|
|
993
1086
|
lines: [
|
|
994
|
-
JSON.stringify({
|
|
1087
|
+
JSON.stringify({
|
|
1088
|
+
runId,
|
|
1089
|
+
status: run.status,
|
|
1090
|
+
stop,
|
|
1091
|
+
items,
|
|
1092
|
+
hasFinal,
|
|
1093
|
+
warnings,
|
|
1094
|
+
guidance,
|
|
1095
|
+
...(failedStoryboard?.planFailure ? { planFailure: failedStoryboard.planFailure } : {}),
|
|
1096
|
+
...(failedStoryboard?.planFailure
|
|
1097
|
+
? { storyboardNodeId: failedStoryboard.nodeId }
|
|
1098
|
+
: {}),
|
|
1099
|
+
...(failedStoryboard?.hasRejectedDraft === true ? { hasRejectedDraft: true } : {}),
|
|
1100
|
+
}),
|
|
995
1101
|
],
|
|
996
1102
|
};
|
|
997
1103
|
}
|
|
@@ -1026,6 +1132,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1026
1132
|
...(carryingOn(run) ? [CARRYING_ON_LINE] : []),
|
|
1027
1133
|
...guidance,
|
|
1028
1134
|
...(stop.at === "running" && run.pauseAhead ? [pauseAheadLine(run.pauseAhead)] : []),
|
|
1135
|
+
...planFailureLines(failedStoryboard, runId),
|
|
1029
1136
|
...warnings.map((w) => `Heads-up (${w.step}): ${w.warning}`),
|
|
1030
1137
|
];
|
|
1031
1138
|
if (byScene.size === 0) {
|
|
@@ -1038,6 +1145,11 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1038
1145
|
lines.push(`${String(sceneIndex).padEnd(5)} ${itemWord(row.clip).padEnd(8)} ${itemWord(row.voiceover).padEnd(8)} ${itemWord(row.frame)}`);
|
|
1039
1146
|
if (row.clip?.lastRedo)
|
|
1040
1147
|
lines.push(` clip: ${row.clip.lastRedo.label}`);
|
|
1148
|
+
if (row.clip?.lastRedo || row.clip?.flagged) {
|
|
1149
|
+
for (const line of renderQcTakeHistory(clipQcOf(row.clip))) {
|
|
1150
|
+
lines.push(` ${line}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1041
1153
|
if (row.voiceover?.lastRedo)
|
|
1042
1154
|
lines.push(` voice: ${row.voiceover.lastRedo.label}`);
|
|
1043
1155
|
if (row.frame?.lastRedo)
|
|
@@ -1074,6 +1186,45 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1074
1186
|
: "Final cut: not uploaded yet.");
|
|
1075
1187
|
return { code: 0, lines };
|
|
1076
1188
|
}
|
|
1189
|
+
export async function rejectedDraftFlow(runId, json, deps) {
|
|
1190
|
+
const runRes = await deps.get(`${RUN_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
1191
|
+
if (!runRes.ok)
|
|
1192
|
+
return errorResult(runRes, json);
|
|
1193
|
+
const node = failedStoryboardNode(asVideoRun(runRes.data));
|
|
1194
|
+
if (!node) {
|
|
1195
|
+
const note = "The storyboard on this run didn't fail, so there is no rejected draft to read.";
|
|
1196
|
+
return {
|
|
1197
|
+
code: 0,
|
|
1198
|
+
lines: json ? [JSON.stringify({ runId, nodeId: null, draft: null })] : [note],
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
const res = await deps.get(`${REJECTED_DRAFT_PATH}?runId=${encodeURIComponent(runId)}&nodeId=${encodeURIComponent(node.nodeId)}`);
|
|
1202
|
+
if (missingRouteLine(res, "exodus video status --rejected-draft")) {
|
|
1203
|
+
return {
|
|
1204
|
+
code: 1,
|
|
1205
|
+
lines: json
|
|
1206
|
+
? [JSON.stringify({ ok: false, status: 404, error: REJECTED_DRAFT_NOT_ON_THIS_SERVER })]
|
|
1207
|
+
: [REJECTED_DRAFT_NOT_ON_THIS_SERVER],
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
if (!res.ok)
|
|
1211
|
+
return errorResult(res, json);
|
|
1212
|
+
const draft = res.data.draft ?? null;
|
|
1213
|
+
if (json)
|
|
1214
|
+
return { code: 0, lines: [JSON.stringify({ runId, nodeId: node.nodeId, draft })] };
|
|
1215
|
+
if (!draft)
|
|
1216
|
+
return { code: 0, lines: ["No rejected draft was kept for this attempt."] };
|
|
1217
|
+
return {
|
|
1218
|
+
code: 0,
|
|
1219
|
+
lines: [
|
|
1220
|
+
`REJECTED DRAFT — ad run ${runId}, step ${node.nodeId}`,
|
|
1221
|
+
"The system turned this draft down and threw it away. It is kept only as a record of what happened.",
|
|
1222
|
+
"Nothing in it is part of your ad. There is nothing here to act on or edit.",
|
|
1223
|
+
"",
|
|
1224
|
+
...draft.split("\n"),
|
|
1225
|
+
],
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1077
1228
|
export async function storyboardFlow(runId, json, deps) {
|
|
1078
1229
|
const res = await deps.get(`${STORYBOARD_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
1079
1230
|
if (!res.ok)
|
|
@@ -2066,8 +2217,12 @@ export async function run(flags, occurrences) {
|
|
|
2066
2217
|
if (needsRunId.includes(sub) && !runId) {
|
|
2067
2218
|
usage(`video ${sub} needs a run id: exodus video ${sub} <runId>`);
|
|
2068
2219
|
}
|
|
2069
|
-
if (sub === "status")
|
|
2220
|
+
if (sub === "status") {
|
|
2221
|
+
if (flags["rejected-draft"] !== undefined && flags["rejected-draft"] !== false) {
|
|
2222
|
+
return printResult(await rejectedDraftFlow(runId, json, defaultDeps));
|
|
2223
|
+
}
|
|
2070
2224
|
return printResult(await statusFlow(runId, json, defaultDeps));
|
|
2225
|
+
}
|
|
2071
2226
|
if (sub === "storyboard")
|
|
2072
2227
|
return printResult(await storyboardFlow(runId, json, defaultDeps));
|
|
2073
2228
|
if (sub === "approve") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HELPER_LEDGER_BASE = 900000;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const HELPER_LEDGER_BASE = 900000;
|