@lingjingai/scriptctl 0.63.12 → 0.63.14
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.
- package/changes/0.63.13.md +3 -0
- package/changes/0.63.14.md +7 -0
- package/dist/bin.js +58 -26
- package/dist/bin.js.map +3 -3
- package/dist/llm/tasks/ingest/video-serial.d.ts +11 -1
- package/dist/llm/tasks/ingest/video-serial.js +53 -28
- package/dist/llm/tasks/ingest/video-serial.js.map +1 -1
- package/dist/review-agent.js +29 -9
- package/dist/review-agent.js.map +2 -2
- package/dist/usecases/ingest/failure.js +2 -0
- package/dist/usecases/ingest/failure.js.map +1 -1
- package/dist/usecases/ingest/video-pipeline.js +3 -2
- package/dist/usecases/ingest/video-pipeline.js.map +1 -1
- package/package.json +19 -18
- package/skills/scriptctl/SKILL.md +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# 0.63.14
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
- `scriptctl ingest` appends each missing-timestamp response to `ingest-validation.jsonl`, including episode, time, attempt, missing count and locations, and retry exhaustion; history is retained after successful retries and resumed runs.
|
|
5
|
+
|
|
6
|
+
## Fixed
|
|
7
|
+
- `scriptctl ingest` rejects video episodes with missing or empty Action timestamps, retries the model up to three times, and errors without accepting the episode if timestamps remain missing.
|
package/dist/bin.js
CHANGED
|
@@ -16088,31 +16088,60 @@ function toSerialOutput(input) {
|
|
|
16088
16088
|
}
|
|
16089
16089
|
return out;
|
|
16090
16090
|
}
|
|
16091
|
-
async function runVideoSerial(port, media, episodeNo, contextText, fps, signal, local) {
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
parsed
|
|
16108
|
-
|
|
16109
|
-
|
|
16091
|
+
async function runVideoSerial(port, media, episodeNo, contextText, fps, signal, local, onTimestampMissing) {
|
|
16092
|
+
let correction = "";
|
|
16093
|
+
for (let attempt = 0; ; attempt++) {
|
|
16094
|
+
const response = await port.generate({
|
|
16095
|
+
media,
|
|
16096
|
+
system: VIDEO_SERIAL_SYSTEM,
|
|
16097
|
+
user: buildSerialUser(episodeNo, contextText + correction),
|
|
16098
|
+
fps,
|
|
16099
|
+
responseJsonSchema: VIDEO_SERIAL_SCHEMA,
|
|
16100
|
+
// Lets a cross-provider fallback (Doubao) re-upload the same transcode when the
|
|
16101
|
+
// Gemini handle can't be reused. The Gemini path ignores these.
|
|
16102
|
+
...local ? { localPath: local.path, localMime: local.mime } : {},
|
|
16103
|
+
...signal ? { signal } : {}
|
|
16104
|
+
});
|
|
16105
|
+
const raw = response.text.trim();
|
|
16106
|
+
if (raw.length < 40) throw serialError(`episode ${episodeNo}: serial output too short (finishReason=${response.finishReason})`, [`length: ${raw.length}`]);
|
|
16107
|
+
let parsed;
|
|
16108
|
+
try {
|
|
16109
|
+
parsed = JSON.parse(raw);
|
|
16110
|
+
} catch {
|
|
16111
|
+
throw serialError(`episode ${episodeNo}: serial output was not valid JSON (finishReason=${response.finishReason})`, [raw.slice(0, 200)]);
|
|
16112
|
+
}
|
|
16113
|
+
const clean2 = stripNul(parsed);
|
|
16114
|
+
const output = toSerialOutput(clean2);
|
|
16115
|
+
if (output.scenes.length === 0) throw serialError(`episode ${episodeNo}: serial output produced no scenes`, [raw.slice(0, 200)]);
|
|
16116
|
+
const missing = output.scenes.flatMap((scene, sceneIndex2) => scene.anchors.flatMap(
|
|
16117
|
+
(anchor, anchorIndex) => anchor.ts ? [] : [`scenes[${sceneIndex2}].anchors[${anchorIndex}] (${anchor.kind})`]
|
|
16118
|
+
));
|
|
16119
|
+
if (missing.length > 0) {
|
|
16120
|
+
onTimestampMissing?.({
|
|
16121
|
+
event: "timestamp_missing",
|
|
16122
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16123
|
+
episode: episodeNo,
|
|
16124
|
+
attempt: attempt + 1,
|
|
16125
|
+
missing_count: missing.length,
|
|
16126
|
+
missing_paths: missing,
|
|
16127
|
+
retries_exhausted: attempt === 3
|
|
16128
|
+
});
|
|
16129
|
+
if (attempt === 3) {
|
|
16130
|
+
throw new CliError(`Episode ${episodeNo}: ${missing.length} anchors still have no timestamp after 3 retries.`, {
|
|
16131
|
+
exitCode: EXIT_RUNTIME,
|
|
16132
|
+
errorCode: "INGEST_VIDEO_TIMESTAMP_MISSING",
|
|
16133
|
+
hint: "Inspect the episode error sidecar and rerun ingest to resume.",
|
|
16134
|
+
data: { actual: missing }
|
|
16135
|
+
});
|
|
16136
|
+
}
|
|
16137
|
+
correction = `
|
|
16138
|
+
|
|
16139
|
+
\u4E0A\u6B21\u8F93\u51FA\u6709 ${missing.length} \u6761\u955C\u5934\u6216\u58F0\u97F3\u7F3A\u5C11 ts\uFF0C\u672A\u88AB\u63A5\u53D7\u3002\u7F3A\u5931\u4F4D\u7F6E\uFF1A${missing.join("\u3001")}\u3002\u8BF7\u91CD\u65B0\u89C2\u770B\u5F53\u524D\u96C6\uFF0C\u4E3A\u6BCF\u4E00\u6761 shots \u548C voices \u586B\u5199\u771F\u5B9E\u8D77\u70B9 ts\uFF08MM:SS\uFF09\uFF0C\u4E0D\u5F97\u7F3A\u5931\u3001\u7559\u7A7A\u6216\u586B null\uFF1B\u8FD4\u56DE\u5B8C\u6574 JSON\uFF0C\u4E0D\u8981\u53EA\u8FD4\u56DE\u4FEE\u8865\u5B57\u6BB5\u3002`;
|
|
16140
|
+
continue;
|
|
16141
|
+
}
|
|
16142
|
+
const md = JSON.stringify(clean2, null, 2);
|
|
16143
|
+
return { md, output };
|
|
16110
16144
|
}
|
|
16111
|
-
const clean2 = stripNul(parsed);
|
|
16112
|
-
const output = toSerialOutput(clean2);
|
|
16113
|
-
if (output.scenes.length === 0) throw serialError(`episode ${episodeNo}: serial output produced no scenes`, [raw.slice(0, 200)]);
|
|
16114
|
-
const md = JSON.stringify(clean2, null, 2);
|
|
16115
|
-
return { md, output };
|
|
16116
16145
|
}
|
|
16117
16146
|
|
|
16118
16147
|
// src/usecases/ingest/failure.ts
|
|
@@ -16129,6 +16158,8 @@ function classificationForCliError(error) {
|
|
|
16129
16158
|
return { failure_type: "media", failure_code: "media_too_large" };
|
|
16130
16159
|
case "INGEST_VIDEO_SERIAL_FAILED":
|
|
16131
16160
|
return { failure_type: "pipeline", failure_code: "serial_parse_failed" };
|
|
16161
|
+
case "INGEST_VIDEO_TIMESTAMP_MISSING":
|
|
16162
|
+
return { failure_type: "validation", failure_code: "script_validation_failed" };
|
|
16132
16163
|
case "INGEST_SOURCE_MISMATCH":
|
|
16133
16164
|
return { failure_type: "pipeline", failure_code: "source_mismatch" };
|
|
16134
16165
|
case "INGEST_PIPELINE_MISMATCH":
|
|
@@ -16754,7 +16785,8 @@ async function runVideoIngestUnlocked(opts) {
|
|
|
16754
16785
|
generationContext,
|
|
16755
16786
|
fps,
|
|
16756
16787
|
sig,
|
|
16757
|
-
{ path: enc.path, mime: enc.mime }
|
|
16788
|
+
{ path: enc.path, mime: enc.mime },
|
|
16789
|
+
(event) => fs27.appendFileSync(store.path("ingest-validation.jsonl"), JSON.stringify(event) + "\n")
|
|
16758
16790
|
);
|
|
16759
16791
|
} catch (error) {
|
|
16760
16792
|
if (!(error instanceof CliError) || error.errorCode !== "INGEST_VIDEO_SERIAL_FAILED" || attempt === VIDEO_VALIDATION_ATTEMPTS) throw error;
|
|
@@ -16816,7 +16848,7 @@ async function runVideoIngestUnlocked(opts) {
|
|
|
16816
16848
|
const detail = formatFailure(sidecar.failure);
|
|
16817
16849
|
store.writeJson(`ep/ep_${pad5(e.episode)}.error.json`, sidecar);
|
|
16818
16850
|
store.writeJson("manifest.json", { ...manifest, updated: sidecar.occurred_at, current_error: sidecar });
|
|
16819
|
-
if (err instanceof CliError && (err.errorCode === "INGEST_VIDEO_INLINE_TOO_LARGE" || err.errorCode === "INGEST_VALIDATION_FAILED")) throw err;
|
|
16851
|
+
if (err instanceof CliError && (err.errorCode === "INGEST_VIDEO_INLINE_TOO_LARGE" || err.errorCode === "INGEST_VALIDATION_FAILED" || err.errorCode === "INGEST_VIDEO_TIMESTAMP_MISSING")) throw err;
|
|
16820
16852
|
throw new CliError(`Episode ${e.episode} failed serial parse; later episodes depend on its context.`, {
|
|
16821
16853
|
exitCode: EXIT_INPUT,
|
|
16822
16854
|
errorCode: "INGEST_SERIAL_INCOMPLETE",
|