@xiaohhhh1/canvas-agent 0.4.57 → 0.4.59

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.
@@ -2,9 +2,9 @@
2
2
  export declare const FLOW_C_SCRIPT_CHUNK_MAX = 30;
3
3
  export declare const FLOW_C_SCRIPT_CHUNK_SIZES: readonly [10, 5, 1];
4
4
  /**
5
- * 长视频每条都包含总脚本和 2/3 套完整分镜,不能沿用短脚本的 30 条输出量。
6
- * 小段回传既能更早显示进度,也避免单个 Codex turn 因输出过大而被中断。
5
+ * 20 秒在移除中心可确定性补齐的重复视觉/质量字段后,先安全尝试每回合 2 条;
6
+ * 任何结构或截断失败会降级到 1 条。30 秒输出仍更大,继续保持单条。
7
7
  */
8
- export declare function flowCScriptChunkSizes(durationSeconds: 10 | 20 | 30): readonly [10, 5, 1] | readonly [1];
9
- /** 将当前缺失 ordinal 切成可独立持久化的子批;长视频绝不恢复 30 条大回合。 */
8
+ export declare function flowCScriptChunkSizes(durationSeconds: 10 | 20 | 30): readonly [10, 5, 1] | readonly [2, 1] | readonly [1];
9
+ /** 将当前缺失 ordinal 切成受控子批;长视频绝不恢复 30 条大回合。 */
10
10
  export declare function flowCScriptChunks(durationSeconds: 10 | 20 | 30, ordinals: number[], size?: number): number[][];
@@ -2,17 +2,17 @@
2
2
  export const FLOW_C_SCRIPT_CHUNK_MAX = 30;
3
3
  export const FLOW_C_SCRIPT_CHUNK_SIZES = [10, 5, 1];
4
4
  /**
5
- * 长视频每条都包含总脚本和 2/3 套完整分镜,不能沿用短脚本的 30 条输出量。
6
- * 小段回传既能更早显示进度,也避免单个 Codex turn 因输出过大而被中断。
5
+ * 20 秒在移除中心可确定性补齐的重复视觉/质量字段后,先安全尝试每回合 2 条;
6
+ * 任何结构或截断失败会降级到 1 条。30 秒输出仍更大,继续保持单条。
7
7
  */
8
8
  export function flowCScriptChunkSizes(durationSeconds) {
9
9
  if (durationSeconds === 20)
10
- return [1];
10
+ return [2, 1];
11
11
  if (durationSeconds === 30)
12
12
  return [1];
13
13
  return FLOW_C_SCRIPT_CHUNK_SIZES;
14
14
  }
