@xiaohhhh1/canvas-agent 0.4.67 → 0.4.68

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.
@@ -1,6 +1,7 @@
1
1
  import type { AgentEmit } from "../agent/types.js";
2
2
  import { type CanvasAgentConfig } from "../config.js";
3
3
  export declare const FLOW_C_CODEX_TURN_TIMEOUT_MS: number;
4
+ export declare const FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS = 36000;
4
5
  type ScriptStatus = "queued" | "running" | "complete" | "error" | "expired";
5
6
  type DownloadStatus = "waiting" | "running" | "complete" | "error" | "expired";
6
7
  type ScriptRecord = {
@@ -60,6 +61,8 @@ type SelectedCandidate = {
60
61
  ordinal: number;
61
62
  productIndex: number;
62
63
  learnedTemplateId?: string | null;
64
+ learnedTemplateSource?: Record<string, unknown> | null;
65
+ productIdentityProfile?: Record<string, unknown>;
63
66
  executionBlueprint?: string;
64
67
  templateMatch?: Record<string, unknown>;
65
68
  retimingInstruction?: string;
@@ -82,7 +85,7 @@ type SelectedCandidate = {
82
85
  creativeFingerprint: Record<string, string>;
83
86
  fingerprintKey?: string;
84
87
  selectionScore?: number;
85
- selectionBreakdown?: Record<string, number>;
88
+ selectionBreakdown?: Record<string, unknown>;
86
89
  selectionMode?: string;
87
90
  };
88
91
  type ScriptTask = {
@@ -314,6 +317,9 @@ export declare function terminalScriptChunkError(results: Array<{
314
317
  export declare function scriptCreativeReplanOrdinals(results: unknown[]): number[];
315
318
  export declare function creativeReplanOrdinals(error: unknown): number[];
316
319
  export declare function recordCreativeReplanAttempts(attempts: Map<number, number>, ordinals: number[], limit?: number): void;
320
+ export declare function isFlowCPromptPayloadTooLarge(error: unknown): error is Error & {
321
+ code: "FLOW_C_PROMPT_PAYLOAD_TOO_LARGE";
322
+ };
317
323
  export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[]): string;
318
324
  export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
319
325
  export declare function selectedBlueprintPromptPayload(values: SelectedCandidate[]): {
@@ -321,34 +327,13 @@ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate
321
327
  blueprintRef: string;
322
328
  executionBlueprint: string;
323
329
  }[];
324
- selectedCandidates: {
325
- blueprintRef: string;
330
+ productAdaptations: Record<string, unknown>[];
331
+ ordinalBindings: {
326
332
  ordinal: number;
327
333
  productIndex: number;
328
- learnedTemplateId?: string | null;
329
- templateMatch?: Record<string, unknown>;
330
- retimingInstruction?: string;
331
- visualPremise: string;
332
- mutationAxes: string[];
333
- culturalAnchors: Array<Record<string, unknown>>;
334
- spectacleEscalation: string;
335
- productProofAction: string;
336
- purchaseReason: string;
337
- truthBoundary: string;
338
- riskFlags: string[];
339
- firstFrame: string;
340
- firstFrameFocus: string;
341
- visualContrast: string;
342
- sensoryTexture: string;
343
- motionPeak: string;
344
- compositionLighting: string;
345
- localNativeDetail: string;
346
- antiFlatness: string;
347
- creativeFingerprint: Record<string, string>;
348
- fingerprintKey?: string;
349
- selectionScore?: number;
350
- selectionBreakdown?: Record<string, number>;
351
- selectionMode?: string;
334
+ blueprintRef: string | null;
335
+ adaptationRef: string;
336
+ variationSeed: string;
352
337
  }[];
353
338
  };
354
339
  /**
@@ -14,6 +14,7 @@ import { FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION, flowCCreativeCandidateOutpu
14
14
  import { flowCScriptOutputSchema, parseFlowCScriptOutput } from "./script-output.js";
15
15
  const STATE_FILE = path.join(CONFIG_DIR, "workflow-state.json");
16
16
  export const FLOW_C_CODEX_TURN_TIMEOUT_MS = 8 * 60 * 1000;
17
+ export const FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS = 36_000;
17
18
  /** 本机持久化的 Flow C 控制器:脚本交给本机 Codex,视频直接落盘。 */
18
19
  export class WorkflowManager {
19
20
  config;
@@ -377,7 +378,16 @@ export class WorkflowManager {
377
378
  }
378
379
  }
379
380
  async runCandidateChunk(id, task, ordinals, cwd) {
380
- const result = await runCodexWorkflowTurn(creativeCandidatePrompt(id, task, ordinals), this.emit, {
381
+ let prompt;
382
+ try {
383
+ prompt = creativeCandidatePrompt(id, task, ordinals);
384
+ }
385
+ catch (error) {
386
+ if (isFlowCPromptPayloadTooLarge(error))
387
+ return { error: error.message, terminal: false };
388
+ throw error;
389
+ }
390
+ const result = await runCodexWorkflowTurn(prompt, this.emit, {
381
391
  cwd,
382
392
  permissionMode: "full",
383
393
  timeoutMs: FLOW_C_CODEX_TURN_TIMEOUT_MS,
@@ -403,7 +413,16 @@ export class WorkflowManager {
403
413
  /** 一个 chunk 使用独立线程;解析与中心持久化失败不会影响并行 lane。 */
404
414
  async runScriptChunk(id, task, ordinals, cwd) {
405
415
  const durationSeconds = Number(task.duration_seconds || 10);
406
- const result = await runCodexWorkflowTurn(scriptChunkPrompt(id, task, ordinals), this.emit, {
416
+ let prompt;
417
+ try {
418
+ prompt = scriptChunkPrompt(id, task, ordinals);
419
+ }
420
+ catch (error) {
421
+ if (isFlowCPromptPayloadTooLarge(error))
422
+ return { error: error.message, terminal: false };
423
+ throw error;
424
+ }
425
+ const result = await runCodexWorkflowTurn(prompt, this.emit, {
407
426
  cwd,
408
427
  permissionMode: "full",
409
428
  timeoutMs: FLOW_C_CODEX_TURN_TIMEOUT_MS,
@@ -630,10 +649,21 @@ export function recordCreativeReplanAttempts(attempts, ordinals, limit = 3) {
630
649
  }
631
650
  class ExpiredCapabilityError extends Error {
632
651
  }
652
+ class FlowCPromptPayloadTooLargeError extends Error {
653
+ code = "FLOW_C_PROMPT_PAYLOAD_TOO_LARGE";
654
+ constructor(label, actual, limit) {
655
+ super(`${label}为 ${actual} 字符,超过单个模型回合上限 ${limit};本机将缩小脚本子批,单条仍超限时会明确暂停。`);
656
+ this.name = "FlowCPromptPayloadTooLargeError";
657
+ }
658
+ }
659
+ export function isFlowCPromptPayloadTooLarge(error) {
660
+ return Boolean(error && typeof error === "object" && error.code === "FLOW_C_PROMPT_PAYLOAD_TOO_LARGE");
661
+ }
633
662
  export function scriptChunkPrompt(id, task, ordinals) {
634
663
  const duration = Number(task.duration_seconds || 10);
635
664
  const layoutVersion = task.storyboard_layout_version || task.storyboardLayoutVersion || "director-table-scripted-v1";
636
665
  const products = relevantProductInputs(task, ordinals);
666
+ const productFacts = scriptPromptProductFacts(products);
637
667
  const selected = selectedCandidatesForOrdinals(task, ordinals);
638
668
  if (selected.size !== ordinals.length)
639
669
  throw new Error("中心尚未为当前 ordinal 完成创意选题");
@@ -641,17 +671,18 @@ export function scriptChunkPrompt(id, task, ordinals) {
641
671
  ? "每条只输出 openingState 和一个完整 0–10 秒 segment,不生成 masterScript。"
642
672
  : `每条只输出 openingState 和 ${duration / 10} 个各自 0–10 秒的 segments;Agent 会确定性合成 masterScript,不得重复输出总稿,也不得写 10–20 或 20–30 全局时轴。`;
643
673
  return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}。
644
- 目标市场:${task.market}。商品事实:${compactJson(products, 12_000)}
645
- 中心已按人工商品/类目标注完成模板匹配;下面每个 ordinal 通过 blueprintRef 引用一次反推后持久化的 executionBlueprint,是唯一结构权威。相同蓝图只传一次,直接把它换成当前商品执行,不要生成候选、重新选题或重新评分:
646
- ${compactJson(selectedBlueprintPromptPayload([...selected.values()]), 36_000)}
674
+ 目标市场:${task.market}。商品事实:${compactJson(productFacts, 12_000, "当前脚本子批的商品事实")}
675
+ 中心已完成当前 ordinal 的创意选择。executionBlueprints 只保存一次共享爆款详细蓝图;productAdaptations 只保存一次每个不同的当前商品执行卡;ordinalBindings 只用 blueprintRef/adaptationRef variationSeed 映射到具体脚本。存在 blueprintRef 时,对应 executionBlueprint 是唯一结构权威;没有 blueprintRef 时,对应 productAdaptation 是现存普通 Flow C 候选或用户框架的唯一执行权威。相同蓝图可供同一商品整批复用,直接替换成当前商品执行,不要生成候选、重新选题或重新评分:
676
+ ${compactJson(selectedBlueprintPromptPayload([...selected.values()]), FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS, "当前脚本子批的共享蓝图与商品适配")}
647
677
  ${durationRules}
648
678
  写作要求:
649
- 1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;不得扩大功效、稀释构图或换成普通模板。productProofAction 必须同时写清一个真实动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
679
+ 1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;保留爆款的因果时间线、节拍比例、镜头/剪辑节奏、动作压力、证明方式和购买逻辑,只替换原商品、人物、来源身份、文案及目标市场口播,不得稀释构图或换成普通模板。productProofAction 必须同时写清一个真实动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
650
680
  2. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须是目标市场 ${task.market} 的自然原生语言、偏快但清晰;导演说明统一用简洁制作英文。
651
681
  3. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState;模型不要输出 continuity 或 continuityMode。
652
682
  4. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片、电视购物或伪造工厂/销量/来源。
653
683
  5. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
654
684
  6. 不要输出 creativePlan、executionBindings 或 masterScript;这些字段由 Agent 从选中蓝图确定性回填/合成,避免重复 token 和脆弱的文字绑定校验。若 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾仍是最高权威。
685
+ 7. 商品身份只由当前商品标题、大概类目和按顺序提供的原商品图决定:第1张是主SKU身份图,后续图片只是同一SKU补充视角。任何爆款源商品、首帧、故事板或上一段尾帧都不得改变商品颜色、款式、结构、比例、材质、包装、标识位置、部件、配件和实际套装数量;修正身份时不得降低动作、运镜、节奏、景深、真实感或画质。
655
686
  固定使用 GPT-5.6 Terra 中等推理。storyboardLayoutVersion=${layoutVersion} 仅由中心管理。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
656
687
  }
657
688
  export function creativeCandidatePrompt(id, task, ordinals) {
@@ -659,9 +690,9 @@ export function creativeCandidatePrompt(id, task, ordinals) {
659
690
  const styleCards = relevantStyleCards(task.reference_style_cards || [], products, task.market).map(compactReferenceStyleCard).filter(Boolean).slice(0, 6);
660
691
  const ledger = (task.creative_fingerprint_ledger || []).slice(-60);
661
692
  return `Flow C creative-candidate stage for handoff ${id}. Set contractVersion exactly to ${FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION}. Return exactly one group for each ordinal: ${ordinals.join(", ")}; mapping: ${scriptProductAssignments(task.product_quantities, ordinals)}.
662
- Market: ${task.market}. Target output duration: ${Number(task.duration_seconds || 10)} seconds. Product facts: ${compactJson(products, 12_000)}
663
- Optional learned style abstractions: ${compactJson(styleCards, 5_000)}
664
- Already accepted fingerprint ledger: ${compactJson(ledger, 8_000)}
693
+ Market: ${task.market}. Target output duration: ${Number(task.duration_seconds || 10)} seconds. Product facts: ${compactJson(products, 12_000, "candidate product facts")}
694
+ Optional learned style abstractions: ${compactJsonArray(styleCards, 5_000, "candidate style cards", "first")}
695
+ Already accepted fingerprint ledger: ${compactJsonArray(ledger, 8_000, "candidate fingerprint ledger", "last")}
665
696
  For each ordinal return exactly 6 short, auditable candidate cards. These are visible structured alternatives, not hidden reasoning. Vary the overall creative skeleton through open dimensions such as relationship/identity, scene mismatch, scale/quantity, group reaction, viewpoint, sensory texture and proof action; do not rotate a fixed template list.
666
697
  Set learnedTemplateId to the id of one supplied learned-video-structure card when its product/market fit is credible; otherwise set it to null. A fitting learned template has priority over free ideation. Preserve its hook-to-proof-to-purchase-reason causal structure, but re-time it to the current target duration instead of stretching, truncating or copying the source timeline. The template is model-neutral evidence: never output Seedance wording or source-specific people, dialogue, wardrobe, set or exact shots. The same learnedTemplateId may be used for multiple ordinals when useful, but each adapted candidate must still have truthful product-specific content, native VO intent and a meaningfully changed opening/proof execution. If the product has a non-empty user-authored creativeBrief, this candidate stage is bypassed entirely and no learned template may influence the script.
667
698
  Every card must be surprising but physically filmable and product-relevant. productProofAction must state one truthful intended-use action and the observable result that remains visible in the same scene. purchaseReason must state one concrete reason the supported result matters to a real target buyer; it is not a generic CTA and must not invent price, discount, scarcity, sales, inventory or unsupported quantified performance. productProofAction, purchaseReason and truthBoundary must follow supplied facts. For food, baby, medical/efficacy and personal-hygiene products, reject unhygienic display, fear marketing, dangerous use and unsupported promises.
@@ -702,6 +733,16 @@ function relevantProductInputs(task, ordinals) {
702
733
  return products;
703
734
  return [{ productIndex: -1, title: "Server compatibility fallback", quantity: ordinals.length, creativeBrief: String(task.instructions || "").slice(0, 4_000) }];
704
735
  }
736
+ function scriptPromptProductFacts(products) {
737
+ return products.map((product) => ({
738
+ productIndex: product.productIndex,
739
+ title: product.title,
740
+ category: product.category || "",
741
+ quantity: product.quantity,
742
+ sellingForm: product.sellingForm || "",
743
+ productImageUrlsInExactOrder: product.productImageUrlsInExactOrder || [],
744
+ }));
745
+ }
705
746
  function selectedCandidateOrdinals(task) {
706
747
  return [...new Set((task.selected_candidates || []).map((candidate) => Number(candidate.ordinal)).filter(Number.isInteger))].sort((left, right) => left - right);
707
748
  }
@@ -711,17 +752,63 @@ function selectedCandidatesForOrdinals(task, ordinals) {
711
752
  }
712
753
  export function selectedBlueprintPromptPayload(values) {
713
754
  const blueprints = new Map();
714
- const selectedCandidates = values.map((candidate) => {
715
- const blueprint = String(candidate.executionBlueprint || "").trim().slice(0, 8_000);
716
- const blueprintRef = String(candidate.learnedTemplateId || candidate.fingerprintKey || `ordinal-${candidate.ordinal}`);
717
- if (blueprint && !blueprints.has(blueprintRef))
718
- blueprints.set(blueprintRef, blueprint);
719
- const { executionBlueprint: _executionBlueprint, ...compactCandidate } = candidate;
720
- return { ...compactCandidate, blueprintRef };
755
+ const blueprintRefByContent = new Map();
756
+ const adaptations = new Map();
757
+ const ordinalBindings = values.map((candidate) => {
758
+ const blueprint = String(candidate.executionBlueprint || "").trim();
759
+ let blueprintRef = null;
760
+ if (blueprint) {
761
+ blueprintRef = blueprintRefByContent.get(blueprint) || null;
762
+ if (!blueprintRef) {
763
+ const requestedRef = String(candidate.learnedTemplateId || `blueprint-${createHash("sha256").update(blueprint).digest("hex").slice(0, 16)}`);
764
+ const existing = blueprints.get(requestedRef);
765
+ blueprintRef = existing && existing.executionBlueprint !== blueprint
766
+ ? `${requestedRef}-${createHash("sha256").update(blueprint).digest("hex").slice(0, 12)}`
767
+ : requestedRef;
768
+ blueprints.set(blueprintRef, { blueprintRef, executionBlueprint: blueprint });
769
+ blueprintRefByContent.set(blueprint, blueprintRef);
770
+ }
771
+ }
772
+ const adaptation = {
773
+ productIndex: candidate.productIndex,
774
+ blueprintRef,
775
+ selectionMode: candidate.selectionMode || null,
776
+ templateMatch: candidate.templateMatch || null,
777
+ retimingInstruction: candidate.retimingInstruction || null,
778
+ productIdentityProfile: candidate.productIdentityProfile || null,
779
+ visualPremise: candidate.visualPremise,
780
+ mutationAxes: candidate.mutationAxes,
781
+ culturalAnchors: candidate.culturalAnchors,
782
+ spectacleEscalation: candidate.spectacleEscalation,
783
+ productProofAction: candidate.productProofAction,
784
+ purchaseReason: candidate.purchaseReason,
785
+ truthBoundary: candidate.truthBoundary,
786
+ riskFlags: candidate.riskFlags,
787
+ firstFrame: candidate.firstFrame,
788
+ firstFrameFocus: candidate.firstFrameFocus,
789
+ visualContrast: candidate.visualContrast,
790
+ sensoryTexture: candidate.sensoryTexture,
791
+ motionPeak: candidate.motionPeak,
792
+ compositionLighting: candidate.compositionLighting,
793
+ localNativeDetail: candidate.localNativeDetail,
794
+ antiFlatness: candidate.antiFlatness,
795
+ };
796
+ const signature = JSON.stringify(adaptation);
797
+ const adaptationRef = `product-${candidate.productIndex}-${createHash("sha256").update(signature).digest("hex").slice(0, 16)}`;
798
+ if (!adaptations.has(adaptationRef))
799
+ adaptations.set(adaptationRef, { adaptationRef, ...adaptation });
800
+ return {
801
+ ordinal: candidate.ordinal,
802
+ productIndex: candidate.productIndex,
803
+ blueprintRef,
804
+ adaptationRef,
805
+ variationSeed: candidate.fingerprintKey || `ordinal-${candidate.ordinal}`,
806
+ };
721
807
  });
722
808
  return {
723
- executionBlueprints: [...blueprints].map(([blueprintRef, executionBlueprint]) => ({ blueprintRef, executionBlueprint })),
724
- selectedCandidates,
809
+ executionBlueprints: [...blueprints.values()],
810
+ productAdaptations: [...adaptations.values()],
811
+ ordinalBindings,
725
812
  };
726
813
  }
727
814
  function selectedCandidatePlan(value) {
@@ -730,6 +817,8 @@ function selectedCandidatePlan(value) {
730
817
  return {
731
818
  visualPremise: value.visualPremise,
732
819
  learnedTemplateId: value.learnedTemplateId || null,
820
+ learnedTemplateSource: value.learnedTemplateSource || null,
821
+ productIdentityProfile: value.productIdentityProfile || {},
733
822
  mutationAxes: value.mutationAxes,
734
823
  culturalAnchors: value.culturalAnchors,
735
824
  spectacleEscalation: value.spectacleEscalation,
@@ -786,8 +875,22 @@ export function immediateScriptOrdinals(task, receivedOrdinals, candidateOrdinal
786
875
  const received = new Set(receivedOrdinals);
787
876
  return candidateOrdinals.filter((ordinal) => selected.has(ordinal) && !received.has(ordinal));
788
877
  }
789
- function compactJson(value, limit) {
790
- return JSON.stringify(value).slice(0, limit);
878
+ function compactJson(value, limit, label = "prompt JSON") {
879
+ const serialized = JSON.stringify(value);
880
+ if (serialized.length > limit)
881
+ throw new FlowCPromptPayloadTooLargeError(label, serialized.length, limit);
882
+ return serialized;
883
+ }
884
+ /** Drop only complete optional array entries so the prompt always receives valid JSON. */
885
+ function compactJsonArray(value, limit, label, keep) {
886
+ const items = [...value];
887
+ while (items.length > 1 && JSON.stringify(items).length > limit) {
888
+ if (keep === "last")
889
+ items.shift();
890
+ else
891
+ items.pop();
892
+ }
893
+ return compactJson(items, limit, label);
791
894
  }
792
895
  function scriptProductAssignments(productQuantities, ordinals) {
793
896
  const assignments = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.67",
3
+ "version": "0.4.68",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",