@lingjingai/scriptctl 0.62.2 → 0.62.4

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.
@@ -0,0 +1,4 @@
1
+ # 0.62.3
2
+
3
+ - 梗概、文本整理与结构化文本任务遇到明确的内容审核拒绝时,使用已配置的豆包 Pro Ark Responses 端点兜底。
4
+ - 保持原始文本、结构化 Schema、取消信号及每条实际模型线路的 token 与调用成本记录。
@@ -0,0 +1,5 @@
1
+ # 0.62.4
2
+
3
+ - `review --converge-only` 只运行全剧收敛,支持客户端保留已完成集后的续跑。
4
+ - 审核 NDJSON 增加 `unit-started` 事件,提供真实的分集与全剧阶段开始状态。
5
+ - 流式命令在 JSON 模式下直接结束,保持每行一个完整事件。
package/dist/bin.js CHANGED
@@ -1043,7 +1043,7 @@ var AnthropicModel = class {
1043
1043
  }
1044
1044
  };
1045
1045
 
1046
- // src/infra/llm/doubao-video-model.ts
1046
+ // src/infra/llm/doubao-model.ts
1047
1047
  import * as fs7 from "node:fs";
1048
1048
  import * as path7 from "node:path";
1049
1049
  import { File } from "node:buffer";
@@ -1173,7 +1173,7 @@ function httpError(status, message2, detail, context = {}) {
1173
1173
  });
1174
1174
  }
1175
1175
 
1176
- // src/infra/llm/doubao-video-model.ts
1176
+ // src/infra/llm/doubao-model.ts
1177
1177
  var POLL_INTERVAL_MS = 2e3;
1178
1178
  function delay2(ms) {
1179
1179
  return new Promise((resolve19) => setTimeout(resolve19, ms));
@@ -1203,7 +1203,7 @@ function extractText(res) {
1203
1203
  }
1204
1204
  return parts.join("").trim();
1205
1205
  }
