@xiaohhhh1/canvas-agent 0.4.1 → 0.4.3

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.
@@ -15,7 +15,7 @@
15
15
  ## 流程 C:脚本分镜到视频
16
16
 
17
17
  - 用户要求打开带货任务时,调用 `open_flow_c_website`,不要输出或询问 Local URL、连接令牌。
18
- - 网站把脚本任务交给本机 Codex 后,使用 `flow_c_get_script_task` 读取完整、服务器持久化的任务,再用 `flow_c_submit_script_chunk` 每次回传 1–25 条。不要要求用户复制 JSON、选择交接文件或把短期令牌发到聊天中。
18
+ - 网站把脚本任务交给本机 Codex 后,使用 `flow_c_get_script_task` 读取完整、服务器持久化的任务,再用 `flow_c_submit_script_chunk` 每次回传 1–30 条。不要要求用户复制 JSON、选择交接文件或把短期令牌发到聊天中。
19
19
  - 大批量脚本按本机助手指定的序号分段创作;批量大小不能成为降低质量、缩短脚本或套用模板的理由。每条都必须独立构思、符合目标市场自然语言习惯、保持紧凑但清晰可说完的 10 秒节奏。
20
20
  - 写脚本和回传草案不创建付费任务。只有客户在网站审阅并确认费用后,中心服务才可开始故事板和视频生成。
21
21
 
@@ -22,7 +22,7 @@ function registerWorkflowTools(server, config) {
22
22
  inputSchema: { handoffId: z.string().uuid().describe("网站创建的脚本交接 ID") },
23
23
  }, async ({ handoffId }) => workflowTool(config, `/agent/workflow/script-handoffs/${encodeURIComponent(handoffId)}/task`, { method: "GET" }));
