@xiaohhhh1/canvas-agent 0.4.49 → 0.4.50
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.
package/dist/agent/codex.d.ts
CHANGED
|
@@ -17,7 +17,18 @@ export type CodexRunResult = {
|
|
|
17
17
|
ok: false;
|
|
18
18
|
error: string;
|
|
19
19
|
};
|
|
20
|
-
export type CodexWorkflowRunResult =
|
|
20
|
+
export type CodexWorkflowRunResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
text: string;
|
|
23
|
+
timings: {
|
|
24
|
+
queueWaitMs: number;
|
|
25
|
+
threadStartMs: number;
|
|
26
|
+
modelMs: number;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: string;
|
|
31
|
+
retryable: boolean;
|
|
21
32
|
timings: {
|
|
22
33
|
queueWaitMs: number;
|
|
23
34
|
threadStartMs: number;
|
|
@@ -45,6 +56,8 @@ export declare function runCodexWorkflowTurn(prompt: string, emit: AgentEmit, op
|
|
|
45
56
|
onWorkerStart?: () => void;
|
|
46
57
|
onWorkerFinish?: () => void;
|
|
47
58
|
}): Promise<CodexWorkflowRunResult>;
|
|
59
|
+
/** Contract/preflight 4xx errors are deterministic and must never burn every fallback chunk size. */
|
|
60
|
+
export declare function isDeterministicWorkflowContractError(error: unknown): boolean;
|
|
48
61
|
/** 强制回收失去响应的 app-server;只用于有界超时恢复。 */
|
|
49
62
|
export declare function restartCodexApp(message?: string): Promise<void>;
|
|
50
63
|
/** 回复当前 app-server 的待处理权限请求。 */
|
package/dist/agent/codex.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function interruptCodexTurn(threadId) {
|
|
|
37
37
|
*/
|
|
38
38
|
export async function runCodexWorkflowTurn(prompt, emit, options) {
|
|
39
39
|
if (!prompt.trim())
|
|
40
|
-
return { ok: false, error: "Codex prompt is empty", timings: { queueWaitMs: 0, threadStartMs: 0, modelMs: 0 } };
|
|
40
|
+
return { ok: false, error: "Codex prompt is empty", retryable: false, timings: { queueWaitMs: 0, threadStartMs: 0, modelMs: 0 } };
|
|
41
41
|
return await workflowCodexPool.run(async (workerIndex, queueWaitMs) => {
|
|
42
42
|
options.onWorkerStart?.();
|
|
43
43
|
const modelSettings = { model: FLOW_C_CODEX_MODEL, reasoningEffort: FLOW_C_CODEX_REASONING_EFFORT };
|
|
@@ -61,7 +61,7 @@ export async function runCodexWorkflowTurn(prompt, emit, options) {
|
|
|
61
61
|
workflowCodexApps.delete(workerIndex);
|
|
62
62
|
await app.terminate("Flow C 脚本回合超时,已仅回收当前 worker");
|
|
63
63
|
await turn.catch(() => undefined);
|
|
64
|
-
return { ok: false, error: "Flow C 脚本回合超过 8 分钟,已自动终止当前 worker", timings: { queueWaitMs, threadStartMs, modelMs: Date.now() - modelStartedAt } };
|
|
64
|
+
return { ok: false, error: "Flow C 脚本回合超过 8 分钟,已自动终止当前 worker", retryable: true, timings: { queueWaitMs, threadStartMs, modelMs: Date.now() - modelStartedAt } };
|
|
65
65
|
}
|
|
66
66
|
return { ok: true, text: result, timings: { queueWaitMs, threadStartMs, modelMs: Date.now() - modelStartedAt } };
|
|
67
67
|
}
|
|
@@ -69,7 +69,7 @@ export async function runCodexWorkflowTurn(prompt, emit, options) {
|
|
|
69
69
|
logger.error("Flow C Codex worker failed", { workerIndex, error });
|
|
70
70
|
const message = errorMessage(error);
|
|
71
71
|
emit("agent_error", { message });
|
|
72
|
-
return { ok: false, error: message, timings: { queueWaitMs, threadStartMs: threadStartMs || Date.now() - threadStartedAt, modelMs: modelStartedAt ? Date.now() - modelStartedAt : 0 } };
|
|
72
|
+
return { ok: false, error: message, retryable: !isDeterministicWorkflowContractError(message), timings: { queueWaitMs, threadStartMs: threadStartMs || Date.now() - threadStartedAt, modelMs: modelStartedAt ? Date.now() - modelStartedAt : 0 } };
|
|
73
73
|
}
|
|
74
74
|
finally {
|
|
75
75
|
if (timer)
|
|
@@ -78,6 +78,11 @@ export async function runCodexWorkflowTurn(prompt, emit, options) {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
+
/** Contract/preflight 4xx errors are deterministic and must never burn every fallback chunk size. */
|
|
82
|
+
export function isDeterministicWorkflowContractError(error) {
|
|
83
|
+
const message = errorMessage(error);
|
|
84
|
+
return /invalid_json_schema|invalid schema for response_format|text\.format\.schema|response[_ ]format[^\n]*(?:invalid|schema)/i.test(message);
|
|
85
|
+
}
|
|
81
86
|
async function startWorkflowCodexApp(workerIndex, emit) {
|
|
82
87
|
let started;
|
|
83
88
|
started = await CodexAppClient.start(emit, () => {
|
|
@@ -229,6 +229,11 @@ export declare class WorkflowManager {
|
|
|
229
229
|
private save;
|
|
230
230
|
}
|
|
231
231
|
export declare function compareScriptQueueRecords(left: Pick<ScriptRecord, "priorityAt" | "updatedAt">, right: Pick<ScriptRecord, "priorityAt" | "updatedAt">): number;
|
|
232
|
+
/** A deterministic response-format failure stops fallback isolation immediately. */
|
|
233
|
+
export declare function terminalScriptChunkError(results: Array<{
|
|
234
|
+
error?: string;
|
|
235
|
+
terminal?: boolean;
|
|
236
|
+
}>): string;
|
|
232
237
|
export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[]): string;
|
|
233
238
|
export declare function missingOrdinals(total: number, received: number[]): number[];
|
|
234
239
|
export {};
|
package/dist/workflow/manager.js
CHANGED
|
@@ -239,6 +239,10 @@ export class WorkflowManager {
|
|
|
239
239
|
const workspace = ensureSiteWorkspace(this.config);
|
|
240
240
|
const durationSeconds = Number(task.duration_seconds || 10);
|
|
241
241
|
const chunkSizes = flowCScriptChunkSizes(durationSeconds);
|
|
242
|
+
// Fail locally before starting any worker if a future schema edit
|
|
243
|
+
// violates strict response-format invariants.
|
|
244
|
+
for (const chunkSize of new Set(chunkSizes))
|
|
245
|
+
flowCScriptOutputSchema(durationSeconds, chunkSize);
|
|
242
246
|
record.activeChunks = 0;
|
|
243
247
|
let chunkSizeIndex = 0;
|
|
244
248
|
while (record.receivedOrdinals.length < task.requested_count) {
|
|
@@ -253,6 +257,10 @@ export class WorkflowManager {
|
|
|
253
257
|
this.save();
|
|
254
258
|
const results = await Promise.all(chunks.map((ordinals) => this.runScriptChunk(id, task, ordinals, workspace.workspacePath)));
|
|
255
259
|
await this.scriptTask(id);
|
|
260
|
+
const terminalError = terminalScriptChunkError(results);
|
|
261
|
+
if (terminalError) {
|
|
262
|
+
throw new Error(`本机脚本结构化契约被 Codex 拒绝,已停止自动重试且未提交缺失脚本。请先升级或修复 Canvas Agent,再手动重试(${terminalError})`);
|
|
263
|
+
}
|
|
256
264
|
const progressed = record.receivedOrdinals.length > before;
|
|
257
265
|
if (progressed) {
|
|
258
266
|
chunkSizeIndex = 0;
|
|
@@ -304,7 +312,7 @@ export class WorkflowManager {
|
|
|
304
312
|
this.emitScriptStage(id, ordinals, "thread_start", result.timings.threadStartMs);
|
|
305
313
|
this.emitScriptStage(id, ordinals, "model", result.timings.modelMs);
|
|
306
314
|
if (!result.ok || !result.text)
|
|
307
|
-
return { error: result.ok ? "Codex 未返回可用的结构化脚本" : result.error };
|
|
315
|
+
return { error: result.ok ? "Codex 未返回可用的结构化脚本" : result.error, terminal: !result.ok && !result.retryable };
|
|
308
316
|
try {
|
|
309
317
|
const parseStartedAt = Date.now();
|
|
310
318
|
const jobs = parseFlowCScriptOutput(result.text, ordinals);
|
|
@@ -312,10 +320,10 @@ export class WorkflowManager {
|
|
|
312
320
|
const persistStartedAt = Date.now();
|
|
313
321
|
await this.submitGeneratedScriptJobs(id, jobs);
|
|
314
322
|
this.emitScriptStage(id, ordinals, "persist", Date.now() - persistStartedAt);
|
|
315
|
-
return {};
|
|
323
|
+
return { terminal: false };
|
|
316
324
|
}
|
|
317
325
|
catch (error) {
|
|
318
|
-
return { error: error instanceof Error ? `结构化脚本未通过本机校验:${error.message}` : "结构化脚本未通过本机校验" };
|
|
326
|
+
return { error: error instanceof Error ? `结构化脚本未通过本机校验:${error.message}` : "结构化脚本未通过本机校验", terminal: false };
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
/** 只记录阶段、ordinal 和毫秒数;禁止记录 prompt、令牌或中心能力。 */
|
|
@@ -487,6 +495,10 @@ export function compareScriptQueueRecords(left, right) {
|
|
|
487
495
|
return String(right.priorityAt || "").localeCompare(String(left.priorityAt || ""));
|
|
488
496
|
return left.updatedAt.localeCompare(right.updatedAt);
|
|
489
497
|
}
|
|
498
|
+
/** A deterministic response-format failure stops fallback isolation immediately. */
|
|
499
|
+
export function terminalScriptChunkError(results) {
|
|
500
|
+
return results.find((result) => result.terminal)?.error || "";
|
|
501
|
+
}
|
|
490
502
|
class ExpiredCapabilityError extends Error {
|
|
491
503
|
}
|
|
492
504
|
export function scriptChunkPrompt(id, task, ordinals) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
type JsonSchema = Record<string, unknown>;
|
|
2
2
|
/** Codex app-server 的结构化脚本返回契约;回传由 Agent 完成,不依赖模型调用 MCP。 */
|
|
3
3
|
export declare function flowCScriptOutputSchema(durationSeconds: 10 | 20 | 30, count: number): JsonSchema;
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI strict structured output requires every declared object property to
|
|
6
|
+
* appear in `required`. Optional semantics must therefore be represented by a
|
|
7
|
+
* required nullable field, never by omitting that key from `required`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertStrictResponseSchema(schemaValue: unknown, path?: string): asserts schemaValue is JsonSchema;
|
|
4
10
|
export declare function parseFlowCScriptOutput(value: string, expectedOrdinals: number[]): unknown[];
|
|
5
11
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const text = { type: "string", minLength: 1 };
|
|
2
|
+
const nullableText = { anyOf: [text, { type: "null" }] };
|
|
2
3
|
const continuityFields = ["character", "wardrobe", "location", "lighting", "productState", "unfinishedAction", "nextGoal"];
|
|
3
4
|
function object(properties, required = Object.keys(properties)) {
|
|
4
5
|
return { type: "object", properties, required, additionalProperties: false };
|
|
@@ -26,16 +27,16 @@ function creativePlanSchema() {
|
|
|
26
27
|
format: text,
|
|
27
28
|
visualHook: text,
|
|
28
29
|
conflict: text,
|
|
29
|
-
escalation:
|
|
30
|
-
turn:
|
|
30
|
+
escalation: nullableText,
|
|
31
|
+
turn: nullableText,
|
|
31
32
|
productIntervention: text,
|
|
32
33
|
visibleProof: text,
|
|
33
34
|
callbackMotivation: text,
|
|
34
35
|
truthBoundary: text,
|
|
35
36
|
differentiationKey: text,
|
|
36
|
-
styleCardSummary:
|
|
37
|
+
styleCardSummary: nullableText,
|
|
37
38
|
};
|
|
38
|
-
return object(properties
|
|
39
|
+
return object(properties);
|
|
39
40
|
}
|
|
40
41
|
function qualityGateSchema() {
|
|
41
42
|
return object({
|
|
@@ -87,7 +88,40 @@ export function flowCScriptOutputSchema(durationSeconds, count) {
|
|
|
87
88
|
properties.masterScript = { type: "string", minLength: 40 };
|
|
88
89
|
properties.segments = { type: "array", minItems: durationSeconds / 10, maxItems: durationSeconds / 10, items: segmentSchema() };
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
const schema = object({ jobs: { type: "array", minItems: count, maxItems: count, items: object(properties) } });
|
|
92
|
+
assertStrictResponseSchema(schema);
|
|
93
|
+
return schema;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* OpenAI strict structured output requires every declared object property to
|
|
97
|
+
* appear in `required`. Optional semantics must therefore be represented by a
|
|
98
|
+
* required nullable field, never by omitting that key from `required`.
|
|
99
|
+
*/
|
|
100
|
+
export function assertStrictResponseSchema(schemaValue, path = "$") {
|
|
101
|
+
const schema = recordOf(schemaValue);
|
|
102
|
+
if (!schema)
|
|
103
|
+
throw new Error(`Strict response schema at ${path} must be an object`);
|
|
104
|
+
if (schema.type === "object") {
|
|
105
|
+
const properties = recordOf(schema.properties);
|
|
106
|
+
if (!properties)
|
|
107
|
+
throw new Error(`Strict response schema object at ${path} needs properties`);
|
|
108
|
+
const keys = Object.keys(properties).sort();
|
|
109
|
+
const required = Array.isArray(schema.required) ? schema.required.map(String).sort() : [];
|
|
110
|
+
if (keys.length !== required.length || keys.some((key, index) => key !== required[index])) {
|
|
111
|
+
throw new Error(`Strict response schema object at ${path} must require every property`);
|
|
112
|
+
}
|
|
113
|
+
if (schema.additionalProperties !== false)
|
|
114
|
+
throw new Error(`Strict response schema object at ${path} must disable additional properties`);
|
|
115
|
+
for (const [key, child] of Object.entries(properties))
|
|
116
|
+
assertStrictResponseSchema(child, `${path}.properties.${key}`);
|
|
117
|
+
}
|
|
118
|
+
if (schema.items)
|
|
119
|
+
assertStrictResponseSchema(schema.items, `${path}.items`);
|
|
120
|
+
for (const branchKey of ["anyOf", "oneOf", "allOf"]) {
|
|
121
|
+
const branches = schema[branchKey];
|
|
122
|
+
if (Array.isArray(branches))
|
|
123
|
+
branches.forEach((branch, index) => assertStrictResponseSchema(branch, `${path}.${branchKey}[${index}]`));
|
|
124
|
+
}
|
|
91
125
|
}
|
|
92
126
|
export function parseFlowCScriptOutput(value, expectedOrdinals) {
|
|
93
127
|
const source = String(value || "").trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|