15
- /** 将当前缺失 ordinal 切成可独立持久化的子批;长视频绝不恢复 30 条大回合。 */
15
+ /** 将当前缺失 ordinal 切成受控子批;长视频绝不恢复 30 条大回合。 */
16
16
  export function flowCScriptChunks(durationSeconds, ordinals, size = flowCScriptChunkSizes(durationSeconds)[0]) {
17
17
  const chunks = [];
18
18
  for (let index = 0; index < ordinals.length; index += size)
@@ -160,6 +160,8 @@ export declare class WorkflowManager {
160
160
  status: string;
161
161
  } & {
162
162
  error?: string;
163
+ code?: string;
164
+ resetOrdinals?: unknown;
163
165
  }>;
164
166
  submitCandidateChunk(idValue: unknown, groupsValue: unknown): Promise<{
165
167
  accepted: number;
@@ -168,6 +170,8 @@ export declare class WorkflowManager {
168
170
  selectedCandidates: SelectedCandidate[];
169
171
  } & {
170
172
  error?: string;
173
+ code?: string;
174
+ resetOrdinals?: unknown;
171
175
  }>;
172
176
  downloadState(): {
173
177
  configured: boolean;
@@ -293,6 +297,9 @@ export declare function terminalScriptChunkError(results: Array<{
293
297
  error?: string;
294
298
  terminal?: boolean;
295
299
  }>): string;
300
+ export declare function scriptCreativeReplanOrdinals(results: unknown[]): number[];
301
+ export declare function creativeReplanOrdinals(error: unknown): number[];
302
+ export declare function recordCreativeReplanAttempts(attempts: Map<number, number>, ordinals: number[], limit?: number): void;
296
303
  export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[]): string;
297
304
  export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
298
305
  /**
@@ -253,6 +253,7 @@ export class WorkflowManager {
253
253
  record.activeChunks = 0;
254
254
  let chunkSizeIndex = 0;
255
255
  let candidateChunkSize = 2;
256
+ const creativeReplanAttempts = new Map();
256
257
  while (record.receivedOrdinals.length < task.requested_count) {
257
258
  task = await this.scriptTask(id);
258
259
  const selectedBefore = selectedCandidateOrdinals(task);
@@ -280,6 +281,22 @@ export class WorkflowManager {
280
281
  const terminalError = terminalScriptChunkError(results);
281
282
  if (terminalError)
282
283
  throw new Error(`创意或脚本结构化契约被 Codex 拒绝,已停止自动重试(${terminalError})`);
284
+ const replanOrdinals = scriptCreativeReplanOrdinals(results);
285
+ if (replanOrdinals.length) {
286
+ recordCreativeReplanAttempts(creativeReplanAttempts, replanOrdinals);
287
+ record.message = `中心发现创意骨架重复,正在为 ordinal ${replanOrdinals.join(", ")} 重新选题;已完成脚本保持不变`;
288
+ record.updatedAt = now();
289
+ this.save();
290
+ const replanResults = await Promise.all(replanOrdinals.map((ordinal) => this.runCandidateChunk(id, task, [ordinal], workspace.workspacePath)));
291
+ const replanTerminalError = terminalScriptChunkError(replanResults);
292
+ if (replanTerminalError)
293
+ throw new Error(`创意重新选题被 Codex 拒绝,已停止自动重试(${replanTerminalError})`);
294
+ const replanError = replanResults.find((result) => result.error)?.error;
295
+ if (replanError)
296
+ throw new Error(`创意重新选题失败(${replanError}),请点击重试`);
297
+ chunkSizeIndex = 0;
298
+ continue;
299
+ }
283
300
  if (record.receivedOrdinals.length > receivedBefore)
284
301
  chunkSizeIndex = 0;
285
302
  if (selectedCandidateOrdinals(task).length > selectedBefore.length) {
@@ -306,6 +323,22 @@ export class WorkflowManager {
306
323
  if (terminalError) {
307
324
  throw new Error(`本机脚本结构化契约被 Codex 拒绝,已停止自动重试且未提交缺失脚本。请先升级或修复 Canvas Agent,再手动重试(${terminalError})`);
308
325
  }
326
+ const replanOrdinals = scriptCreativeReplanOrdinals(results);
327
+ if (replanOrdinals.length) {
328
+ recordCreativeReplanAttempts(creativeReplanAttempts, replanOrdinals);
329
+ record.message = `中心发现创意骨架重复,正在为 ordinal ${replanOrdinals.join(", ")} 重新选题;已完成脚本保持不变`;
330
+ record.updatedAt = now();
331
+ this.save();
332
+ const replanResults = await Promise.all(replanOrdinals.map((ordinal) => this.runCandidateChunk(id, task, [ordinal], workspace.workspacePath)));
333
+ const replanTerminalError = terminalScriptChunkError(replanResults);
334
+ if (replanTerminalError)
335
+ throw new Error(`创意重新选题被 Codex 拒绝,已停止自动重试(${replanTerminalError})`);
336
+ const replanError = replanResults.find((result) => result.error)?.error;
337
+ if (replanError)
338
+ throw new Error(`创意重新选题失败(${replanError}),请点击重试`);
339
+ chunkSizeIndex = 0;
340
+ continue;
341
+ }
309
342
  const progressed = record.receivedOrdinals.length > before;
310
343
  if (progressed) {
311
344
  chunkSizeIndex = 0;
@@ -396,7 +429,11 @@ export class WorkflowManager {
396
429
  return { terminal: false };
397
430
  }
398
431
  catch (error) {
399
- return { error: error instanceof Error ? `结构化脚本未通过本机校验:${error.message}` : "结构化脚本未通过本机校验", terminal: false };
432
+ return {
433
+ error: error instanceof Error ? `结构化脚本未通过本机校验:${error.message}` : "结构化脚本未通过本机校验",
434
+ terminal: false,
435
+ replanOrdinals: creativeReplanOrdinals(error),
436
+ };
400
437
  }
401
438
  }
402
439
  /** 只记录阶段、ordinal 和毫秒数;禁止记录 prompt、令牌或中心能力。 */
@@ -572,6 +609,23 @@ export function compareScriptQueueRecords(left, right) {
572
609
  export function terminalScriptChunkError(results) {
573
610
  return results.find((result) => result.terminal)?.error || "";
574
611
  }
612
+ export function scriptCreativeReplanOrdinals(results) {
613
+ return [...new Set(results.flatMap((result) => (result && typeof result === "object" ? result.replanOrdinals || [] : [])).filter((ordinal) => Number.isInteger(ordinal) && ordinal > 0))].sort((left, right) => left - right);
614
+ }
615
+ export function creativeReplanOrdinals(error) {
616
+ if (!error || typeof error !== "object" || error.code !== "FLOW_C_CREATIVE_REPLAN_REQUIRED")
617
+ return [];
618
+ const values = error.resetOrdinals;
619
+ return Array.isArray(values) ? [...new Set(values.map(Number).filter((ordinal) => Number.isInteger(ordinal) && ordinal > 0))].sort((left, right) => left - right) : [];
620
+ }
621
+ export function recordCreativeReplanAttempts(attempts, ordinals, limit = 3) {
622
+ for (const ordinal of ordinals) {
623
+ const next = Number(attempts.get(ordinal) || 0) + 1;
624
+ attempts.set(ordinal, next);
625
+ if (next > limit)
626
+ throw new Error(`ordinal ${ordinal} 已重新选题 ${limit} 次仍与已完成脚本重复,已停止避免无效循环`);
627
+ }
628
+ }
575
629
  class ExpiredCapabilityError extends Error {
576
630
  }
577
631
  export function scriptChunkPrompt(id, task, ordinals) {
@@ -595,7 +649,7 @@ ${durationRules}
595
649
  3. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState;模型不要输出 continuity 或 continuityMode。
596
650
  4. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片、电视购物或伪造工厂/销量/来源。
597
651
  5. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
598
- 6. creativePlan 只补充脚本执行字段;Agent 会把选中卡的结构化裂变、文化、安全和 fingerprint 字段确定性并回,模型不要重复这些字段。
652
+ 6. creativePlan 只写 format、visualHook、conflict、escalation/turn、productIntervention、visibleProof、callbackMotivation 与 differentiationKey。Agent 会把选中卡的构图、材质、光线、文化、真实性边界和 fingerprint 确定性并回,中心会程序化计算质量门;模型不要重复这些字段。
599
653
  固定使用 GPT-5.6 Terra 高推理。storyboardLayoutVersion=${layoutVersion} 仅由中心管理。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
600
654
  }
601
655
  export function creativeCandidatePrompt(id, task, ordinals) {
@@ -765,8 +819,13 @@ async function commerceJson(url, token, tokenHeader, init = {}) {
765
819
  headers.set("content-type", "application/json");
766
820
  const response = await fetch(url, { ...init, headers, signal: AbortSignal.timeout(120_000) });
767
821
  const body = await response.json().catch(() => ({}));
768
- if (!response.ok)
769
- throw new Error(body.error || `中心接口返回 ${response.status}`);
822
+ if (!response.ok) {
823
+ const error = new Error(body.error || `中心接口返回 ${response.status}`);
824
+ error.status = response.status;
825
+ error.code = body.code;
826
+ error.resetOrdinals = body.resetOrdinals;
827
+ throw error;
828
+ }
770
829
  return body;
771
830
  }
772
831
  function loadState() {
@@ -26,37 +26,16 @@ function creativePlanSchema() {
26
26
  const properties = {
27
27
  format: text,
28
28
  visualHook: text,
29
- firstFrameFocus: text,
30
- visualContrast: text,
31
- sensoryTexture: text,
32
- motionPeak: text,
33
- compositionLighting: text,
34
- localNativeDetail: text,
35
- antiFlatness: text,
36
29
  conflict: text,
37
30
  escalation: nullableText,
38
31
  turn: nullableText,
39
32
  productIntervention: text,
40
33
  visibleProof: text,
41
34
  callbackMotivation: text,
42
- truthBoundary: text,
43
35
  differentiationKey: text,
44
- styleCardSummary: nullableText,
45
36
  };
46
37
  return object(properties);
47
38
  }
48
- function qualityGateSchema() {
49
- return object({
50
- hook: text,
51
- conflict: text,
52
- escalationOrTurn: text,
53
- productIntervention: text,
54
- visibleProof: text,
55
- callbackMotivation: text,
56
- truthBoundary: text,
57
- batchDifferentiation: text,
58
- });
59
- }
60
39
  function segmentSchema() {
61
40
  return object({
62
41
  voiceCue: text,
@@ -86,7 +65,6 @@ export function flowCScriptOutputSchema(durationSeconds, count) {
86
65
  sellingFormId: text,
87
66
  creativePlan: creativePlanSchema(),
88
67
  voiceProfile: voiceProfileSchema(),
89
- qualityGate: qualityGateSchema(),
90
68
  openingState: openingStateSchema(),
91
69
  };
92
70
  if (durationSeconds === 10)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.57",
3
+ "version": "0.4.59",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",