odai-dsh-agent 0.2.39 → 0.2.51

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.
Files changed (69) hide show
  1. package/README.md +13 -11
  2. package/build/src/control-center-installer.d.mts.map +1 -1
  3. package/build/src/control-center-installer.mjs +5 -0
  4. package/build/src/control-center-installer.mjs.map +1 -1
  5. package/build/src/installer.d.mts.map +1 -1
  6. package/build/src/installer.mjs +1 -1
  7. package/build/src/installer.mjs.map +1 -1
  8. package/client/client.js +27 -6
  9. package/package.json +1 -1
  10. package/preset/odai/package.json +2 -1
  11. package/preset/odai/runtime/compaction-config.d.mts +2 -2
  12. package/preset/odai/runtime/compaction-config.d.mts.map +1 -1
  13. package/preset/odai/runtime/context-activation.mjs +1 -1
  14. package/preset/odai/runtime/context-activation.mjs.map +1 -1
  15. package/preset/odai/runtime/index.mjs +1 -1
  16. package/preset/odai/runtime/index.mjs.map +1 -1
  17. package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -1
  18. package/preset/odai/runtime/lifecycle-runtime.mjs +25 -4
  19. package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -1
  20. package/preset/odai/runtime/model-route.d.mts +5 -0
  21. package/preset/odai/runtime/model-route.d.mts.map +1 -1
  22. package/preset/odai/runtime/model-route.mjs +21 -2
  23. package/preset/odai/runtime/model-route.mjs.map +1 -1
  24. package/preset/odai/runtime/output-config.d.mts.map +1 -1
  25. package/preset/odai/runtime/output-config.mjs +2 -1
  26. package/preset/odai/runtime/output-config.mjs.map +1 -1
  27. package/preset/odai/runtime/role-overlays.d.mts +1 -1
  28. package/preset/odai/runtime/role-overlays.d.mts.map +1 -1
  29. package/preset/odai/runtime/role-overlays.mjs +4 -1
  30. package/preset/odai/runtime/role-overlays.mjs.map +1 -1
  31. package/preset/odai/runtime/router.d.mts +5 -1
  32. package/preset/odai/runtime/router.d.mts.map +1 -1
  33. package/preset/odai/runtime/router.mjs +47 -2
  34. package/preset/odai/runtime/router.mjs.map +1 -1
  35. package/preset/odai/runtime/routing-config.d.mts +5 -2
  36. package/preset/odai/runtime/routing-config.d.mts.map +1 -1
  37. package/preset/odai/runtime/routing-config.mjs +87 -42
  38. package/preset/odai/runtime/routing-config.mjs.map +1 -1
  39. package/preset/odai/runtime/runtime-config.d.mts +1 -1
  40. package/preset/odai/runtime/runtime-config.d.mts.map +1 -1
  41. package/preset/odai/runtime/runtime-config.mjs +15 -9
  42. package/preset/odai/runtime/runtime-config.mjs.map +1 -1
  43. package/preset/odai/runtime/runtime-support.d.mts +6 -1
  44. package/preset/odai/runtime/runtime-support.d.mts.map +1 -1
  45. package/preset/odai/runtime/runtime-support.mjs +90 -7
  46. package/preset/odai/runtime/runtime-support.mjs.map +1 -1
  47. package/preset/odai/runtime/runtime-types.d.mts +8 -1
  48. package/preset/odai/runtime/runtime-types.d.mts.map +1 -1
  49. package/preset/odai/runtime/runtime-types.mjs.map +1 -1
  50. package/preset/odai/runtime/skill-bundle.d.mts +1 -1
  51. package/preset/odai/runtime/skill-bundle.d.mts.map +1 -1
  52. package/preset/odai/runtime/work-policy.d.mts.map +1 -1
  53. package/preset/odai/runtime/work-policy.mjs +8 -2
  54. package/preset/odai/runtime/work-policy.mjs.map +1 -1
  55. package/preset/odai/skills/odai-orchestration/SKILL.md +1 -1
  56. package/preset/odai/skills/odai-orchestration/assets/codex-agents/role.toml +1 -0
  57. package/preset/odai/skills/odai-orchestration/assets/routing-roles/controller.md +1 -1
  58. package/preset/odai/skills/odai-orchestration/assets/routing-roles/creative.md +9 -0
  59. package/preset/odai/skills/odai-orchestration/manifest.json +9 -4
  60. package/preset/odai/skills/odai-orchestration/references/install.md +31 -1
  61. package/preset/odai/skills/odai-orchestration/references/orchestration.md +1 -0
  62. package/preset/odai/skills/odai-orchestration/scripts/build-routing.mjs +78 -17
  63. package/preset/odai/skills/odai-orchestration/scripts/compose-contracts.d.mts +1 -1
  64. package/preset/odai/skills/odai-orchestration/scripts/compose-contracts.mjs +2 -2
  65. package/preset/odai/skills/odai-orchestration/scripts/install-routing.mjs +101 -23
  66. package/preset/odai/skills/odai-orchestration/scripts/local-cli.d.mts +75 -0
  67. package/preset/odai/skills/odai-orchestration/scripts/local-cli.mjs +409 -0
  68. package/preset/odai/skills/odai-orchestration/scripts/run-role.mjs +190 -92
  69. package/preset/odai/skills/odai-orchestration/scripts/verify-routing.mjs +3 -3
