@xiaohhhh1/canvas-agent 0.4.52 → 0.4.54

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.
@@ -137,7 +137,9 @@ export function localVideoAnalysisPrompt(source, durationMs, transcript, attachm
137
137
  `3. segments 每段必须写 visual;若该段有人声,spokenText 必须概述口播;可见字幕写 onScreenText。画面、口播或字幕缺失就写 null。没有听觉音轨输入,audio 必须写 null,不得猜音乐、语气或音效。\n` +
138
138
  `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` +
139
139
  `5. 0-3 秒至少按 500ms 证据判断;后续按镜头变化。所有机制、带货形式和“为什么可能爆”的假设都必须引用真实 startMs/endMs。榜单指标只是相关性,不是因果。\n` +
140
- `6. 目标是学习、融合、进化:只提炼可迁移机制,不复制原文案、人物、视觉资产或连续镜头顺序。\n\n` +
140
+ `6. 目标是学习、融合、进化:只提炼可迁移机制,不复制原文案、人物、视觉资产或连续镜头顺序。\n` +
141
+ `7. 必须单独输出 creativeMutationProfile,识别这条视频怎样把普通商品变成反常但现实可拍的完整画面事件。mutationAxes 是开放维度,不是固定模板轮换;从实际证据判断人物身份/关系、场景错位、规模/数量、群体反应、角色反转、构图视角、感官材质、当地行为/审美和真实商品证明,也允许发现新的维度。\n` +
142
+ `8. culturalSignals 必须引用画面时间证据,说明当地行为、环境、幽默、地位符号、色彩/画面密度或消费场景怎样参与叙事,并写清迁移条件和避免刻板印象的方法。只出现当地字幕、旗帜或民族服装不等于理解了文化。\n\n` +
141
143
  `样本:${JSON.stringify({
142
144
  sourceUrl: source.sourceUrl || source.source_url,
143
145
  platformVideoId: source.platformVideoId || source.platform_video_id,
@@ -151,11 +153,12 @@ export function localVideoAnalysisPrompt(source, durationMs, transcript, attachm
151
153
  durationMs,
152
154
  })}\n\n` +
153
155
  `带时间码的口播字幕:\n${transcript}\n\n` +
154
- `只返回一个 JSON 对象,不要 Markdown。字段必须是:schemaVersion="commerce-video-intelligence-v2";durationMs;language;summary;` +
156
+ `只返回一个 JSON 对象,不要 Markdown。字段必须是:schemaVersion="commerce-video-intelligence-v3";durationMs;language;summary;` +
155
157
  `sellingFormat{primary,label,secondary,rationale,evidence[{startMs,endMs,observation}],confidence};` +
156
158
  `hook{startMs,endMs,visual,spokenText,onScreenText,patternInterrupt,openLoop};productFirstSeenMs;` +
157
159
  `segments[{startMs,endMs,role,visual,spokenText,onScreenText,audio,editing,confidence}](至少2段);` +
158
- `mechanisms[{type,label,mechanism,whyItMayWork,evidence[{startMs,endMs,observation}],confidence,replicationRisk}](type 仅 hook/conflict/proof/pacing/trust/product-reveal/offer-framing/cta/audio);` +
160
+ `creativeMutationProfile{coreVisualPremise,mutationAxes[至少2项],culturalSignals[{market,signal,narrativeRole,aestheticRole,transferConditions,avoidStereotype,evidence[{startMs,endMs,observation}]}],firstFrameComposition,spectacleMechanism,productIntegration,transferableRule,culturalLimitations,doNotCopy,evidence[{startMs,endMs,observation}],confidence};` +
161
+ `mechanisms[{type,label,mechanism,whyItMayWork,evidence[{startMs,endMs,observation}],confidence,replicationRisk}](type 仅 hook/conflict/proof/pacing/trust/product-reveal/offer-framing/cta/audio/visual-grammar/environment/visual-mutation/culture-code/spectacle);` +
159
162
  `viralHypotheses[{hypothesis,supportingMetrics,supportingEvidence[{startMs,endMs,observation}],confounders,confidence}];` +
160
163
  `nonReplicableFactors;complianceRisks;originalityGuidance{preserveMechanisms,mustRewrite,forbiddenCopying}。`;
161
164
  }
