@xiaohhhh1/canvas-agent 0.4.44 → 0.4.46
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,5 +1,5 @@
|
|
|
1
1
|
import { type JsonRecord } from "../utils/value.js";
|
|
2
|
-
import type { CodexPlanUpdate, CodexRequestParams } from "./codex-protocol.js";
|
|
2
|
+
import type { CodexPlanUpdate, CodexRequestParams, CodexTurn } from "./codex-protocol.js";
|
|
3
3
|
import type { AgentEmit, AgentPermissionMode } from "./types.js";
|
|
4
4
|
/** 封装 Codex app-server 的 JSON-RPC 通信与事件转换。 */
|
|
5
5
|
export declare class CodexAppClient {
|
|
@@ -66,6 +66,13 @@ export declare class CodexAppClient {
|
|
|
66
66
|
private emitDelta;
|
|
67
67
|
/** 合并短时间内的文本增量,减少 SSE 传输和前端渲染次数。 */
|
|
68
68
|
private flushDelta;
|
|
69
|
+
/**
|
|
70
|
+
* app-server 的流式通知偶尔会在本地重连时丢失。定期读取线程的最终
|
|
71
|
+
* 状态,保证被中断的 turn 不会只因少了一条通知而永久占着 Flow C 队列。
|
|
72
|
+
*/
|
|
73
|
+
private scheduleTurnReconciliation;
|
|
74
|
+
/** 清除 turn 等待器与其轮询计时器。 */
|
|
75
|
+
private clearActiveTurn;
|
|
69
76
|
/** 自动回复 app-server 发起的授权或交互请求。 */
|
|
70
77
|
private answerServerRequest;
|
|
71
78
|
/** 完成指定 JSON-RPC 请求。 */
|
|
@@ -75,3 +82,9 @@ export declare class CodexAppClient {
|
|
|
75
82
|
/** 拒绝进程退出时仍未完成的请求与 turn。 */
|
|
76
83
|
private failAll;
|
|
77
84
|
}
|
|
85
|
+
/** 只有正常 completed 才能被调用方视为可用脚本。 */
|
|
86
|
+
export declare function isTerminalTurnStatus(status: unknown): boolean;
|
|
87
|
+
/** interrupted 也必须作为失败传回,不能被误当成空的成功响应。 */
|
|
88
|
+
export declare function turnFailure(turn: Pick<CodexTurn, "status" | "error">): Error | null;
|
|
89
|
+
/** 补偿轮询发现终态时,尽量取回已完成的结构化文本。 */
|
|
90
|
+
export declare function turnText(turn: CodexTurn): string;
|
|
@@ -8,6 +8,7 @@ import { field } from "../utils/value.js";
|
|
|
8
8
|
const canvasAgentMcp = canvasAgentMcpCommand();
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const STREAM_UPDATE_INTERVAL_MS = 40;
|
|
11
|
+
const TURN_RECONCILE_INTERVAL_MS = 5_000;
|
|
11
12
|
/** 封装 Codex app-server 的 JSON-RPC 通信与事件转换。 */
|
|
12
13
|
export class CodexAppClient {
|
|
13
14
|
child;
|
|
@@ -110,7 +111,10 @@ export class CodexAppClient {
|
|
|
110
111
|
throw completed.error;
|
|
111
112
|
return completed?.text || "";
|
|
112
113
|
}
|
|
113
|
-
return await new Promise((resolve, reject) =>
|
|
114
|
+
return await new Promise((resolve, reject) => {
|
|
115
|
+
this.activeTurns.set(turnId, { resolve, reject });
|
|
116
|
+
this.scheduleTurnReconciliation(threadId, turnId);
|
|
117
|
+
});
|
|
114
118
|
}
|
|
115
119
|
/** 中断当前正在运行的 Codex turn。 */
|
|
116
120
|
async interruptCurrentTurn() {
|
|
@@ -267,11 +271,11 @@ export class CodexAppClient {
|
|
|
267
271
|
const turn = params.turn;
|
|
268
272
|
const turnId = turn.id;
|
|
269
273
|
const pending = this.activeTurns.get(turnId);
|
|
270
|
-
const error = turn
|
|
274
|
+
const error = turnFailure(turn);
|
|
271
275
|
const text = this.finalTextByTurn.get(turnId) || "";
|
|
272
276
|
this.finalTextByTurn.delete(turnId);
|
|
273
277
|
if (pending) {
|
|
274
|
-
this.
|
|
278
|
+
this.clearActiveTurn(turnId);
|
|
275
279
|
error ? pending.reject(new Error(error.message || "Codex turn failed")) : pending.resolve(text);
|
|
276
280
|
}
|
|
277
281
|
else if (turnId) {
|
|
@@ -311,6 +315,53 @@ export class CodexAppClient {
|
|
|
311
315
|
if (pending.delta)
|
|
312
316
|
this.emit("agent_event", { agent: "codex", type: "item.updated", item: { id, type: pending.itemType, delta: pending.delta }, ...codexEventScope(pending.params) });
|
|
313
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* app-server 的流式通知偶尔会在本地重连时丢失。定期读取线程的最终
|
|
320
|
+
* 状态,保证被中断的 turn 不会只因少了一条通知而永久占着 Flow C 队列。
|
|
321
|
+
*/
|
|
322
|
+
scheduleTurnReconciliation(threadId, turnId) {
|
|
323
|
+
const check = async () => {
|
|
324
|
+
const pending = this.activeTurns.get(turnId);
|
|
325
|
+
if (!pending)
|
|
326
|
+
return;
|
|
327
|
+
try {
|
|
328
|
+
const result = await this.readThread(threadId, true);
|
|
329
|
+
const thread = field(result, "thread");
|
|
330
|
+
const turns = Array.isArray(field(thread, "turns")) ? field(thread, "turns") : [];
|
|
331
|
+
const turn = turns.find((item) => item?.id === turnId);
|
|
332
|
+
if (turn && isTerminalTurnStatus(turn.status)) {
|
|
333
|
+
const error = turnFailure(turn);
|
|
334
|
+
const text = turnText(turn);
|
|
335
|
+
this.finalTextByTurn.delete(turnId);
|
|
336
|
+
this.clearActiveTurn(turnId);
|
|
337
|
+
error ? pending.reject(new Error(error.message)) : pending.resolve(text);
|
|
338
|
+
if (turnId === this.currentTurnId) {
|
|
339
|
+
this.currentThreadId = "";
|
|
340
|
+
this.currentTurnId = "";
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
// The normal deadline in the workflow manager remains the hard
|
|
347
|
+
// fallback. A transient read error must not cancel live work.
|
|
348
|
+
logger.debug("Codex turn reconciliation read failed", { threadId, turnId, error });
|
|
349
|
+
}
|
|
350
|
+
const current = this.activeTurns.get(turnId);
|
|
351
|
+
if (current)
|
|
352
|
+
current.reconcileTimer = setTimeout(() => void check(), TURN_RECONCILE_INTERVAL_MS);
|
|
353
|
+
};
|
|
354
|
+
const pending = this.activeTurns.get(turnId);
|
|
355
|
+
if (pending)
|
|
356
|
+
pending.reconcileTimer = setTimeout(() => void check(), TURN_RECONCILE_INTERVAL_MS);
|
|
357
|
+
}
|
|
358
|
+
/** 清除 turn 等待器与其轮询计时器。 */
|
|
359
|
+
clearActiveTurn(turnId) {
|
|
360
|
+
const pending = this.activeTurns.get(turnId);
|
|
361
|
+
if (pending?.reconcileTimer)
|
|
362
|
+
clearTimeout(pending.reconcileTimer);
|
|
363
|
+
this.activeTurns.delete(turnId);
|
|
364
|
+
}
|
|
314
365
|
/** 自动回复 app-server 发起的授权或交互请求。 */
|
|
315
366
|
answerServerRequest(message) {
|
|
316
367
|
const method = String(message.method);
|
|
@@ -339,7 +390,13 @@ export class CodexAppClient {
|
|
|
339
390
|
}
|
|
340
391
|
/** 拒绝进程退出时仍未完成的请求与 turn。 */
|
|
341
392
|
failAll(message) {
|
|
342
|
-
[...this.pending.values()
|
|
393
|
+
[...this.pending.values()].forEach((item) => item.reject(new Error(message)));
|
|
394
|
+
[...this.activeTurns.entries()].forEach(([turnId, item]) => {
|
|
395
|
+
if (item.reconcileTimer)
|
|
396
|
+
clearTimeout(item.reconcileTimer);
|
|
397
|
+
this.activeTurns.delete(turnId);
|
|
398
|
+
item.reject(new Error(message));
|
|
399
|
+
});
|
|
343
400
|
this.pendingDeltas.forEach((item) => clearTimeout(item.timer));
|
|
344
401
|
this.pending.clear();
|
|
345
402
|
this.activeTurns.clear();
|
|
@@ -351,6 +408,35 @@ export class CodexAppClient {
|
|
|
351
408
|
this.currentTurnId = "";
|
|
352
409
|
}
|
|
353
410
|
}
|
|
411
|
+
/** 只有正常 completed 才能被调用方视为可用脚本。 */
|
|
412
|
+
export function isTerminalTurnStatus(status) {
|
|
413
|
+
return ["completed", "failed", "interrupted", "cancelled", "canceled"].includes(String(status || "").toLowerCase());
|
|
414
|
+
}
|
|
415
|
+
/** interrupted 也必须作为失败传回,不能被误当成空的成功响应。 */
|
|
416
|
+
export function turnFailure(turn) {
|
|
417
|
+
if (turn.error)
|
|
418
|
+
return new Error(turn.error.message || "Codex turn failed");
|
|
419
|
+
const status = String(turn.status || "completed").toLowerCase();
|
|
420
|
+
return status === "completed" ? null : new Error(`Codex turn ${status}`);
|
|
421
|
+
}
|
|
422
|
+
/** 补偿轮询发现终态时,尽量取回已完成的结构化文本。 */
|
|
423
|
+
export function turnText(turn) {
|
|
424
|
+
const items = Array.isArray(field(turn, "items")) ? field(turn, "items") : [];
|
|
425
|
+
for (const item of [...items].reverse()) {
|
|
426
|
+
if (!["agent_message", "agentMessage"].includes(String(field(item, "type") || "")))
|
|
427
|
+
continue;
|
|
428
|
+
const text = field(item, "text");
|
|
429
|
+
if (typeof text === "string" && text)
|
|
430
|
+
return text;
|
|
431
|
+
const content = field(item, "content");
|
|
432
|
+
if (Array.isArray(content)) {
|
|
433
|
+
const joined = content.map((part) => String(field(part, "text") || "")).join("");
|
|
434
|
+
if (joined)
|
|
435
|
+
return joined;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return "";
|
|
439
|
+
}
|
|
354
440
|
/** 生成 Codex 调用 Canvas Agent MCP 的启动命令。 */
|
|
355
441
|
function canvasAgentMcpCommand() {
|
|
356
442
|
const current = process.argv.find((arg) => /index\.(t|j)s$/.test(arg)) || "";
|
package/dist/canvas/schemas.d.ts
CHANGED
|
@@ -975,16 +975,16 @@ export declare const toolInputSchemas: {
|
|
|
975
975
|
}, "strip", z.ZodTypeAny, {
|
|
976
976
|
title: string;
|
|
977
977
|
kind: "text" | "image";
|
|
978
|
-
source?: string | undefined;
|
|
979
978
|
content?: string | undefined;
|
|
979
|
+
source?: string | undefined;
|
|
980
980
|
tags?: string[] | undefined;
|
|
981
981
|
imageUrl?: string | undefined;
|
|
982
982
|
note?: string | undefined;
|
|
983
983
|
}, {
|
|
984
984
|
title: string;
|
|
985
985
|
kind: "text" | "image";
|
|
986
|
-
source?: string | undefined;
|
|
987
986
|
content?: string | undefined;
|
|
987
|
+
source?: string | undefined;
|
|
988
988
|
tags?: string[] | undefined;
|
|
989
989
|
imageUrl?: string | undefined;
|
|
990
990
|
note?: string | undefined;
|
package/dist/canvas/tools.d.ts
CHANGED
|
@@ -173,8 +173,8 @@ export declare function parseToolInput(name: ToolName, input: unknown): {
|
|
|
173
173
|
} | {
|
|
174
174
|
title: string;
|
|
175
175
|
kind: "text" | "image";
|
|
176
|
-
source?: string | undefined;
|
|
177
176
|
content?: string | undefined;
|
|
177
|
+
source?: string | undefined;
|
|
178
178
|
tags?: string[] | undefined;
|
|
179
179
|
imageUrl?: string | undefined;
|
|
180
180
|
note?: string | undefined;
|
|
@@ -8,6 +8,10 @@ function continuitySchema(frameField) {
|
|
|
8
8
|
}
|
|
9
9
|
function segmentSchema() {
|
|
10
10
|
return object({
|
|
11
|
+
// The central Flow C API receives each long-video segment as an
|
|
12
|
+
// independently executable script. Do not omit this just because the
|
|
13
|
+
// richer storyboard fields below are also present.
|
|
14
|
+
script: { type: "string", minLength: 40 },
|
|
11
15
|
continuityMode: { type: "string", enum: ["reset", "continue"] },
|
|
12
16
|
continuity: continuitySchema("previousEndingFrame"),
|
|
13
17
|
endingState: continuitySchema("endingFrame"),
|