@@ -5,7 +5,8 @@ import { homedir, tmpdir } from "node:os";
5
5
  import path from "node:path";
6
6
  import process from "node:process";
7
7
  import { spawnSync } from "node:child_process";
8
- import { fileURLToPath } from "node:url";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
+ import { randomUUID } from "node:crypto";
9
10
 
10
11
  process.on("uncaughtException", (error) => {
11
12
  console.error(error?.message || String(error));
@@ -14,121 +15,217 @@ process.on("uncaughtException", (error) => {
14
15
 
15
16
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
16
17
  const args = parseArgs(process.argv.slice(2));
17
- const supportedRoles = new Set(["controller", "researcher", "planner", "reviewer", "frontend"]);
18
+ const supportedRoles = new Set(["controller", "researcher", "planner", "reviewer", "frontend", "creative"]);
18
19
 
19
20
  if (args.help) {
20
21
  console.log(`Usage:
21
- node .codex/odai-run-role.mjs --role <controller|researcher|planner|reviewer|frontend> \\
22
+ node .codex/odai-run-role.mjs --role <controller|researcher|planner|reviewer|frontend|creative> \\
22
23
  [--input FILE] [--output FILE] [--evidence FILE] [--cwd PATH] [--manifest FILE] \\
23
24
  [--codex-bin FILE] [--sandbox MODE] [--session-id ID] [--closeout-ids A1,A2]
24
25
 
25
- 从 odai 托管清单取得当前 Codex 宿主的角色、模型与推理档,运行一次实际责任并记录可核对证据。`);
26
+ 从 odai 托管清单取得角色、模型与推理档,运行一次实际责任并记录可核对证据。
27
+ Claude 宿主使用 .claude/odai-run-role.mjs,仅支持非 controller 的 codex/antigravity/grok 本机 CLI 映射;
28
+ 未传 --evidence 时保存到执行器同目录 role-evidence/。--timeout 可覆盖本机 CLI 超时(1–7200 整数秒)。
29
+ --codex-bin、--sandbox、--session-id、--closeout-ids 仅用于 Codex 宿主。`);
26
30
  process.exit(0);
27
31
  }
28
32
 
29
33
  if (!supportedRoles.has(args.role)) fail(`不支持的角色:${args.role || "(missing)"}`);
30
34
  const manifestFile = path.resolve(args.manifest || path.join(scriptDir, "odai-routing.json"));
31
35
  const manifest = readJson(manifestFile, "路由清单");
32
- if (manifest.id !== "odai-routing-installation" || manifest.host !== "codex") fail("路由清单不是 Codex odai 托管安装");
36
+ if (manifest.id !== "odai-routing-installation" || !["codex", "claude"].includes(manifest.host)) fail("路由清单不是 Codex 或 Claude odai 托管安装");
33
37
  const mapping = manifest.mapping?.[args.role];
34
- if (!mapping?.model) fail(`路由清单缺少 ${args.role} 映射`);
38
+ if (manifest.host === "codex" && !mapping?.model) fail(`路由清单缺少 ${args.role} 映射`);
39
+ if (manifest.host === "codex" && mapping.backend === "local-cli") fail("Codex 宿主尚不支持 local-cli 映射");
35
40
 
36
41
  const workdir = path.resolve(args.cwd || process.cwd());
37
- const input = args.input ? readFileSync(path.resolve(args.input), "utf8").trim() : readFileSync(0, "utf8").trim();
38
- if (!input) fail("角色输入为空");
39
- const contractFile = path.join(scriptDir, "role-contracts", `odai-${args.role}.md`);
40
- if (!existsSync(contractFile)) fail(`缺少角色契约:${contractFile}`);
41
- const contract = readFileSync(contractFile, "utf8");
42
- const temporary = mkdtempSync(path.join(tmpdir(), "odai-role-"));
43
- const outputFile = path.resolve(args.output || path.join(temporary, "result.txt"));
44
- const evidenceFile = args.evidence ? path.resolve(args.evidence) : "";
45
- const startedAt = new Date().toISOString();
46
- const started = Date.now();
47
- mkdirSync(path.dirname(outputFile), { recursive: true });
42
+ if (manifest.host === "claude") await runLocalRole();
43
+ else runCodexRole();
48
44
 
49
- try {
50
- const readOnly = args.role === "researcher" || args.role === "planner" || args.role === "reviewer";
51
- const closeoutIds = parseCloseoutIds(args.closeoutIds);
52
- const schemaFile = closeoutIds.length > 0 ? writeCloseoutSchema(temporary, closeoutIds) : "";
53
- const hostOwnsRouting = process.env.ODAI_ROUTING_ACTIVE === "1";
54
- const shared = [
55
- "--json", "--model", mapping.model,
56
- ...effortArgs(mapping.reasoning_effort),
57
- ...(hostOwnsRouting ? ["-c", "features.multi_agent=false"] : []),
58
- ...(args.sessionId ? ["-c", `sandbox_mode=${JSON.stringify(readOnly ? "read-only" : args.sandbox)}`] : []),
59
- "-c", `developer_instructions=${JSON.stringify(contract)}`,
60
- ...(schemaFile ? ["--output-schema", schemaFile] : []),
61
- "-o", outputFile,
62
- ];
63
- const command = args.sessionId
64
- ? ["exec", "resume", ...shared, args.sessionId, "-"]
65
- : ["exec", ...shared, "--sandbox", readOnly ? "read-only" : args.sandbox, "-C", workdir, "-"];
66
- const child = spawnSync(args.codexBin || process.env.ODAI_CODEX_COMMAND || "codex", command, {
67
- cwd: workdir,
68
- input,
69
- encoding: "utf8",
70
- windowsHide: true,
71
- timeout: args.timeout * 1000,
72
- maxBuffer: 64 * 1024 * 1024,
73
- });
74
- if (child.error) fail(child.error.message);
75
- const raw = `${child.stdout || ""}${child.stderr || ""}`;
76
- if (child.status !== 0) fail(`Codex ${args.role} 调用失败:${tail(raw)}`);
77
- if (!existsSync(outputFile) || !readFileSync(outputFile, "utf8").trim()) fail("角色没有形成输出");
78
- if (closeoutIds.length > 0) writeFileSync(outputFile, renderCloseout(readFileSync(outputFile, "utf8"), closeoutIds), "utf8");
79
- const events = parseJsonLines(raw);
80
- const threadId = String(events.find((event) => event.type === "thread.started")?.thread_id || args.sessionId || "");
81
- if (args.sessionId && threadId !== args.sessionId) fail(`Codex 恢复了错误线程:期望 ${args.sessionId},实际 ${threadId || "unknown"}`);
82
- const eventModels = collectStrings(events, "model");
83
- const runtime = eventModels.length > 0 ? null : queryRuntime(threadId, Date.parse(startedAt));
84
- const models = runtime?.model ? [runtime.model] : eventModels;
45
+ function runCodexRole() {
46
+ const input = args.input ? readFileSync(path.resolve(args.input), "utf8").trim() : readFileSync(0, "utf8").trim();
47
+ if (!input) fail("角色输入为空");
48
+ const contractFile = path.join(scriptDir, "role-contracts", `odai-${args.role}.md`);
49
+ if (!existsSync(contractFile)) fail(`缺少角色契约:${contractFile}`);
50
+ const contract = readFileSync(contractFile, "utf8");
51
+ const temporary = mkdtempSync(path.join(tmpdir(), "odai-role-"));
52
+ const outputFile = path.resolve(args.output || path.join(temporary, "result.txt"));
53
+ const evidenceFile = args.evidence ? path.resolve(args.evidence) : "";
54
+ const startedAt = new Date().toISOString();
55
+ const started = Date.now();
56
+ mkdirSync(path.dirname(outputFile), { recursive: true });
57
+
58
+ try {
59
+ const readOnly = ["researcher", "planner", "reviewer", "creative"].includes(args.role);
60
+ const closeoutIds = parseCloseoutIds(args.closeoutIds);
61
+ const schemaFile = closeoutIds.length > 0 ? writeCloseoutSchema(temporary, closeoutIds) : "";
62
+ const hostOwnsRouting = process.env.ODAI_ROUTING_ACTIVE === "1";
63
+ const shared = [
64
+ "--json", "--model", mapping.model,
65
+ ...effortArgs(mapping.reasoning_effort),
66
+ ...(hostOwnsRouting ? ["-c", "features.multi_agent=false"] : []),
67
+ ...(args.sessionId ? ["-c", `sandbox_mode=${JSON.stringify(readOnly ? "read-only" : args.sandbox)}`] : []),
68
+ "-c", `developer_instructions=${JSON.stringify(contract)}`,
69
+ ...(schemaFile ? ["--output-schema", schemaFile] : []),
70
+ "-o", outputFile,
71
+ ];
72
+ const command = args.sessionId
73
+ ? ["exec", "resume", ...shared, args.sessionId, "-"]
74
+ : ["exec", ...shared, "--sandbox", readOnly ? "read-only" : args.sandbox, "-C", workdir, "-"];
75
+ const child = spawnSync(args.codexBin || process.env.ODAI_CODEX_COMMAND || "codex", command, {
76
+ cwd: workdir,
77
+ input,
78
+ encoding: "utf8",
79
+ windowsHide: true,
80
+ timeout: args.timeout * 1000,
81
+ maxBuffer: 64 * 1024 * 1024,
82
+ });
83
+ if (child.error) fail(child.error.message);
84
+ const raw = `${child.stdout || ""}${child.stderr || ""}`;
85
+ if (child.status !== 0) fail(`Codex ${args.role} 调用失败:${tail(raw)}`);
86
+ if (!existsSync(outputFile) || !readFileSync(outputFile, "utf8").trim()) fail("角色没有形成输出");
87
+ if (closeoutIds.length > 0) writeFileSync(outputFile, renderCloseout(readFileSync(outputFile, "utf8"), closeoutIds), "utf8");
88
+ const events = parseJsonLines(raw);
89
+ const threadId = String(events.find((event) => event.type === "thread.started")?.thread_id || args.sessionId || "");
90
+ if (args.sessionId && threadId !== args.sessionId) fail(`Codex 恢复了错误线程:期望 ${args.sessionId},实际 ${threadId || "unknown"}`);
91
+ const eventModels = collectStrings(events, "model");
92
+ const runtime = eventModels.length > 0 ? null : queryRuntime(threadId, Date.parse(startedAt));
93
+ const models = runtime?.model ? [runtime.model] : eventModels;
94
+ const evidence = {
95
+ version: 1,
96
+ role: args.role,
97
+ requested: { provider: "codex", model: mapping.model, reasoning_effort: mapping.reasoning_effort || null },
98
+ observed: {
99
+ provider: "codex",
100
+ thread_id: threadId || null,
101
+ models,
102
+ model_verified: models.length > 0,
103
+ reasoning_efforts: runtime?.reasoning_effort ? [runtime.reasoning_effort] : [],
104
+ tool_use_detected: containsToolUse(events),
105
+ tool_evidence: collectToolEvidence(events),
106
+ usage: lastDefined(events.map((event) => event.usage || event.payload?.usage)) || null,
107
+ cost_usd: null,
108
+ duration_ms: Date.now() - started,
109
+ started_at: startedAt,
110
+ completed_at: new Date().toISOString(),
111
+ exit_code: child.status,
112
+ },
113
+ output: outputFile,
114
+ };
115
+ writeEvidence(evidence);
116
+ const response = readFileSync(outputFile, "utf8");
117
+ process.stdout.write(response);
118
+ if (!response.endsWith("\n")) process.stdout.write("\n");
119
+ } catch (error) {
120
+ writeEvidence({
121
+ version: 1,
122
+ role: args.role,
123
+ requested: { provider: "codex", model: mapping.model, reasoning_effort: mapping.reasoning_effort || null },
124
+ observed: {
125
+ provider: "codex", thread_id: null, models: [], model_verified: false,
126
+ usage: null, cost_usd: null, duration_ms: Date.now() - started,
127
+ started_at: startedAt, completed_at: new Date().toISOString(), exit_code: null,
128
+ error: error?.message || String(error),
129
+ },
130
+ output: null,
131
+ });
132
+ throw error;
133
+ } finally {
134
+ rmSync(temporary, { recursive: true, force: true });
135
+ }
136
+
137
+ function writeEvidence(value) {
138
+ if (!evidenceFile) return;
139
+ mkdirSync(path.dirname(evidenceFile), { recursive: true });
140
+ writeFileSync(evidenceFile, `${JSON.stringify(value, null, 2)}\n`, "utf8");
141
+ }
142
+
143
+ }
144
+
145
+ async function runLocalRole() {
146
+ const evidenceFile = path.resolve(args.evidence || path.join(scriptDir, "role-evidence", `${args.role}-${Date.now()}-${randomUUID()}.json`));
147
+ const outputFile = args.output ? path.resolve(args.output) : null;
148
+ const startedAt = new Date().toISOString();
149
+ const started = Date.now();
150
+ let result;
151
+ let error;
152
+ try {
153
+ if (args.role === "controller") fail("Claude 宿主的 controller 不支持 local-cli 映射");
154
+ if (mapping?.backend !== "local-cli" || !["codex", "antigravity", "grok"].includes(mapping.cli)) {
155
+ fail("Claude 宿主执行器仅接受 codex、antigravity 或 grok 的 local-cli 映射");
156
+ }
157
+ const unsupported = ["--codex-bin", "--sandbox", "--session-id", "--closeout-ids"].filter((option) => args.supplied.has(option));
158
+ if (unsupported.length) fail(`Claude 本机 CLI 分支不支持 Codex 专有选项:${unsupported.join("、")}`);
159
+ const allowedFields = new Set(["backend", "cli", "model", "reasoning_effort", "timeoutSeconds"]);
160
+ for (const key of Object.keys(mapping)) {
161
+ if (!allowedFields.has(key)) fail(`未知本机 CLI 映射字段:${key}`);
162
+ }
163
+ const installedModule = path.join(scriptDir, "odai-local-cli.mjs");
164
+ const moduleFile = existsSync(installedModule) ? installedModule : path.join(scriptDir, "local-cli.mjs");
165
+ const { executeLocalCli, validateLocalCliRoute } = await import(pathToFileURL(moduleFile).href);
166
+ const route = validateLocalCliRoute({
167
+ backend: mapping.backend, cli: mapping.cli, model: mapping.model,
168
+ ...(mapping.reasoning_effort != null ? { reasoningEffort: mapping.reasoning_effort } : {}),
169
+ ...(mapping.timeoutSeconds !== undefined ? { timeoutSeconds: mapping.timeoutSeconds } : {}),
170
+ });
171
+ // Only an explicit flag overrides the shared executor's 1200-second default.
172
+ if (args.supplied.has("--timeout")) route.timeoutSeconds = args.timeout;
173
+ validateLocalCliRoute(route);
174
+ const input = args.input ? readFileSync(path.resolve(args.input), "utf8").trim() : readFileSync(0, "utf8").trim();
175
+ if (!input) fail("角色输入为空");
176
+ const contractFile = path.join(scriptDir, "role-contracts", `odai-${args.role}.md`);
177
+ if (!existsSync(contractFile)) fail(`缺少角色契约:${contractFile}`);
178
+ const contract = readFileSync(contractFile, "utf8");
179
+ result = await executeLocalCli({ route, prompt: `${contract}\n\n${input}`, cwd: workdir });
180
+ if (result.status !== "completed") fail(result.error || `本机 CLI 未完成:${result.status}`);
181
+ if (result.exitCode !== 0) fail(`本机 CLI 退出状态异常:${result.exitCode ?? "unknown"}`);
182
+ if (typeof result.text !== "string" || !result.text.trim()) fail("角色没有形成输出");
183
+ if (!result.actualModel || !result.modelVerified || result.modelVerification !== "verified") {
184
+ fail(`本机 CLI 模型未核实:${result.modelVerification || "missing"}(请求 ${route.model},实际 ${result.actualModel || "unknown"})`);
185
+ }
186
+ // Same gate as DSH: only adapters that can observe effort may fail on it; requested-only is recorded, not blocking.
187
+ if (result.reasoningEffortVerification === "mismatch") {
188
+ fail(`本机 CLI 推理档不一致:请求 ${route.reasoningEffort || "none"},实际 ${result.actualReasoningEffort || "unknown"}`);
189
+ }
190
+ if (outputFile) {
191
+ if (outputFile === evidenceFile) fail("--output 与 --evidence 不能使用同一路径");
192
+ mkdirSync(path.dirname(outputFile), { recursive: true });
193
+ writeFileSync(outputFile, result.text, "utf8");
194
+ }
195
+ } catch (failure) {
196
+ error = failure instanceof Error ? failure : new Error(String(failure));
197
+ }
198
+ const provider = `local-cli:${mapping?.cli || "unknown"}`;
85
199
  const evidence = {
86
200
  version: 1,
87
201
  role: args.role,
88
- requested: { provider: "codex", model: mapping.model, reasoning_effort: mapping.reasoning_effort || null },
202
+ requested: { provider, model: mapping?.model || null, reasoning_effort: mapping?.reasoning_effort ?? null },
89
203
  observed: {
90
- provider: "codex",
91
- thread_id: threadId || null,
92
- models,
93
- model_verified: models.length > 0,
94
- reasoning_efforts: runtime?.reasoning_effort ? [runtime.reasoning_effort] : [],
95
- tool_use_detected: containsToolUse(events),
96
- tool_evidence: collectToolEvidence(events),
97
- usage: lastDefined(events.map((event) => event.usage || event.payload?.usage)) || null,
98
- cost_usd: null,
99
- duration_ms: Date.now() - started,
204
+ provider,
205
+ session_id: result?.sessionId || null,
206
+ models: result?.actualModel ? [result.actualModel] : [],
207
+ model_verified: result?.modelVerified === true,
208
+ model_verification: result?.modelVerification || "missing",
209
+ reasoning_efforts: result?.actualReasoningEffort ? [result.actualReasoningEffort] : [],
210
+ reasoning_effort_verification: result?.reasoningEffortVerification || "requested-only",
211
+ usage: result?.usage ?? null,
212
+ cli_reported_cost_usd: result?.cliReportedCostUsd ?? null,
213
+ denied_actions_count: result?.deniedActionsCount ?? null,
214
+ duration_ms: result?.durationMs ?? Date.now() - started,
100
215
  started_at: startedAt,
101
216
  completed_at: new Date().toISOString(),
102
- exit_code: child.status,
217
+ exit_code: result?.exitCode ?? null,
218
+ status: result?.status || "error",
219
+ error: error?.message || result?.error || null,
103
220
  },
104
- output: outputFile,
221
+ output: error ? null : outputFile,
105
222
  };
106
- writeEvidence(evidence);
107
- const response = readFileSync(outputFile, "utf8");
108
- process.stdout.write(response);
109
- if (!response.endsWith("\n")) process.stdout.write("\n");
110
- } catch (error) {
111
- writeEvidence({
112
- version: 1,
113
- role: args.role,
114
- requested: { provider: "codex", model: mapping.model, reasoning_effort: mapping.reasoning_effort || null },
115
- observed: {
116
- provider: "codex", thread_id: null, models: [], model_verified: false,
117
- usage: null, cost_usd: null, duration_ms: Date.now() - started,
118
- started_at: startedAt, completed_at: new Date().toISOString(), exit_code: null,
119
- error: error?.message || String(error),
120
- },
121
- output: null,
122
- });
123
- throw error;
124
- } finally {
125
- rmSync(temporary, { recursive: true, force: true });
126
- }
127
-
128
- function writeEvidence(value) {
129
- if (!evidenceFile) return;
130
223
  mkdirSync(path.dirname(evidenceFile), { recursive: true });
131
- writeFileSync(evidenceFile, `${JSON.stringify(value, null, 2)}\n`, "utf8");
224
+ writeFileSync(evidenceFile, `${JSON.stringify(evidence, null, 2)}\n`, "utf8");
225
+ console.error(`odai evidence: ${evidenceFile}`);
226
+ if (error) throw error;
227
+ process.stdout.write(result.text);
228
+ if (!result.text.endsWith("\n")) process.stdout.write("\n");
132
229
  }
133
230
 
134
231
  function queryRuntime(threadId, startedAt) {
@@ -208,7 +305,7 @@ function lastDefined(values) { return [...values].reverse().find((value) => valu
208
305
  function readJson(file, label) { if (!existsSync(file)) fail(`缺少${label}:${file}`); return JSON.parse(readFileSync(file, "utf8")); }
209
306
 
210
307
  function parseArgs(values) {
211
- const result = { role: "", input: "", output: "", evidence: "", cwd: "", manifest: "", codexBin: "", sandbox: "workspace-write", sessionId: "", closeoutIds: "", timeout: 900, help: false };
308
+ const result = { role: "", input: "", output: "", evidence: "", cwd: "", manifest: "", codexBin: "", sandbox: "workspace-write", sessionId: "", closeoutIds: "", timeout: 900, help: false, supplied: new Set() };
212
309
  const fields = new Map([
213
310
  ["--role", "role"], ["--input", "input"], ["--output", "output"], ["--evidence", "evidence"], ["--cwd", "cwd"],
214
311
  ["--manifest", "manifest"], ["--codex-bin", "codexBin"], ["--sandbox", "sandbox"], ["--session-id", "sessionId"], ["--closeout-ids", "closeoutIds"], ["--timeout", "timeout"],
@@ -217,6 +314,7 @@ function parseArgs(values) {
217
314
  const value = values[index];
218
315
  if (value === "--help" || value === "-h") result.help = true;
219
316
  else if (fields.has(value)) {
317
+ result.supplied.add(value);
220
318
  const next = values[++index] || "";
221
319
  if (!next || next.startsWith("--")) fail(`${value} 需要一个值`);
222
320
  result[fields.get(value)] = fields.get(value) === "timeout" ? Number(next) : next;
@@ -9,19 +9,19 @@ import { fileURLToPath } from "node:url";
9
9
 
10
10
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
11
11
  const args = parseArgs(process.argv.slice(2));
12
- const mappings = new Map([["odai_researcher", "researcher"], ["odai_planner", "planner"], ["odai_reviewer", "reviewer"], ["odai_frontend", "frontend"]]);
12
+ const mappings = new Map([["odai_researcher", "researcher"], ["odai_planner", "planner"], ["odai_reviewer", "reviewer"], ["odai_frontend", "frontend"], ["odai_creative", "creative"]]);
13
13
 
14
14
  if (args.help) {
15
15
  console.log(`Usage:
16
16
  node odai-verify-routing.mjs --host codex --project <directory> \\
17
- --role <odai_researcher|odai_planner|odai_reviewer|odai_frontend> --after <ISO timestamp> \\
17
+ --role <odai_researcher|odai_planner|odai_reviewer|odai_frontend|odai_creative> --after <ISO timestamp> \\
18
18
  [--agent-path </root/task>] [--sessions <directory>]
19
19
 
20
20
  只读取近期 Codex rollout metadata,把实际角色、模型与推理档和 odai 托管清单对账。`);
21
21
  process.exit(0);
22
22
  }
23
23
  if (args.host !== "codex") fail(`不支持的 host:${args.host || "(missing)"}`);
24
- if (!mappings.has(args.role)) fail("--role 必须是 odai_researcher、odai_planner、odai_reviewer 或 odai_frontend");
24
+ if (!mappings.has(args.role)) fail("--role 必须是 odai_researcher、odai_planner、odai_reviewer、odai_frontend 或 odai_creative");
25
25
  if (!args.project || !args.after) fail("缺少 --project 或 --after");
26
26
  const after = Date.parse(args.after);
27
27
  if (!Number.isFinite(after)) fail(`无效的 --after:${args.after}`);