1206
- var DoubaoVideoModel = class {
1206
+ var DoubaoModel = class {
1207
1207
  constructor(model, timeoutMs, maxAttempts, apiKey, baseUrl, client) {
1208
1208
  this.model = model;
1209
1209
  this.timeoutMs = timeoutMs;
@@ -1278,12 +1278,45 @@ var DoubaoVideoModel = class {
1278
1278
  if (request2.responseJsonSchema) {
1279
1279
  params["text"] = { format: { type: "json_schema", name: "video_serial", schema: request2.responseJsonSchema } };
1280
1280
  }
1281
+ return this.generateResponse(params, request2.signal, Boolean(request2.responseJsonSchema));
1282
+ }
1283
+ async generateText(request2) {
1284
+ const params = this.textParams(request2);
1285
+ if (request2.responseFormat) params["text"] = { format: { type: request2.responseFormat } };
1286
+ return this.generateResponse(params, request2.signal, Boolean(request2.responseFormat));
1287
+ }
1288
+ async generateTool(request2) {
1289
+ const params = this.textParams(request2);
1290
+ params["text"] = { format: {
1291
+ type: "json_schema",
1292
+ name: request2.tool.name,
1293
+ description: request2.tool.description,
1294
+ schema: request2.tool.inputSchema
1295
+ } };
1296
+ const response = await this.generateResponse(params, request2.signal, true);
1297
+ return {
1298
+ input: JSON.parse(response.text),
1299
+ finishReason: response.finishReason,
1300
+ attempts: response.attempts,
1301
+ usage: response.usage,
1302
+ raw: response.raw
1303
+ };
1304
+ }
1305
+ textParams(request2) {
1306
+ return {
1307
+ model: this.model,
1308
+ max_output_tokens: request2.maxOutputTokens,
1309
+ instructions: request2.system,
1310
+ input: [{ role: "user", content: [{ type: "input_text", text: request2.user }] }]
1311
+ };
1312
+ }
1313
+ async generateResponse(params, signal, structured) {
1281
1314
  const { value, attempts, failedUsage } = await withTransientRetry(this.maxAttempts, async () => {
1282
1315
  let res;
1283
1316
  try {
1284
- res = await this.client.createResponse(params, request2.signal);
1317
+ res = await this.client.createResponse(params, signal);
1285
1318
  } catch (error) {
1286
- throw toArkError(error, request2.signal, "generate", this.model);
1319
+ throw toArkError(error, signal, "generate", this.model);
1287
1320
  }
1288
1321
  const status = (res.status ?? "").toLowerCase();
1289
1322
  const usage = arkUsage(res.usage);
@@ -1294,11 +1327,11 @@ var DoubaoVideoModel = class {
1294
1327
  }
1295
1328
  const text2 = extractText(res);
1296
1329
  if (!text2) throw new LlmTransportError("empty_response", "Doubao returned an empty response.", { retryable: true, usage });
1297
- if (request2.responseJsonSchema) {
1330
+ if (structured) {
1298
1331
  try {
1299
1332
  JSON.parse(text2);
1300
1333
  } catch {
1301
- throw new LlmTransportError("invalid_response", "Doubao returned malformed JSON for a structured video response.", {
1334
+ throw new LlmTransportError("invalid_response", "Doubao returned malformed JSON for a structured response.", {
1302
1335
  retryable: true,
1303
1336
  usage,
1304
1337
  context: { provider: "doubao", model: this.model, operation: "generate" }
@@ -2457,6 +2490,12 @@ var ModelRouter = class {
2457
2490
  // Resolve an explicit Doubao route or the default fallback; absent fallback credentials skip it.
2458
2491
  // Overridable in tests to inject a fake Ark client.
2459
2492
  doubaoAdapter(route) {
2493
+ return this.arkAdapter(route);
2494
+ }
2495
+ doubaoTextAdapter() {
2496
+ return this.arkAdapter();
2497
+ }
2498
+ arkAdapter(route) {
2460
2499
  const apiKey = route ? this.videoCredential(route) : (process.env[DOUBAO_FALLBACK.apiKeyEnv] ?? "").trim();
2461
2500
  if (!apiKey) return null;
2462
2501
  const model = route?.info.model ?? this.doubaoModel();
@@ -2464,7 +2503,7 @@ var ModelRouter = class {
2464
2503
  const key = `doubao ${baseUrl} ${model}`;
2465
2504
  const cached2 = this.doubaoAdapters.get(key);
2466
2505
  if (cached2) return cached2;
2467
- const adapter = new DoubaoVideoModel(model, DOUBAO_FALLBACK.timeoutMs, DOUBAO_FALLBACK.maxAttempts, apiKey, baseUrl);
2506
+ const adapter = new DoubaoModel(model, DOUBAO_FALLBACK.timeoutMs, DOUBAO_FALLBACK.maxAttempts, apiKey, baseUrl);
2468
2507
  this.doubaoAdapters.set(key, adapter);
2469
2508
  return adapter;
2470
2509
  }
@@ -2472,19 +2511,29 @@ var ModelRouter = class {
2472
2511
  doubaoModel() {
2473
2512
  return (process.env[DOUBAO_FALLBACK.modelEnv] ?? "").trim() || DOUBAO_FALLBACK.model;
2474
2513
  }
2514
+ async withModerationFallback(task, signal, generate) {
2515
+ const route = this.resolve(task);
2516
+ const adapter = this.adapter(route);
2517
+ try {
2518
+ return await this.tracked(route.info, () => generate(adapter));
2519
+ } catch (error) {
2520
+ if (signal?.aborted || !(error instanceof LlmTransportError) || error.code !== "content_filtered") throw error;
2521
+ const doubao = this.doubaoTextAdapter();
2522
+ if (!doubao) throw error;
2523
+ process.stderr.write(`\u21AA\uFE0F \u6587\u672C\u5185\u5BB9\u5BA1\u6838\u62D2\u7EDD\uFF0C\u56DE\u9000\u5230\u8C46\u5305 ${this.doubaoModel()}
2524
+ `);
2525
+ return this.tracked(this.doubaoRoute(task), () => generate(doubao));
2526
+ }
2527
+ }
2475
2528
  async generateText(task, request2) {
2476
2529
  const spec2 = LLM_TASK_REGISTRY[task];
2477
2530
  if (spec2.mode !== "text") throw configRuntimeError("The task does not support text generation.", ["text task"], [`task: ${task}`]);
2478
- const route = this.resolve(task);
2479
- const adapter = this.adapter(route);
2480
- return this.tracked(route.info, () => adapter.generateText({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
2531
+ return this.withModerationFallback(task, request2.signal, (adapter) => adapter.generateText({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
2481
2532
  }
2482
2533
  async generateTool(task, request2) {
2483
2534
  const spec2 = LLM_TASK_REGISTRY[task];
2484
2535
  if (spec2.mode !== "tool") throw configRuntimeError("The task does not support tool generation.", ["tool task"], [`task: ${task}`]);
2485
- const route = this.resolve(task);
2486
- const adapter = this.adapter(route);
2487
- return this.tracked(route.info, () => adapter.generateTool({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
2536
+ return this.withModerationFallback(task, request2.signal, (adapter) => adapter.generateTool({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
2488
2537
  }
2489
2538
  };
2490
2539
  function createLlmExecutor(explicitConfigPath, options) {
@@ -2956,6 +3005,7 @@ function tableText(data) {
2956
3005
  ].join("\n");
2957
3006
  }
2958
3007
  function emit(output, exitCode, jsonMode, format) {
3008
+ if (output.text === "" && Object.keys(output.data).length === 0) return;
2959
3009
  if (format === "addr" && hasRows(output.data)) {
2960
3010
  if (output.data.rows.length === 0) {
2961
3011
  process.stdout.write("");
@@ -2977,7 +3027,6 @@ function emit(output, exitCode, jsonMode, format) {
2977
3027
  payload["exit_code"] = exitCode;
2978
3028
  process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
2979
3029
  } else {
2980
- if (output.text === "" && Object.keys(output.data).length === 0) return;
2981
3030
  process.stdout.write(withNewline(tableText(output.data) ?? output.text));
2982
3031
  }
2983
3032
  }
@@ -3216,6 +3265,7 @@ async function commandReview(opts) {
3216
3265
  ...episodeList(strOf(opts["episodes"])) ? { episodes: episodeList(strOf(opts["episodes"])) } : {},
3217
3266
  concurrency: positive(opts, "concurrency", 5),
3218
3267
  converge: opts["converge"] !== false,
3268
+ convergeOnly: opts["converge_only"] === true,
3219
3269
  apiKey: profile.apiKey,
3220
3270
  model: profile.model,
3221
3271
  mediaModel: profile.model,
@@ -3244,7 +3294,7 @@ function registerReviewCommand(program, state2) {
3244
3294
  "\u7B2C N \u96C6\u7684\u89C6\u9891\uFF1B\u6309\u96C6\u987A\u5E8F\u91CD\u590D\u4F20\uFF0C\u8986\u76D6 -s \u7684\u76EE\u5F55\u6392\u5E8F",
3245
3295
  (value, previous) => [...previous, value],
3246
3296
  []
3247
- ).option("--episodes <list>", "\u53EA\u5BA1\u8FD9\u4E9B\u96C6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u9ED8\u8BA4\u5168\u5267\uFF09").option("--concurrency <n>", "\u540C\u65F6\u5728\u98DE\u7684\u96C6\u6570", Number).option("--episode-timeout-ms <ms>", "\u5355\u96C6\u8D85\u65F6\uFF1B\u5230\u70B9\u6536\u5C3E\u5E76\u4FDD\u7559\u5DF2\u8BB0\u5F55\u7684\u7ED3\u8BBA", Number).option("--max-steps <n>", "\u5355\u96C6\u6700\u591A\u51E0\u6B65\u5DE5\u5177\u8C03\u7528", Number).option("--media-budget <n>", "\u5355\u96C6\u6700\u591A\u51E0\u6B21\u89C6\u9891\u6838\u5BF9", Number).option("--no-converge", "\u8DF3\u8FC7\u5168\u5267\u6536\u655B\u9636\u6BB5").option("--model <profile>", "\u8986\u76D6 LLM profile\uFF08\u9ED8\u8BA4 review-agent\uFF09").option("--llm-config <path>", "LLM configuration file").option("--check", "\u53EA\u68C0\u67E5\u6A21\u578B\u4E0E agent \u662F\u5426\u5C31\u7EEA\uFF0C\u4E0D\u5BA1\u6838").action(dispatch(commandReview, state2));
3297
+ ).option("--episodes <list>", "\u53EA\u5BA1\u8FD9\u4E9B\u96C6\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u9ED8\u8BA4\u5168\u5267\uFF09").option("--concurrency <n>", "\u540C\u65F6\u5728\u98DE\u7684\u96C6\u6570", Number).option("--episode-timeout-ms <ms>", "\u5355\u96C6\u8D85\u65F6\uFF1B\u5230\u70B9\u6536\u5C3E\u5E76\u4FDD\u7559\u5DF2\u8BB0\u5F55\u7684\u7ED3\u8BBA", Number).option("--max-steps <n>", "\u5355\u96C6\u6700\u591A\u51E0\u6B65\u5DE5\u5177\u8C03\u7528", Number).option("--media-budget <n>", "\u5355\u96C6\u6700\u591A\u51E0\u6B21\u89C6\u9891\u6838\u5BF9", Number).option("--converge-only", "\u53EA\u6267\u884C\u5168\u5267\u6536\u655B\uFF0C\u4E0D\u91CD\u65B0\u5BA1\u6838\u5404\u96C6").option("--no-converge", "\u8DF3\u8FC7\u5168\u5267\u6536\u655B\u9636\u6BB5").option("--model <profile>", "\u8986\u76D6 LLM profile\uFF08\u9ED8\u8BA4 review-agent\uFF09").option("--llm-config <path>", "LLM configuration file").option("--check", "\u53EA\u68C0\u67E5\u6A21\u578B\u4E0E agent \u662F\u5426\u5C31\u7EEA\uFF0C\u4E0D\u5BA1\u6838").action(dispatch(commandReview, state2));
3248
3298
  }
3249
3299
 
3250
3300
  // src/cli/register-script.ts
@@ -4552,67 +4602,6 @@ function spanAddresses(doc2, actions) {
4552
4602
  });
4553
4603
  }
4554
4604
 
4555
- // packages/screenplay/dist/references.js
4556
- function references(doc2, address, opts = {}) {
4557
- const offset = opts.offset ?? 0, limit = opts.limit ?? 20, context = opts.context ?? 3;
4558
- for (const [name, value, minimum] of [["offset", offset, 0], ["limit", limit, 1], ["context", context, 0]]) {
4559
- if (!Number.isSafeInteger(value) || value < minimum)
4560
- throw new Error(`${name} must be an integer >= ${minimum}`);
4561
- }
4562
- const node = resolve6(doc2, address);
4563
- if (node.t !== "asset" && node.t !== "state")
4564
- throw new Error("references requires an asset or asset/state address; use read for episodes/scenes/actions");
4565
- const asset2 = node.asset;
4566
- const state2 = node.t === "state" ? node.state.id : null;
4567
- const names = [.../* @__PURE__ */ new Set([asset2.id, asset2.name, ...asset2.aliases])].filter(Boolean);
4568
- const hits = [];
4569
- for (const ep of doc2.episodes)
4570
- for (const scene of ep.scenes) {
4571
- const bindings = [...scene.location ? [scene.location] : [], ...scene.actors, ...scene.props].filter((ref2) => ref2.id === asset2.id && (state2 === null || ref2.states.includes(state2)));
4572
- if (bindings.length)
4573
- hits.push({ kind: "scene_binding", scene, matched: bindings.flatMap((ref2) => ref2.states) });
4574
- for (const action of scene.actions) {
4575
- if (action.speaker?.id === asset2.id && (state2 === null || action.speaker.state === state2)) {
4576
- hits.push({ kind: "speaker", scene, action, matched: [asset2.id] });
4577
- }
4578
- if (state2 === null) {
4579
- const matched = names.filter((name) => action.content.includes(name));
4580
- if (matched.length)
4581
- hits.push({ kind: "text_mention", scene, action, matched });
4582
- }
4583
- }
4584
- }
4585
- const end = offset + limit;
4586
- return {
4587
- asset_id: asset2.id,
4588
- state_id: state2,
4589
- total: hits.length,
4590
- next_offset: end < hits.length ? end : null,
4591
- items: hits.slice(offset, end).map(({ kind, scene, action, matched }) => {
4592
- const index = action ? scene.actions.indexOf(action) : -1;
4593
- const nearby = action ? scene.actions.slice(Math.max(0, index - context), index + context + 1) : [];
4594
- const gutter = Math.max(0, ...nearby.map((row) => row.anchor.length));
4595
- return {
4596
- kind,
4597
- address: action?.anchor ?? scene.anchor,
4598
- scene_address: scene.anchor,
4599
- episode: scene.ep.no,
4600
- matched,
4601
- text: [formatSceneHead(doc2, scene), ...nearby.map((row) => (row === action ? "> " : " ") + formatAction(doc2, row, gutter))].join("\n")
4602
- };
4603
- })
4604
- };
4605
- }
4606
- function formatReferences(result) {
4607
- return [
4608
- `${result.asset_id}${result.state_id === null ? "" : "/" + result.state_id} ${result.total} references \xB7 showing ${result.items.length}`,
4609
- "scene_binding=explicit scene presence; speaker=explicit speaker; text_mention=name/alias mention only (not proof of presence).",
4610
- ...result.items.map((hit) => `${hit.address} ${hit.kind} ep=${hit.episode}
4611
- ${hit.text}`),
4612
- result.next_offset === null ? "end" : `Continue: --offset ${result.next_offset}`
4613
- ].join("\n\n");
4614
- }
4615
-
4616
4605
  // packages/screenplay/dist/tools.js
4617
4606
  var NON_ACTOR_NAMES = ["\u65C1\u767D", "\u5E7F\u64AD", "\u753B\u5916", "\u7FA4\u58F0"];
4618
4607
  var NON_ACTOR_KIND = {
@@ -5740,15 +5729,6 @@ ${issues(d, {})}`
5740
5729
  ...opts["full"] ? { full: true } : {}
5741
5730
  }), "find"), state2));
5742
5731
  describeArguments(find2, ["\u6B63\u5219\uFF1B\u53EA\u6309 speaker/type \u8FC7\u6EE4\u65F6\u53EF\u7701\u7565"]);
5743
- const refs = withScriptOpts(program.command("refs <addr>"), "read").description("\u53EA\u8BFB\u5B9A\u4F4D\u8D44\u4EA7/\u9020\u578B\u7684\u573A\u666F\u7ED1\u5B9A\u3001\u8BF4\u8BDD\u5F15\u7528\u548C\u6B63\u6587\u63D0\u53CA\uFF0C\u8FD4\u56DE\u53EF read \u7684\u5730\u5740\u53CA\u4E0A\u4E0B\u6587").option("--offset <n>", "\u4ECE\u7B2C\u51E0\u6761\u5F15\u7528\u7EE7\u7EED\uFF08\u4ECE 0 \u5F00\u59CB\uFF09").option("--limit <n>", "\u672C\u6279\u5F15\u7528\u6570\uFF08\u9ED8\u8BA4 20\uFF09").option("--context <n>", "\u6B63\u6587\u547D\u4E2D\u524D\u540E\u5404\u51E0\u6761 action\uFF08\u9ED8\u8BA4 3\uFF09").action(dispatch((opts) => {
5744
- const result = references(doc(opts), arg(opts), {
5745
- offset: num(opts, "offset"),
5746
- limit: num(opts, "limit"),
5747
- context: num(opts, "context")
5748
- });
5749
- return [{ text: formatReferences(result), data: { op: "refs", changed: false, ...result } }, EXIT_OK];
5750
- }, state2));
5751
- describeArguments(refs, ["\u4EBA\u7269/\u573A\u666F/\u9053\u5177 ID \u6216\u540D\u79F0\uFF0C\u6216 <\u8D44\u4EA7>/<\u9020\u578B>\uFF1B\u6B63\u6587\u63D0\u53CA\u4E0D\u7B49\u4E8E\u5B9E\u9645\u51FA\u573A"]);
5752
5732
  const span2 = withScriptOpts(program.command("span <range>"), "read").description("\u628A\u4E00\u6BB5 action \u6362\u7B97\u6210\u96C6\u5185\u89C6\u9891\u65F6\u95F4\u7A97\u53E3\uFF08\u534A\u5F00\u533A\u95F4\uFF09").action(dispatch((opts) => text(() => span(doc(opts), arg(opts)), "span"), state2));
5753
5733
  describeArguments(span2, [`${ADDR}\uFF0C\u6216 \`\u8D77ID..\u6B62ID\``]);
5754
5734
  withScriptOpts(program.command("validate"), "read").description("\u8DD1\u6574\u672C\u6821\u9A8C").action(dispatch((opts) => text(() => validate(doc(opts)), "validate"), state2));