@@ -0,0 +1,5 @@
1
+ type JsonSchema = Record<string, unknown>;
2
+ export declare const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = "flow-c-creative-candidates-v2";
3
+ export declare function flowCCreativeCandidateOutputSchema(count: number): JsonSchema;
4
+ export declare function parseFlowCCreativeCandidateOutput(value: string, expectedOrdinals: number[]): Record<string, unknown>[];
5
+ export {};
@@ -0,0 +1,72 @@
1
+ import { assertStrictResponseSchema } from './script-output.js';
2
+ export const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = 'flow-c-creative-candidates-v2';
3
+ const text = { type: 'string', minLength: 1 };
4
+ function object(properties) {
5
+ return { type: 'object', properties, required: Object.keys(properties), additionalProperties: false };
6
+ }
7
+ function candidateSchema() {
8
+ return object({
9
+ visualPremise: text,
10
+ mutationAxes: { type: 'array', minItems: 2, maxItems: 4, items: text },
11
+ culturalAnchors: {
12
+ type: 'array', minItems: 0, maxItems: 3,
13
+ items: object({ signal: text, narrativeRole: text, aestheticRole: text, source: { type: 'string', enum: ['learned-style-card', 'product-market-evidence', 'conservative-everyday-detail'] }, evidence: text, avoidStereotype: text }),
14
+ },
15
+ spectacleEscalation: text,
16
+ productProofAction: text,
17
+ truthBoundary: text,
18
+ riskFlags: { type: 'array', minItems: 0, maxItems: 8, items: text },
19
+ firstFrame: text,
20
+ firstFrameFocus: text,
21
+ visualContrast: text,
22
+ sensoryTexture: text,
23
+ motionPeak: text,
24
+ compositionLighting: text,
25
+ localNativeDetail: text,
26
+ antiFlatness: text,
27
+ creativeFingerprint: object({
28
+ characterRelation: text,
29
+ scene: text,
30
+ firstFrameComposition: text,
31
+ spectacleMechanism: text,
32
+ proofMethod: text,
33
+ }),
34
+ });
35
+ }
36
+ export function flowCCreativeCandidateOutputSchema(count) {
37
+ const schema = object({
38
+ contractVersion: { type: 'string', enum: [FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION] },
39
+ groups: {
40
+ type: 'array', minItems: count, maxItems: count,
41
+ items: object({
42
+ ordinal: { type: 'integer', minimum: 1 },
43
+ productIndex: { type: 'integer', minimum: 0 },
44
+ candidates: { type: 'array', minItems: 6, maxItems: 10, items: candidateSchema() },
45
+ }),
46
+ },
47
+ });
48
+ assertStrictResponseSchema(schema);
49
+ return schema;
50
+ }
51
+ export function parseFlowCCreativeCandidateOutput(value, expectedOrdinals) {
52
+ const source = String(value || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
53
+ const parsed = JSON.parse(source);
54
+ if (parsed.contractVersion !== FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION)
55
+ throw new Error('Creative candidate contract version does not match the current Agent');
56
+ if (!Array.isArray(parsed.groups))
57
+ throw new Error('Codex did not return creative candidate groups');
58
+ const expected = new Set(expectedOrdinals);
59
+ const accepted = new Map();
60
+ for (const value of parsed.groups) {
61
+ if (!value || typeof value !== 'object' || Array.isArray(value))
62
+ continue;
63
+ const group = value;
64
+ const ordinal = Number(group.ordinal);
65
+ if (!expected.has(ordinal) || accepted.has(ordinal) || !Array.isArray(group.candidates) || group.candidates.length < 6 || group.candidates.length > 10)
66
+ continue;
67
+ accepted.set(ordinal, group);
68
+ }
69
+ if (accepted.size !== expectedOrdinals.length)
70
+ throw new Error('Creative candidate ordinals or counts do not match the current chunk');
71
+ return expectedOrdinals.map((ordinal) => accepted.get(ordinal));
72
+ }
@@ -20,17 +20,58 @@ type ScriptRecord = {
20
20
  updatedAt: string;
21
21
  };
22
22
  type ReferenceStyleCard = {
23
- openingComposition?: unknown;
24
- visualDensity?: unknown;
25
- conflictContrast?: unknown;
26
- characterProductEntrance?: unknown;
27
- rhythm?: unknown;
28
- turn?: unknown;
29
- proofMethod?: unknown;
23
+ id?: unknown;
24
+ visualPremisePattern?: unknown;
25
+ mutationAxes?: unknown;
26
+ culturePattern?: unknown;
27
+ spectaclePattern?: unknown;
28
+ productIntegrationPattern?: unknown;
29
+ openingPattern?: unknown;
30
+ proofPattern?: unknown;
31
+ rhythmPattern?: unknown;
32
+ capturePattern?: unknown;
30
33
  voiceTone?: unknown;
31
- categoryFit?: unknown;
34
+ sourceEvidenceTier?: unknown;
35
+ truthBoundaries?: unknown;
36
+ applicability?: {
37
+ markets?: unknown;
38
+ categories?: unknown;
39
+ };
32
40
  };
33
41
  type StoryboardLayoutVersion = "director-table-scripted-v1" | "hybrid-three-anchor-v1" | "legacy-five-row-v1";
42
+ type ProductInput = {
43
+ productIndex: number;
44
+ title: string;
45
+ category?: string;
46
+ quantity: number;
47
+ sellingForm?: string;
48
+ creativeBrief?: string;
49
+ productImageUrlsInExactOrder?: string[];
50
+ };
51
+ type SelectedCandidate = {
52
+ ordinal: number;
53
+ productIndex: number;
54
+ visualPremise: string;
55
+ mutationAxes: string[];
56
+ culturalAnchors: Array<Record<string, unknown>>;
57
+ spectacleEscalation: string;
58
+ productProofAction: string;
59
+ truthBoundary: string;
60
+ riskFlags: string[];
61
+ firstFrame: string;
62
+ firstFrameFocus: string;
63
+ visualContrast: string;
64
+ sensoryTexture: string;
65
+ motionPeak: string;
66
+ compositionLighting: string;
67
+ localNativeDetail: string;
68
+ antiFlatness: string;
69
+ creativeFingerprint: Record<string, string>;
70
+ fingerprintKey?: string;
71
+ selectionScore?: number;
72
+ selectionBreakdown?: Record<string, number>;
73
+ selectionMode?: string;
74
+ };
34
75
  type ScriptTask = {
35
76
  id: string;
36
77
  workflow: "flow-c";
@@ -40,6 +81,14 @@ type ScriptTask = {
40
81
  storyboardLayoutVersion?: StoryboardLayoutVersion;
41
82
  reference_style_card?: ReferenceStyleCard;
42
83
  referenceStyleCard?: ReferenceStyleCard;
84
+ product_inputs?: ProductInput[];
85
+ reference_style_cards?: ReferenceStyleCard[];
86
+ selected_candidates?: SelectedCandidate[];
87
+ creative_fingerprint_ledger?: Array<Record<string, unknown>>;
88
+ candidate_stage?: {
89
+ selected?: number;
90
+ requested?: number;
91
+ };
43
92
  requested_count: number;
44
93
  product_quantities: number[];
45
94
  instructions: string;
@@ -112,6 +161,14 @@ export declare class WorkflowManager {
112
161
  } & {
113
162
  error?: string;
114
163
  }>;
164
+ submitCandidateChunk(idValue: unknown, groupsValue: unknown): Promise<{
165
+ accepted: number;
166
+ selected: number;
167
+ requestedCount: number;
168
+ selectedCandidates: SelectedCandidate[];
169
+ } & {
170
+ error?: string;
171
+ }>;
115
172
  downloadState(): {
116
173
  configured: boolean;
117
174
  directoryName: string | undefined;
@@ -213,6 +270,8 @@ export declare class WorkflowManager {
213
270
  private pumpScriptQueue;
214
271
  private finishDownloadDirectorySelection;
215
272
  private runScript;
273
+ private ensureCreativeCandidateSelections;
274
+ private runCandidateChunk;
216
275
  /** 一个 chunk 使用独立线程;解析与中心持久化失败不会影响并行 lane。 */
217
276
  private runScriptChunk;
218
277
  /** 只记录阶段、ordinal 和毫秒数;禁止记录 prompt、令牌或中心能力。 */
@@ -236,5 +295,6 @@ export declare function terminalScriptChunkError(results: Array<{
236
295
  terminal?: boolean;
237
296
  }>): string;
238
297
  export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[]): string;
298
+ export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
239
299
  export declare function missingOrdinals(total: number, received: number[]): number[];
240
300
  export {};
@@ -10,6 +10,7 @@ import { CONFIG_DIR, ensureSiteWorkspace } from "../config.js";
10
10
  import { logger } from "../utils/logger.js";
11
11
  import { windowsPowerShellExecutable } from "../utils/windows.js";
12
12
  import { FLOW_C_SCRIPT_CHUNK_MAX, flowCScriptChunks, flowCScriptChunkSizes } from "./constants.js";
13
+ import { FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION, flowCCreativeCandidateOutputSchema, parseFlowCCreativeCandidateOutput } from "./creative-candidates.js";
13
14
  import { flowCScriptOutputSchema, parseFlowCScriptOutput } from "./script-output.js";
14
15
  const STATE_FILE = path.join(CONFIG_DIR, "workflow-state.json");
15
16
  export const FLOW_C_CODEX_TURN_TIMEOUT_MS = 8 * 60 * 1000;
@@ -112,6 +113,12 @@ export class WorkflowManager {
112
113
  this.save();
113
114
  return data;
114
115
  }
116
+ async submitCandidateChunk(idValue, groupsValue) {
117
+ const record = this.scriptRecord(workflowId(idValue, "脚本交接 ID"));
118
+ if (!Array.isArray(groupsValue) || !groupsValue.length || groupsValue.length > 10)
119
+ throw new Error("每个候选子批必须包含 1–10 个 ordinal 组");
120
+ return commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/candidate-chunks`, record.accessToken, "x-workflow-handoff-token", { method: "POST", body: JSON.stringify({ contractVersion: FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION, groups: groupsValue }) });
121
+ }
115
122
  downloadState() {
116
123
  return {
117
124
  configured: Boolean(this.state.downloadDirectory),
@@ -233,10 +240,11 @@ export class WorkflowManager {
233
240
  record.message = "正在读取完整产品清单";
234
241
  record.updatedAt = now();
235
242
  this.save();
236
- const task = await this.scriptTask(id);
243
+ let task = await this.scriptTask(id);
237
244
  if (Date.parse(task.expires_at) <= Date.now())
238
245
  throw new ExpiredCapabilityError("脚本交接已过期,请在网页重新点击交给本机 Codex");
239
246
  const workspace = ensureSiteWorkspace(this.config);
247
+ task = await this.ensureCreativeCandidateSelections(id, task, workspace.workspacePath);
240
248
  const durationSeconds = Number(task.duration_seconds || 10);
241
249
  const chunkSizes = flowCScriptChunkSizes(durationSeconds);
242
250
  // Fail locally before starting any worker if a future schema edit
@@ -256,7 +264,7 @@ export class WorkflowManager {
256
264
  record.updatedAt = now();
257
265
  this.save();
258
266
  const results = await Promise.all(chunks.map((ordinals) => this.runScriptChunk(id, task, ordinals, workspace.workspacePath)));
259
- await this.scriptTask(id);
267
+ task = await this.scriptTask(id);
260
268
  const terminalError = terminalScriptChunkError(results);
261
269
  if (terminalError) {
262
270
  throw new Error(`本机脚本结构化契约被 Codex 拒绝,已停止自动重试且未提交缺失脚本。请先升级或修复 Canvas Agent,再手动重试(${terminalError})`);
@@ -296,6 +304,60 @@ export class WorkflowManager {
296
304
  this.runningScripts.delete(id);
297
305
  }
298
306
  }
307
+ async ensureCreativeCandidateSelections(id, initialTask, cwd) {
308
+ let task = initialTask;
309
+ let chunkSize = 2;
310
+ while (selectedCandidateOrdinals(task).length < task.requested_count) {
311
+ const before = selectedCandidateOrdinals(task).length;
312
+ const missing = missingOrdinals(task.requested_count, selectedCandidateOrdinals(task));
313
+ const chunks = chunkNumbers(missing, chunkSize);
314
+ const record = this.scriptRecord(id);
315
+ record.message = `本机 Codex 正在生成可审核创意候选,中心将自动选题(${before}/${task.requested_count})`;
316
+ record.activeChunks = 0;
317
+ record.updatedAt = now();
318
+ this.save();
319
+ const results = await Promise.all(chunks.map((ordinals) => this.runCandidateChunk(id, task, ordinals, cwd)));
320
+ task = await this.scriptTask(id);
321
+ const terminalError = terminalScriptChunkError(results);
322
+ if (terminalError)
323
+ throw new Error(`创意候选结构化契约被 Codex 拒绝,已停止自动重试(${terminalError})`);
324
+ if (selectedCandidateOrdinals(task).length > before) {
325
+ chunkSize = 2;
326
+ continue;
327
+ }
328
+ if (chunkSize > 1) {
329
+ chunkSize = 1;
330
+ continue;
331
+ }
332
+ const lastError = results.map((result) => result.error).filter(Boolean).at(-1) || "候选生成或中心选题未返回结果";
333
+ throw new Error(`创意候选阶段已隔离到单条仍失败(${lastError}),请点击重试`);
334
+ }
335
+ return task;
336
+ }
337
+ async runCandidateChunk(id, task, ordinals, cwd) {
338
+ const result = await runCodexWorkflowTurn(creativeCandidatePrompt(id, task, ordinals), this.emit, {
339
+ cwd,
340
+ permissionMode: "full",
341
+ timeoutMs: FLOW_C_CODEX_TURN_TIMEOUT_MS,
342
+ outputSchema: flowCCreativeCandidateOutputSchema(ordinals.length),
343
+ onThread: (threadId) => { this.scriptRecord(id).threadId = threadId; this.save(); },
344
+ onWorkerStart: () => { const record = this.scriptRecord(id); record.activeChunks = Number(record.activeChunks || 0) + 1; record.updatedAt = now(); this.save(); },
345
+ onWorkerFinish: () => { const record = this.scriptRecord(id); record.activeChunks = Math.max(0, Number(record.activeChunks || 0) - 1); record.updatedAt = now(); this.save(); },
346
+ });
347
+ this.emitScriptStage(id, ordinals, "candidate_model", result.timings.queueWaitMs + result.timings.threadStartMs + result.timings.modelMs);
348
+ if (!result.ok || !result.text)
349
+ return { error: result.ok ? "Codex 未返回创意候选" : result.error, terminal: !result.ok && !result.retryable };
350
+ try {
351
+ const groups = parseFlowCCreativeCandidateOutput(result.text, ordinals);
352
+ const startedAt = Date.now();
353
+ await this.submitCandidateChunk(id, groups);
354
+ this.emitScriptStage(id, ordinals, "candidate_select", Date.now() - startedAt);
355
+ return { terminal: false };
356
+ }
357
+ catch (error) {
358
+ return { error: error instanceof Error ? `候选未通过中心结构/安全/去重校验:${error.message}` : "候选未通过中心校验", terminal: false };
359
+ }
360
+ }
299
361
  /** 一个 chunk 使用独立线程;解析与中心持久化失败不会影响并行 lane。 */
300
362
  async runScriptChunk(id, task, ordinals, cwd) {
301
363
  const durationSeconds = Number(task.duration_seconds || 10);
@@ -315,7 +377,11 @@ export class WorkflowManager {
315
377
  return { error: result.ok ? "Codex 未返回可用的结构化脚本" : result.error, terminal: !result.ok && !result.retryable };
316
378
  try {
317
379
  const parseStartedAt = Date.now();
318
- const jobs = parseFlowCScriptOutput(result.text, ordinals);
380
+ const selected = selectedCandidatesForOrdinals(task, ordinals);
381
+ const jobs = parseFlowCScriptOutput(result.text, ordinals).map((job) => ({
382
+ ...job,
383
+ creativePlan: { ...(job.creativePlan || {}), ...selectedCandidatePlan(selected.get(job.ordinal)) },
384
+ }));
319
385
  this.emitScriptStage(id, ordinals, "parse", Date.now() - parseStartedAt);
320
386
  const persistStartedAt = Date.now();
321
387
  await this.submitGeneratedScriptJobs(id, jobs);
@@ -504,39 +570,114 @@ class ExpiredCapabilityError extends Error {
504
570
  export function scriptChunkPrompt(id, task, ordinals) {
505
571
  const duration = Number(task.duration_seconds || 10);
506
572
  const layoutVersion = task.storyboard_layout_version || task.storyboardLayoutVersion || "director-table-scripted-v1";
507
- const styleCard = compactReferenceStyleCard(task.reference_style_card || task.referenceStyleCard);
508
- const durationRules = duration === 10 ? `
509
- 本任务每条成片为 10 秒,不生成 masterScript。每条只输出一份 openingState 和一个完整 0–10 秒 segment;Agent 会从 structured shots 确定性渲染兼容顶层 script,并按 shots 顺序派生 segmentVoiceovers,模型不要生成这两个派生字段。` : `
510
- 本任务每条成片为 ${duration} 秒。先创作一份完整连贯的 masterScript,再严格展开成 ${duration / 10} 个独立的 0–10 秒 segments;绝不能写 10–20、20–30 秒,也不能让局部段携带总片时轴。
511
- 每条只需回传一份 masterScript、openingState 和完整 segments;不要输出重复的顶层 script 或 segmentVoiceovers。Agent 会从 masterScript 补顶层兼容 script、按 shots 派生 segmentVoiceovers,并把 openingState 变成首段起点、用前一段 endingState 确定性补齐后一段 continuity。`;
512
- return `你正在后台处理“抖音小辉跨境工具”Flow C 脚本交接 ${id}
513
- 完整中心任务已经附在本提示词末尾。只创作 ordinal ${ordinals[0]} ${ordinals.at(-1)}(精确列表:${ordinals.join(", ")})。
514
- 本段 productIndex 必须严格按此映射填写:${scriptProductAssignments(task.product_quantities, ordinals)}。不得凭产品名称猜测或把相邻产品编号混用。
515
- 脚本质量绝不能因批量而降低:每条都必须独立构思、完整、真实合规,严格遵守任务 instructions、产品图片顺序和 productIndex;使用目标市场 ${task.market} 的自然本地口播与偏快但清晰的短视频节奏。${durationRules}
516
- 每条先按“画面先行”写 creativePlan,再写本段/总脚本。creativePlan 必须给出 visualHook、conflict、productIntervention、visibleProof、callbackMotivation、truthBoundary、differentiationKey,并在 escalation 或 turn 中至少给出一项真实成立的升级/转折;format 必须依据当前产品、市场、事实证据和真实场景适配选择。另必须完成视觉磁力设计:firstFrameFocus(第一眼唯一注意物)、visualContrast(明暗/冷暖/大小/前后状态反差)、sensoryTexture(当前品类真正可见或可听的材质感官证据)、motionPeak(最有张力的动作瞬间)、compositionLighting(景别、层次、主体分离和光线方向)、localNativeDetail(目标市场原生环境/动作/拍摄习惯)、antiFlatness(明确禁止的平淡摆拍方式)。食品应优先真实热气、汁水、断面、酥脆、拉丝、挤压回弹等可验证食欲线索;工厂应优先规模、同步运动、材料变化、机械接触和成品揭示;其它品类按真实材质与使用动作动态选择,绝不能套用不相关感官词。中国短剧式冲突/喜剧只能是适合时的实验路线,绝不是默认;工厂、仓库、超市等条件场景也只有在商品动作自然且不伪造来源、库存或销量证据时才可选,不能固定轮换。
517
- styleCardSummary 只使用已缓存的压缩风格卡,没有就明确写“无”;不要分析、读取或转录原始参考视频。每条只生成一份 voiceProfile(性别、年龄感、音高、音质、语速、口音、停顿习惯、情绪基调),所有局部段共同引用它;每段仅用简短 voiceCue 表达情绪变化,不复写整套音色。
518
- 每个 segment 必须有完整 0–10 秒 shots、voiceCue 和 endingState;不要让模型生成 continuityMode 或 continuity。镜头数由本条剧情实际需要决定(契约允许 1–8 个),不得为了故事板版式强行固定为三个或五个。每个 shot 都包含画面、准确本地口播、可控屏幕字、商品证据、soundBgm 与 emotionalNote;shot.visual 必须把本镜头对应的主体位置/景别、光线与颜色分离、材质细节、动作峰值和当地原生环境写成可直接画出的指令,并加入适合该场景的真实捕捉痕迹(例如自然肤色与毛孔、细发、真实布料褶皱/起毛、生活使用痕迹、混合实景光或轻微传感器颗粒),不能只写“展示商品”“人物试用”或“工厂生产”。不得把真实感写成脏污、损坏商品或刻意低清。qualityGate 仍只核对脚本事实与创意完整性,不新增“普通画面自动打回”或额外生成循环。
519
- 【回传语言契约】为了让导演故事板稳定可读,creativePlan、masterScript 中的导演/剧情说明、voiceProfile、qualityGate、openingState、voiceCue、endingState,以及每个 shot 的 visual、evidence、soundBgm、emotionalNote 必须用清晰简洁的制作英文。每个 shot.voiceover 必须逐字使用目标市场 ${task.market} 的自然当地语言,不得翻译成英文;onScreenText 若画面确实需要展示文字,则写目标市场当地语言的准确短文案,否则写英文 NONE。不得在英文导演字段里混入另一份口播或全局时轴。
520
- 每条 differentiationKey 必须在带货形式、第一眼画面、剧情骨架、痛点/反差、商品证明和口播气质上与本批其他条目显著不同,不能只换措辞。
521
- 本脚本回合固定使用 GPT-5.6 Terra 高推理;不要建议换低档模型或降低推理。故事版布局由中心顶层 storyboardLayoutVersion=${layoutVersion} 管理,不要让模型选择,也不要把它重复进每条 job。
522
- 【已缓存压缩的参考 style card】${styleCard || "无参考风格卡"}【style card 结束】
523
- 每一个 ordinal 都必须从头到尾创作一份完整、独立的原生带货脚本,画面和口播必须在同一份脚本中一起独立构思。先让它成为一个当地真人在具体生活情境里自然发现、吐槽、试用或验证商品的短内容,再完成带货;禁止写成品牌广告片、棚拍宣传片、电视购物、逐条念卖点或全程完美对镜讲解。独立脚本天然包含独立口播:不得把任何一份口播当作整批公共模板,不得复用完整台词,也不得只换人物、场景或少数词后保留近似口播。音色身份可以为同一人物保持一致,但每条的 Hook、人物行动、证明表达和 CTA 都必须重新写。
524
- 用户没有指定带货方向时,按当前商品可见卖点、自然使用环境、目标市场表达、事实边界和批内差异动态选择;有条件证据门的形式只有中心任务已提供相应真实证据时才能采用。
525
- 只返回符合当前结构化输出契约的 jobs;每条都包含 ordinal、productIndex、sellingFormId、creativePlan、voiceProfile、qualityGate、openingState 和结构化局部段。10 秒只返回一个 segment;20/30 秒另含 masterScript 与完整 segments。兼容 script、continuity 和 segmentVoiceovers 全由 Agent 确定性补齐。无需调用 MCP,Agent 会在本机校验后自动回传。不要创建付费批次,不要调用供应商模型。
526
-
527
- 【中心任务完整 instructions】
528
- ${task.instructions}
529
- 【中心任务 instructions 结束】`;
573
+ const products = relevantProductInputs(task, ordinals);
574
+ const selected = selectedCandidatesForOrdinals(task, ordinals);
575
+ if (selected.size !== ordinals.length)
576
+ throw new Error("中心尚未为当前 ordinal 完成创意选题");
577
+ const durationRules = duration === 10
578
+ ? "每条只输出 openingState 和一个完整 0–10 秒 segment,不生成 masterScript"
579
+ : `每条先写完整 ${duration} masterScript,再严格展开成 ${duration / 10} 个各自 0–10 秒的 segments;不得写 10–20 或 20–30 全局时轴。`;
580
+ return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}
581
+ 目标市场:${task.market}。商品事实:${compactJson(products, 12_000)}
582
+ 中心已完成结构校验、安全检查、评分与批内去重;下面每个 ordinal selected candidate 是唯一创意权威,必须忠实展开,不要再生成、替换或重新评分创意:
583
+ ${compactJson([...selected.values()], 16_000)}
584
+ ${durationRules}
585
+ 写作要求:
586
+ 1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、truthBoundary 以及七项视觉执行字段;不得扩大功效、稀释构图或换成普通模板。
587
+ 2. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须是目标市场 ${task.market} 的自然原生语言、偏快但清晰;导演说明统一用简洁制作英文。
588
+ 3. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState;模型不要输出 continuity 或 continuityMode。
589
+ 4. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片、电视购物或伪造工厂/销量/来源。
590
+ 5. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
591
+ 6. creativePlan 只补充脚本执行字段;Agent 会把选中卡的结构化裂变、文化、安全和 fingerprint 字段确定性并回,模型不要重复这些字段。
592
+ 固定使用 GPT-5.6 Terra 高推理。storyboardLayoutVersion=${layoutVersion} 仅由中心管理。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
593
+ }
594
+ export function creativeCandidatePrompt(id, task, ordinals) {
595
+ const products = relevantProductInputs(task, ordinals);
596
+ const styleCards = relevantStyleCards(task.reference_style_cards || [], products).map(compactReferenceStyleCard).filter(Boolean).slice(0, 6);
597
+ const ledger = (task.creative_fingerprint_ledger || []).slice(-60);
598
+ 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)}.
599
+ Market: ${task.market}. Product facts: ${compactJson(products, 12_000)}
600
+ Optional learned style abstractions: ${compactJson(styleCards, 5_000)}
601
+ Already accepted fingerprint ledger: ${compactJson(ledger, 8_000)}
602
+ 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.
603
+ Every card must be surprising but physically filmable and product-relevant. productProofAction 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.
604
+ Every card must also contain a concrete visual execution card, not mood adjectives: firstFrameFocus names one dominant subject and its approximate 35%-85% frame share; visualContrast defines one readable scale/color/light/relationship contrast; sensoryTexture names truthful category-specific material evidence; motionPeak names the single peak physical instant; compositionLighting specifies foreground/midground/background, practical light direction and one plausible exposure/optical imperfection; localNativeDetail names observable local behavior/environment/aesthetic density with the same evidence discipline as culturalAnchors; antiFlatness names the exact generic catalogue treatment this concept must avoid. The firstFrame must pass a screenshot test: even without dialogue, a still image must show the unusual relationship, product relevance and depth hierarchy.
605
+ Cultural anchors are optional (0–3). source must be learned-style-card, product-market-evidence, or conservative-everyday-detail. A learned-style-card anchor must cite the supplied card id in evidence. A product-market-evidence anchor must cite a concrete supplied product/market fact; if none exists, use conservative-everyday-detail and explicitly say it is ordinary context, not a custom claim. Never use flags, national costumes, royalty/public figures, religion, stereotypes or cultural mockery as shortcut hooks. Locality belongs in consumption context, natural interaction, language rhythm and aesthetic density.
606
+ creativeFingerprint must use five concise semantic labels: characterRelation, scene, firstFrameComposition, spectacleMechanism and proofMethod. Do not hide duplicate ideas behind paraphrases. Do not copy people, clothing, sets, dialogue or shot sequences from references. Return only the candidate schema; do not write scripts or call tools.`;
530
607
  }
531
608
  function compactReferenceStyleCard(value) {
532
609
  if (!value || typeof value !== "object")
533
610
  return "";
534
- const fields = ["openingComposition", "visualDensity", "conflictContrast", "characterProductEntrance", "rhythm", "turn", "proofMethod", "voiceTone", "categoryFit"];
611
+ const fields = ["id", "visualPremisePattern", "mutationAxes", "culturePattern", "spectaclePattern", "productIntegrationPattern", "openingPattern", "proofPattern", "rhythmPattern", "capturePattern", "voiceTone", "sourceEvidenceTier"];
535
612
  const compact = Object.fromEntries(fields.flatMap((field) => {
536
613
  const text = String(value[field] || "").trim().replace(/\s+/g, " ").slice(0, 400);
537
614
  return text ? [[field, text]] : [];
538
615
  }));
539
- return Object.keys(compact).length ? JSON.stringify(compact) : "";
616
+ const categories = Array.isArray(value.applicability?.categories) ? value.applicability.categories.map((item) => String(item || "").trim()).filter(Boolean).slice(0, 8) : [];
617
+ const markets = Array.isArray(value.applicability?.markets) ? value.applicability.markets.map((item) => String(item || "").trim()).filter(Boolean).slice(0, 8) : [];
618
+ if (categories.length || markets.length)
619
+ Object.assign(compact, { applicability: { categories, markets } });
620
+ return Object.keys(compact).length ? compact : null;
621
+ }
622
+ function relevantStyleCards(cards, products) {
623
+ const categories = new Set(products.map((product) => String(product.category || "").trim().toLowerCase()).filter(Boolean));
624
+ if (!categories.size)
625
+ return cards.slice(0, 3);
626
+ const matched = cards.filter((card) => {
627
+ const allowed = Array.isArray(card.applicability?.categories) ? card.applicability.categories.map((item) => String(item || "").trim().toLowerCase()).filter(Boolean) : [];
628
+ return !allowed.length || allowed.includes("all") || allowed.includes("global") || allowed.some((item) => categories.has(item));
629
+ });
630
+ return matched.length ? matched : [];
631
+ }
632
+ function relevantProductInputs(task, ordinals) {
633
+ const indexes = new Set(ordinals.map((ordinal) => productIndexForOrdinal(task.product_quantities, ordinal)));
634
+ const products = (task.product_inputs || []).filter((product) => indexes.has(Number(product.productIndex)));
635
+ if (products.length)
636
+ return products;
637
+ return [{ productIndex: -1, title: "Server compatibility fallback", quantity: ordinals.length, creativeBrief: String(task.instructions || "").slice(0, 4_000) }];
638
+ }
639
+ function selectedCandidateOrdinals(task) {
640
+ return [...new Set((task.selected_candidates || []).map((candidate) => Number(candidate.ordinal)).filter(Number.isInteger))].sort((left, right) => left - right);
641
+ }
642
+ function selectedCandidatesForOrdinals(task, ordinals) {
643
+ const expected = new Set(ordinals);
644
+ return new Map((task.selected_candidates || []).filter((candidate) => expected.has(Number(candidate.ordinal))).map((candidate) => [Number(candidate.ordinal), candidate]));
645
+ }
646
+ function selectedCandidatePlan(value) {
647
+ if (!value)
648
+ throw new Error("中心缺少选中的创意候选");
649
+ return {
650
+ visualPremise: value.visualPremise,
651
+ mutationAxes: value.mutationAxes,
652
+ culturalAnchors: value.culturalAnchors,
653
+ spectacleEscalation: value.spectacleEscalation,
654
+ productProofAction: value.productProofAction,
655
+ truthBoundary: value.truthBoundary,
656
+ riskFlags: value.riskFlags,
657
+ firstFrame: value.firstFrame,
658
+ firstFrameFocus: value.firstFrameFocus,
659
+ visualContrast: value.visualContrast,
660
+ sensoryTexture: value.sensoryTexture,
661
+ motionPeak: value.motionPeak,
662
+ compositionLighting: value.compositionLighting,
663
+ localNativeDetail: value.localNativeDetail,
664
+ antiFlatness: value.antiFlatness,
665
+ creativeFingerprint: value.creativeFingerprint,
666
+ fingerprintKey: value.fingerprintKey,
667
+ selectionScore: value.selectionScore,
668
+ selectionBreakdown: value.selectionBreakdown,
669
+ selectionMode: value.selectionMode,
670
+ visualExecutionVersion: "flow-c-visual-execution-v1",
671
+ };
672
+ }
673
+ function chunkNumbers(values, size) {
674
+ const chunks = [];
675
+ for (let index = 0; index < values.length; index += size)
676
+ chunks.push(values.slice(index, index + size));
677
+ return chunks;
678
+ }
679
+ function compactJson(value, limit) {
680
+ return JSON.stringify(value).slice(0, limit);
540
681
  }
541
682
  function scriptProductAssignments(productQuantities, ordinals) {
542
683
  const assignments = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.52",
3
+ "version": "0.4.54",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",