@xiaohhhh1/canvas-agent 0.4.64 → 0.4.66

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.
@@ -36,7 +36,7 @@ export type CodexWorkflowRunResult = {
36
36
  };
37
37
  };
38
38
  export declare const FLOW_C_CODEX_MODEL = "gpt-5.6-terra";
39
- export declare const FLOW_C_CODEX_REASONING_EFFORT = "high";
39
+ export declare const FLOW_C_CODEX_REASONING_EFFORT = "medium";
40
40
  export declare const FLOW_C_CODEX_WORKER_CONCURRENCY: number;
41
41
  export declare function flowCCodexWorkerStatus(): {
42
42
  active: number;
@@ -7,7 +7,7 @@ import { CodexAppClient } from "./codex-client.js";
7
7
  import { summarizeCodexThread, threadMessages } from "./codex-history.js";
8
8
  import { boundedWorkerConcurrency, WorkerPool } from "./worker-pool.js";
9
9
  export const FLOW_C_CODEX_MODEL = "gpt-5.6-terra";
10
- export const FLOW_C_CODEX_REASONING_EFFORT = "high";
10
+ export const FLOW_C_CODEX_REASONING_EFFORT = "medium";
11
11
  export const FLOW_C_CODEX_WORKER_CONCURRENCY = boundedWorkerConcurrency(process.env.FLOW_C_SCRIPT_CONCURRENCY);
12
12
  export function flowCCodexWorkerStatus() { return { active: workflowCodexPool.activeCount, limit: FLOW_C_CODEX_WORKER_CONCURRENCY }; }
13
13
  let codexQueue = Promise.resolve();
@@ -71,6 +71,13 @@ export declare function verifiedVideoLearningSource(source: LocalVideoLearningSo
71
71
  export declare function localVideoAnalysisPrompt(source: LocalVideoLearningSource, durationMs: number, transcript: string, attachments: AgentAttachment[]): string;
72
72
  export declare function assertJointVisualAndSpokenEvidence(analysis: Record<string, unknown>): void;
73
73
  export declare function timedTranscriptFromVtt(value: string): string;
74
+ /**
75
+ * Keep the dense first three seconds, then add actual shot-change frames before
76
+ * uniform coverage. This follows shot-level video-understanding practice while
77
+ * staying inside the existing FFmpeg-only customer runtime.
78
+ */
79
+ export declare function sceneAwareVideoEvidenceTimestamps(videoFile: string, durationMs: number, workDir?: string): Promise<number[]>;
80
+ export declare function sceneChangeTimestampsFromMetadata(value: string, durationMs: number): number[];
74
81
  export declare function inspectVideoFile(videoFile: string, workDir?: string): Promise<{
75
82
  durationMs: number;
76
83
  hasAudio: boolean;
@@ -8,7 +8,7 @@ import { videoEvidenceTimestamps } from "../integrations/fastmoss.js";
8
8
  const ANALYSIS_TIMEOUT_MS = 10 * 60_000;
9
9
  const TRANSCRIPT_LANGUAGES = "en.*,es.*,zh.*,pt.*,fr.*,de.*,vi.*,th.*,id.*,ms.*,ja.*,ko.*";
10
10
  const LOCAL_ASR_MODEL = "onnx-community/whisper-base";
11
- const VIDEO_INTELLIGENCE_SCHEMA_VERSION = "commerce-video-intelligence-v6";
11
+ const VIDEO_INTELLIGENCE_SCHEMA_VERSION = "commerce-video-intelligence-v7";
12
12
  let localAsrPipeline;
13
13
  export function shouldUploadVideoArchive(input, publicUrl, alreadyUploaded = false) {
14
14
  return !alreadyUploaded && (input.kind !== "uploaded-mp4" || input.url !== publicUrl);
@@ -141,10 +141,11 @@ export function localVideoAnalysisPrompt(source, durationMs, transcript, attachm
141
141
  `3. segments 每段必须写 visual;若该段有人声,spokenText 必须概述口播;可见字幕写 onScreenText。画面、口播或字幕缺失就写 null。没有听觉音轨输入,audio 必须写 null,不得猜音乐、语气或音效。\n` +
142
142
  `4. 必须先判断整条视频的主带货形式 primary,只能选 factory-demo/comedy/mini-drama/ugc-testimonial/review-demo/tutorial/problem-solution/price-shock/unboxing/expert-explainer/lifestyle/live-cut/comparison/other。secondary 可选其他辅助形式。不能把 Hook 或实验假设冒充带货形式。\n` +
143
143
  `5. 0-3 秒至少按 500ms 证据判断;后续按镜头变化。所有机制、带货形式和“为什么可能爆”的假设都必须引用真实 startMs/endMs。榜单指标只是相关性,不是因果。\n` +
144
- `6. 目标是学习、融合、进化:只提炼可迁移机制,不复制原文案、人物、视觉资产或连续镜头顺序。\n` +
144
+ `6. 目标是一次反推出可直接改写的详细执行蓝图:保留时间线角色、第一帧关系、构图层级、镜头距离/运动、剪辑节奏、证明动作和口播功能;不复制原文案、人物身份、品牌、独特布景、音乐或其他必须替换的资产。\n` +
145
145
  `7. 必须单独输出 creativeMutationProfile,识别这条视频怎样把普通商品变成反常但现实可拍的完整画面事件。mutationAxes 是开放维度,不是固定模板轮换;从实际证据判断人物身份/关系、场景错位、规模/数量、群体反应、角色反转、构图视角、感官材质、当地行为/审美和真实商品证明,也允许发现新的维度。\n` +
146
146
  `8. culturalSignals 必须引用画面时间证据,说明当地行为、环境、幽默、地位符号、色彩/画面密度或消费场景怎样参与叙事,并写清迁移条件和避免刻板印象的方法。只出现当地字幕、旗帜或民族服装不等于理解了文化。\n\n` +
147
- `9. 必须从画面、字幕和口播中判断视频实际售卖的具体商品与具体品类,并输出 productDetection。只写证据支持的名称;无法确认就把 productTitle/category 写成 null,并在 ambiguity 说明原因。未知绝不能写成 ALL、global、全品类或通用。applicability 只表示结构适用范围:category-specific、cross-category-structure 或 unclear,不能代替商品品类。\n\n` +
147
+ `9. 必须从画面、字幕和口播中判断视频实际售卖的具体商品与具体品类,并输出 productDetection。只写证据支持的名称;无法确认就把 productTitle/category 写成 null,并在 ambiguity 说明原因。未知绝不能写成 ALL、global、全品类或通用。applicability 只表示结构适用范围:category-specific、cross-category-structure 或 unclear,不能代替商品品类。\n` +
148
+ `10. 若主形式是 factory-demo,把实际出现的 Hook、证据动作、视觉节奏、环境约束、商品露出和 CTA 分成独立 mechanisms;原视频没有 CTA 或某项机制时就保留缺失,绝不能为了凑齐类型而虚构。segments 的 visual/editing 必须详细到后续能按同样结构换商品执行,不能只写“展示产品”“工厂带货”等泛化标签。\n\n` +
148
149
  `样本:${JSON.stringify({
149
150
  sourceUrl: source.sourceUrl || source.source_url,
150
151
  platformVideoId: source.platformVideoId || source.platform_video_id,
@@ -243,7 +244,8 @@ async function captureArchivedVideoFrames(playerUrl, platformVideoId, workDir) {
243
244
  const media = await inspectVideoFile(videoFile, workDir);
244
245
  const durationMs = media.durationMs;
245
246
  const frames = [];
246
- for (const [index, timestampMs] of videoEvidenceTimestamps(durationMs).entries()) {
247
+ const timestamps = await sceneAwareVideoEvidenceTimestamps(videoFile, durationMs, workDir);
248
+ for (const [index, timestampMs] of timestamps.entries()) {
247
249
  const frameFile = path.join(workDir, `archive-frame-${String(index + 1).padStart(2, "0")}.jpg`);
248
250
  const result = await runProcess(process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg", [
249
251
  "-v", "error", "-ss", (timestampMs / 1_000).toFixed(3), "-i", videoFile,
@@ -259,6 +261,36 @@ async function captureArchivedVideoFrames(playerUrl, platformVideoId, workDir) {
259
261
  throw new Error("站内 MP4 没有生成可分析画面");
260
262
  return { videoId: safeId, playerUrl, durationMs, frames, videoFile, hasAudio: media.hasAudio };
261
263
  }
264
+ /**
265
+ * Keep the dense first three seconds, then add actual shot-change frames before
266
+ * uniform coverage. This follows shot-level video-understanding practice while
267
+ * staying inside the existing FFmpeg-only customer runtime.
268
+ */
269
+ export async function sceneAwareVideoEvidenceTimestamps(videoFile, durationMs, workDir = path.dirname(videoFile)) {
270
+ const baseline = videoEvidenceTimestamps(durationMs);
271
+ const outputTarget = process.platform === "win32" ? "NUL" : "/dev/null";
272
+ const detected = await runProcess(process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg", [
273
+ "-v", "error", "-i", videoFile, "-vf", "select='gt(scene,0.22)',metadata=print:file=-", "-an", "-f", "null", outputTarget,
274
+ ], { cwd: workDir, timeoutMs: 120_000, allowMissing: true });
275
+ const sceneTimestamps = detected.ok ? sceneChangeTimestampsFromMetadata(detected.stdout, durationMs) : [];
276
+ const early = baseline.filter((value) => value <= 3_000);
277
+ const laterBaseline = baseline.filter((value) => value > 3_000);
278
+ const values = new Set(early);
279
+ for (const value of sceneTimestamps.slice(0, 14)) {
280
+ values.add(value);
281
+ values.add(Math.min(Math.max(0, durationMs - 250), value + 250));
282
+ }
283
+ for (const value of laterBaseline)
284
+ values.add(value);
285
+ return [...values].filter((value) => value >= 0 && value <= Math.max(0, durationMs - 250)).sort((a, b) => a - b).slice(0, 28);
286
+ }
287
+ export function sceneChangeTimestampsFromMetadata(value, durationMs) {
288
+ const maximum = Math.max(0, Number(durationMs) - 250);
289
+ const values = [...String(value || "").matchAll(/pts_time:([0-9]+(?:\.[0-9]+)?)/g)]
290
+ .map((match) => Math.round(Number(match[1]) * 1_000))
291
+ .filter((timestamp) => Number.isFinite(timestamp) && timestamp > 0 && timestamp <= maximum);
292
+ return [...new Set(values)].sort((left, right) => left - right);
293
+ }
262
294
  async function extractTimedTranscript(sourceUrl, workDir) {
263
295
  const output = path.join(workDir, "%(id)s.%(ext)s");
264
296
  const args = [
@@ -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
- * 20 秒在移除中心可确定性补齐的重复视觉/质量字段后,先安全尝试每回合 2 条;
6
- * 任何结构或截断失败会降级到 1 条。30 秒输出仍更大,继续保持单条。
5
+ * 直接蓝图契约不再重复 creativePlan、executionBindings masterScript,
6
+ * 20/30 秒可先尝试更大的低开销子批,失败仍逐级缩小到单条。
7
7
  */
8
- export declare function flowCScriptChunkSizes(durationSeconds: 10 | 20 | 30): readonly [10, 5, 1] | readonly [2, 1] | readonly [1];
8
+ export declare function flowCScriptChunkSizes(durationSeconds: 10 | 20 | 30): readonly [10, 5, 1] | readonly [4, 2, 1] | readonly [2, 1];
9
9
  /** 将当前缺失 ordinal 切成受控子批;长视频绝不恢复 30 条大回合。 */
10
10
  export declare function flowCScriptChunks(durationSeconds: 10 | 20 | 30, ordinals: number[], size?: number): number[][];
@@ -2,14 +2,14 @@
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
- * 20 秒在移除中心可确定性补齐的重复视觉/质量字段后,先安全尝试每回合 2 条;
6
- * 任何结构或截断失败会降级到 1 条。30 秒输出仍更大,继续保持单条。
5
+ * 直接蓝图契约不再重复 creativePlan、executionBindings masterScript,
6
+ * 20/30 秒可先尝试更大的低开销子批,失败仍逐级缩小到单条。
7
7
  */
8
8
  export function flowCScriptChunkSizes(durationSeconds) {
9
9
  if (durationSeconds === 20)
10
- return [2, 1];
10
+ return [4, 2, 1];
11
11
  if (durationSeconds === 30)
12
- return [1];
12
+ return [2, 1];
13
13
  return FLOW_C_SCRIPT_CHUNK_SIZES;
14
14
  }
15
15
  /** 将当前缺失 ordinal 切成受控子批;长视频绝不恢复 30 条大回合。 */
@@ -60,6 +60,9 @@ type SelectedCandidate = {
60
60
  ordinal: number;
61
61
  productIndex: number;
62
62
  learnedTemplateId?: string | null;
63
+ executionBlueprint?: string;
64
+ templateMatch?: Record<string, unknown>;
65
+ retimingInstruction?: string;
63
66
  visualPremise: string;
64
67
  mutationAxes: string[];
65
68
  culturalAnchors: Array<Record<string, unknown>>;
@@ -87,6 +90,7 @@ type ScriptTask = {
87
90
  workflow: "flow-c";
88
91
  market: string;
89
92
  duration_seconds?: 10 | 20 | 30;
93
+ script_output_contract_version?: string;
90
94
  storyboard_layout_version?: StoryboardLayoutVersion;
91
95
  storyboardLayoutVersion?: StoryboardLayoutVersion;
92
96
  reference_style_card?: ReferenceStyleCard;
@@ -312,6 +316,41 @@ export declare function creativeReplanOrdinals(error: unknown): number[];
312
316
  export declare function recordCreativeReplanAttempts(attempts: Map<number, number>, ordinals: number[], limit?: number): void;
313
317
  export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[]): string;
314
318
  export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
319
+ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate[]): {
320
+ executionBlueprints: {
321
+ blueprintRef: string;
322
+ executionBlueprint: string;
323
+ }[];
324
+ selectedCandidates: {
325
+ blueprintRef: string;
326
+ ordinal: number;
327
+ 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;
352
+ }[];
353
+ };
315
354
  /**
316
355
  * 已选创意永远优先进入脚本扩写;只有当前没有可写脚本时才生成下一小波候选。
317
356
  * 每次最多占用受控 worker 数量的子批,避免把整批候选提前塞满 worker 队列。
@@ -243,6 +243,8 @@ export class WorkflowManager {
243
243
  let task = await this.scriptTask(id);
244
244
  if (Date.parse(task.expires_at) <= Date.now())
245
245
  throw new ExpiredCapabilityError("脚本交接已过期,请在网页重新点击交给本机 Codex");
246
+ if (task.script_output_contract_version !== "flow-c-template-direct-v2")
247
+ throw new Error("这是旧版候选/执行绑定脚本任务,已停止继续写入;请在网页放弃该任务并用新版直接蓝图重新创建");
246
248
  const workspace = ensureSiteWorkspace(this.config);
247
249
  const durationSeconds = Number(task.duration_seconds || 10);
248
250
  const chunkSizes = flowCScriptChunkSizes(durationSeconds);
@@ -637,21 +639,20 @@ export function scriptChunkPrompt(id, task, ordinals) {
637
639
  throw new Error("中心尚未为当前 ordinal 完成创意选题");
638
640
  const durationRules = duration === 10
639
641
  ? "每条只输出 openingState 和一个完整 0–10 秒 segment,不生成 masterScript。"
640
- : `每条先写完整 ${duration} masterScript,再严格展开成 ${duration / 10} 个各自 0–10 秒的 segments;不得写 10–20 或 20–30 全局时轴。`;
642
+ : `每条只输出 openingState ${duration / 10} 个各自 0–10 秒的 segments;Agent 会确定性合成 masterScript,不得重复输出总稿,也不得写 10–20 或 20–30 全局时轴。`;
641
643
  return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}。
642
644
  目标市场:${task.market}。商品事实:${compactJson(products, 12_000)}
643
- 中心已完成结构校验、安全检查、评分与批内去重;下面每个 ordinal selected candidate 是唯一创意权威,必须忠实展开,不要再生成、替换或重新评分创意:
644
- ${compactJson([...selected.values()], 16_000)}
645
+ 中心已按人工商品/类目标注完成模板匹配;下面每个 ordinal 通过 blueprintRef 引用一次反推后持久化的 executionBlueprint,是唯一结构权威。相同蓝图只传一次,直接把它换成当前商品执行,不要生成候选、重新选题或重新评分:
646
+ ${compactJson(selectedBlueprintPromptPayload([...selected.values()]), 36_000)}
645
647
  ${durationRules}
646
648
  写作要求:
647
649
  1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;不得扩大功效、稀释构图或换成普通模板。productProofAction 必须同时写清一个真实动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
648
- 1a. executionBindings 必须逐项指出 opening、proof、purchaseReason 和七项 visualExecution 实际落在哪个 segment/shot;executedAs 必须同时忠实对应 selected candidate 原要求和该 shot 的真实内容。opening 必须包含 segment 1 shot 1。它是可审计索引,不是新增剧情,也不能把未执行的要求写成已执行。
649
650
  2. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须是目标市场 ${task.market} 的自然原生语言、偏快但清晰;导演说明统一用简洁制作英文。
650
651
  3. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState;模型不要输出 continuity 或 continuityMode。
651
652
  4. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片、电视购物或伪造工厂/销量/来源。
652
653
  5. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
653
- 6. creativePlan 只写 formatvisualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation differentiationKey。callbackMotivation 必须自然回扣开场与已经展示的 purchaseReason;禁止编造折扣、库存、销量、价格或未提供的量化功效。Agent 会把选中卡的构图、材质、光线、文化、真实性边界和 fingerprint 确定性并回,中心会程序化计算质量门;模型不要重复这些字段。若 selected candidate 的 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾是最高权威;只补真实证明、局部时序和执行细节,不得改成另一个创意。
654
- 固定使用 GPT-5.6 Terra 高推理。storyboardLayoutVersion=${layoutVersion} 仅由中心管理。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
654
+ 6. 不要输出 creativePlan、executionBindings masterScript;这些字段由 Agent 从选中蓝图确定性回填/合成,避免重复 token 和脆弱的文字绑定校验。若 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾仍是最高权威。
655
+ 固定使用 GPT-5.6 Terra 中等推理。storyboardLayoutVersion=${layoutVersion} 仅由中心管理。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
655
656
  }
656
657
  export function creativeCandidatePrompt(id, task, ordinals) {
657
658
  const products = relevantProductInputs(task, ordinals);
@@ -708,6 +709,21 @@ function selectedCandidatesForOrdinals(task, ordinals) {
708
709
  const expected = new Set(ordinals);
709
710
  return new Map((task.selected_candidates || []).filter((candidate) => expected.has(Number(candidate.ordinal))).map((candidate) => [Number(candidate.ordinal), candidate]));
710
711
  }
712
+ export function selectedBlueprintPromptPayload(values) {
713
+ 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 };
721
+ });
722
+ return {
723
+ executionBlueprints: [...blueprints].map(([blueprintRef, executionBlueprint]) => ({ blueprintRef, executionBlueprint })),
724
+ selectedCandidates,
725
+ };
726
+ }
711
727
  function selectedCandidatePlan(value) {
712
728
  if (!value)
713
729
  throw new Error("中心缺少选中的创意候选");
@@ -722,6 +738,14 @@ function selectedCandidatePlan(value) {
722
738
  truthBoundary: value.truthBoundary,
723
739
  riskFlags: value.riskFlags,
724
740
  firstFrame: value.firstFrame,
741
+ visualHook: value.firstFrame,
742
+ conflict: value.visualPremise,
743
+ escalation: value.spectacleEscalation,
744
+ turn: value.spectacleEscalation,
745
+ productIntervention: value.productProofAction,
746
+ visibleProof: value.productProofAction,
747
+ callbackMotivation: value.purchaseReason,
748
+ differentiationKey: value.fingerprintKey,
725
749
  firstFrameFocus: value.firstFrameFocus,
726
750
  visualContrast: value.visualContrast,
727
751
  sensoryTexture: value.sensoryTexture,
@@ -1,5 +1,4 @@
1
1
  const text = { type: "string", minLength: 1 };
2
- const nullableText = { anyOf: [text, { type: "null" }] };
3
2
  const continuityFields = ["character", "wardrobe", "location", "lighting", "productState", "unfinishedAction", "nextGoal"];
4
3
  function object(properties, required = Object.keys(properties)) {
5
4
  return { type: "object", properties, required, additionalProperties: false };
@@ -22,39 +21,6 @@ function voiceProfileSchema() {
22
21
  emotionalBaseline: text,
23
22
  });
24
23
  }
25
- function creativePlanSchema() {
26
- const properties = {
27
- format: text,
28
- visualHook: text,
29
- conflict: text,
30
- escalation: nullableText,
31
- turn: nullableText,
32
- productIntervention: text,
33
- visibleProof: text,
34
- purchaseReason: text,
35
- callbackMotivation: text,
36
- differentiationKey: text,
37
- };
38
- return object(properties);
39
- }
40
- function executionBindingSchema() {
41
- return object({
42
- segmentIndex: { type: "integer", minimum: 1, maximum: 3 },
43
- shotIndexes: { type: "array", minItems: 1, maxItems: 8, items: { type: "integer", minimum: 1, maximum: 8 } },
44
- executedAs: text,
45
- });
46
- }
47
- function executionBindingsSchema() {
48
- return object({
49
- opening: executionBindingSchema(),
50
- proof: executionBindingSchema(),
51
- purchaseReason: executionBindingSchema(),
52
- visualExecution: object(Object.fromEntries([
53
- "firstFrameFocus", "visualContrast", "sensoryTexture", "motionPeak",
54
- "compositionLighting", "localNativeDetail", "antiFlatness",
55
- ].map((field) => [field, executionBindingSchema()]))),
56
- });
57
- }
58
24
  function segmentSchema() {
59
25
  return object({
60
26
  voiceCue: text,
@@ -82,15 +48,12 @@ export function flowCScriptOutputSchema(durationSeconds, count) {
82
48
  ordinal: { type: "integer", minimum: 1 },
83
49
  productIndex: { type: "integer", minimum: 0 },
84
50
  sellingFormId: text,
85
- creativePlan: creativePlanSchema(),
86
- executionBindings: executionBindingsSchema(),
87
51
  voiceProfile: voiceProfileSchema(),
88
52
  openingState: openingStateSchema(),
89
53
  };
90
54
  if (durationSeconds === 10)
91
55
  properties.segment = segmentSchema();
92
56
  else {
93
- properties.masterScript = { type: "string", minLength: 40 };
94
57
  properties.segments = { type: "array", minItems: durationSeconds / 10, maxItems: durationSeconds / 10, items: segmentSchema() };
95
58
  }
96
59
  const schema = object({ jobs: { type: "array", minItems: count, maxItems: count, items: object(properties) } });
@@ -178,7 +141,9 @@ function canonicalizeSegmentContinuity(jobValue) {
178
141
  return { ...segment, continuityMode: "continue", continuity };
179
142
  });
180
143
  const { openingState: _openingState, ...persistedJob } = job;
181
- return { ...persistedJob, script: job.masterScript || job.script, segmentVoiceovers: segments.map(segmentVoiceoverLines), segments: segments.map((segment, index) => withLegacySegmentScript(segment, index, segments.length, job.voiceProfile)) };
144
+ const renderedSegments = segments.map((segment, index) => withLegacySegmentScript(segment, index, segments.length, job.voiceProfile));
145
+ const masterScript = renderedSegments.map((segment) => String(recordOf(segment)?.script || "").trim()).filter(Boolean).join("\n\n");
146
+ return { ...persistedJob, script: masterScript, masterScript, segmentVoiceovers: segments.map(segmentVoiceoverLines), segments: renderedSegments };
182
147
  }
183
148
  function segmentVoiceoverLines(segmentValue) {
184
149
  const segment = recordOf(segmentValue);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.64",
3
+ "version": "0.4.66",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",