24
24
  server.registerTool("flow_c_submit_script_chunk", {
25
- description: "把本段独立完成的 Flow C 高质量脚本持久化回传给网站。每次 1–25 条;成功后再创作下一段。",
25
+ description: "把本段独立完成的 Flow C 高质量脚本持久化回传给网站。每次 1–30 条;成功后再创作下一段。",
26
26
  inputSchema: {
27
27
  handoffId: z.string().uuid().describe("脚本交接 ID"),
28
28
  jobs: z.array(z.object({
@@ -9,6 +9,8 @@ type ScriptTask = {
9
9
  requested_count: number;
10
10
  product_quantities: number[];
11
11
  instructions: string;
12
+ status: "pending" | "writing" | "ready";
13
+ received_ordinals: number[];
12
14
  expires_at: string;
13
15
  };
14
16
  /** 本机持久化的 Flow C 控制器:脚本交给本机 Codex,视频直接落盘。 */
@@ -64,6 +66,7 @@ export declare class WorkflowManager {
64
66
  submitScriptChunk(idValue: unknown, jobsValue: unknown): Promise<{
65
67
  accepted: number;
66
68
  received: number;
69
+ receivedOrdinals?: number[];
67
70
  requestedCount: number;
68
71
  status: string;
69
72
  } & {
@@ -10,7 +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
  const STATE_FILE = path.join(CONFIG_DIR, "workflow-state.json");
13
- const SCRIPT_CHUNK_SIZE = 10;
13
+ const SCRIPT_CHUNK_SIZE = 30;
14
14
  const DOWNLOAD_POLL_MS = 10_000;
15
15
  /** 本机持久化的 Flow C 控制器:脚本交给本机 Codex,视频直接落盘。 */
16
16
  export class WorkflowManager {
@@ -76,6 +76,7 @@ export class WorkflowManager {
76
76
  const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/task`, record.accessToken, "x-workflow-handoff-token");
77
77
  const task = data.handoff;
78
78
  record.requestedCount = Number(task.requested_count || 0);
79
+ record.receivedOrdinals = Array.isArray(task.received_ordinals) ? task.received_ordinals.map(Number).filter(Number.isInteger).sort((left, right) => left - right) : [];
79
80
  record.expiresAt = task.expires_at;
80
81
  record.updatedAt = now();
81
82
  this.save();
@@ -84,15 +85,19 @@ export class WorkflowManager {
84
85
  /** MCP 分段回传脚本,中心接口再次执行数量、产品索引和脚本完整性校验。 */
85
86
  async submitScriptChunk(idValue, jobsValue) {
86
87
  const record = this.scriptRecord(workflowId(idValue, "脚本交接 ID"));
87
- if (!Array.isArray(jobsValue) || !jobsValue.length || jobsValue.length > 25)
88
- throw new Error("每段必须包含 1–25 条脚本");
88
+ if (!Array.isArray(jobsValue) || !jobsValue.length || jobsValue.length > SCRIPT_CHUNK_SIZE)
89
+ throw new Error(`每段必须包含 1–${SCRIPT_CHUNK_SIZE} 条脚本`);
89
90
  const jobs = jobsValue;
90
91
  const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/draft-chunks`, record.accessToken, "x-workflow-handoff-token", { method: "POST", body: JSON.stringify({ jobs }) });
91
- const accepted = new Set(record.receivedOrdinals);
92
- for (const job of jobs)
93
- if (Number.isInteger(Number(job?.ordinal)))
94
- accepted.add(Number(job.ordinal));
95
- record.receivedOrdinals = [...accepted].sort((a, b) => a - b);
92
+ if (Array.isArray(data.receivedOrdinals))
93
+ record.receivedOrdinals = data.receivedOrdinals.map(Number).filter(Number.isInteger).sort((left, right) => left - right);
94
+ else {
95
+ const accepted = new Set(record.receivedOrdinals);
96
+ for (const job of jobs)
97
+ if (Number.isInteger(Number(job?.ordinal)))
98
+ accepted.add(Number(job.ordinal));
99
+ record.receivedOrdinals = [...accepted].sort((a, b) => a - b);
100
+ }
96
101
  record.requestedCount = Number(data.requestedCount || record.requestedCount);
97
102
  record.status = data.status === "ready" ? "complete" : "running";
98
103
  record.message = data.status === "ready" ? `全部 ${record.requestedCount} 条高质量脚本已回传` : `已回传 ${data.received}/${data.requestedCount} 条脚本`;
@@ -230,10 +235,13 @@ export class WorkflowManager {
230
235
  if (!progressed)
231
236
  throw new Error(`本机 Codex 连续 3 次未回传第 ${missing[0]}–${missing.at(-1)} 条,请点击重试`);
232
237
  }
233
- if (record.receivedOrdinals.length >= task.requested_count) {
238
+ const finalTask = await this.scriptTask(id);
239
+ if (finalTask.status === "ready" && record.receivedOrdinals.length === task.requested_count) {
234
240
  record.status = "complete";
235
241
  record.message = `全部 ${task.requested_count} 条高质量脚本已回传`;
236
242
  }
243
+ else
244
+ throw new Error(`中心校验尚未通过(${record.receivedOrdinals.length}/${task.requested_count} 条),请点击重试`);
237
245
  }
238
246
  catch (error) {
239
247
  record.status = error instanceof ExpiredCapabilityError ? "expired" : "error";
@@ -364,9 +372,36 @@ class ExpiredCapabilityError extends Error {
364
372
  function scriptChunkPrompt(id, task, ordinals) {
365
373
  return `你正在后台处理“抖音小辉跨境工具”Flow C 脚本交接 ${id}。
366
374
  必须使用 MCP 工具 flow_c_get_script_task 读取完整任务,再只创作 ordinal ${ordinals[0]} 到 ${ordinals.at(-1)}(精确列表:${ordinals.join(", ")})。
375
+ 本段 productIndex 必须严格按此映射填写:${scriptProductAssignments(task.product_quantities, ordinals)}。不得凭产品名称猜测或把相邻产品编号混用。
367
376
  脚本质量绝不能因批量而降低:每条都必须独立构思、完整、真实合规,严格遵守任务 instructions、产品图片顺序和 productIndex;使用目标市场 ${task.market} 的自然本地语言与偏快但清晰的 10 秒节奏。
368
377
  写完后必须调用 flow_c_submit_script_chunk 一次回传这 ${ordinals.length} 条,handoffId=${id}。不要创建付费批次,不要调用供应商模型,不要在聊天输出大段 JSON。工具返回成功后仅简短结束。`;
369
378
  }
379
+ function scriptProductAssignments(productQuantities, ordinals) {
380
+ const assignments = [];
381
+ let first = ordinals[0];
382
+ let previous = first;
383
+ let productIndex = productIndexForOrdinal(productQuantities, first);
384
+ for (const ordinal of ordinals.slice(1)) {
385
+ const nextProductIndex = productIndexForOrdinal(productQuantities, ordinal);
386
+ if (nextProductIndex !== productIndex || ordinal !== previous + 1) {
387
+ assignments.push(`${first}${first === previous ? "" : `–${previous}`} → productIndex ${productIndex}`);
388
+ first = ordinal;
389
+ productIndex = nextProductIndex;
390
+ }
391
+ previous = ordinal;
392
+ }
393
+ assignments.push(`${first}${first === previous ? "" : `–${previous}`} → productIndex ${productIndex}`);
394
+ return assignments.join(";");
395
+ }
396
+ function productIndexForOrdinal(productQuantities, ordinal) {
397
+ let lastOrdinal = 0;
398
+ for (const [productIndex, quantity] of productQuantities.entries()) {
399
+ lastOrdinal += Number(quantity);
400
+ if (ordinal <= lastOrdinal)
401
+ return productIndex;
402
+ }
403
+ return -1;
404
+ }
370
405
  function publicScript(record) {
371
406
  return { id: record.id, status: record.status, requestedCount: record.requestedCount, received: record.receivedOrdinals.length, threadId: record.threadId, message: record.message, expiresAt: record.expiresAt, updatedAt: record.updatedAt };
372
407
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",