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.
- package/README.md +13 -11
- package/build/src/control-center-installer.d.mts.map +1 -1
- package/build/src/control-center-installer.mjs +5 -0
- package/build/src/control-center-installer.mjs.map +1 -1
- package/build/src/installer.d.mts.map +1 -1
- package/build/src/installer.mjs +1 -1
- package/build/src/installer.mjs.map +1 -1
- package/client/client.js +27 -6
- package/package.json +1 -1
- package/preset/odai/package.json +2 -1
- package/preset/odai/runtime/compaction-config.d.mts +2 -2
- package/preset/odai/runtime/compaction-config.d.mts.map +1 -1
- package/preset/odai/runtime/context-activation.mjs +1 -1
- package/preset/odai/runtime/context-activation.mjs.map +1 -1
- package/preset/odai/runtime/index.mjs +1 -1
- package/preset/odai/runtime/index.mjs.map +1 -1
- package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -1
- package/preset/odai/runtime/lifecycle-runtime.mjs +25 -4
- package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -1
- package/preset/odai/runtime/model-route.d.mts +5 -0
- package/preset/odai/runtime/model-route.d.mts.map +1 -1
- package/preset/odai/runtime/model-route.mjs +21 -2
- package/preset/odai/runtime/model-route.mjs.map +1 -1
- package/preset/odai/runtime/output-config.d.mts.map +1 -1
- package/preset/odai/runtime/output-config.mjs +2 -1
- package/preset/odai/runtime/output-config.mjs.map +1 -1
- package/preset/odai/runtime/role-overlays.d.mts +1 -1
- package/preset/odai/runtime/role-overlays.d.mts.map +1 -1
- package/preset/odai/runtime/role-overlays.mjs +4 -1
- package/preset/odai/runtime/role-overlays.mjs.map +1 -1
- package/preset/odai/runtime/router.d.mts +5 -1
- package/preset/odai/runtime/router.d.mts.map +1 -1
- package/preset/odai/runtime/router.mjs +47 -2
- package/preset/odai/runtime/router.mjs.map +1 -1
- package/preset/odai/runtime/routing-config.d.mts +5 -2
- package/preset/odai/runtime/routing-config.d.mts.map +1 -1
- package/preset/odai/runtime/routing-config.mjs +87 -42
- package/preset/odai/runtime/routing-config.mjs.map +1 -1
- package/preset/odai/runtime/runtime-config.d.mts +1 -1
- package/preset/odai/runtime/runtime-config.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-config.mjs +15 -9
- package/preset/odai/runtime/runtime-config.mjs.map +1 -1
- package/preset/odai/runtime/runtime-support.d.mts +6 -1
- package/preset/odai/runtime/runtime-support.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-support.mjs +90 -7
- package/preset/odai/runtime/runtime-support.mjs.map +1 -1
- package/preset/odai/runtime/runtime-types.d.mts +8 -1
- package/preset/odai/runtime/runtime-types.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-types.mjs.map +1 -1
- package/preset/odai/runtime/skill-bundle.d.mts +1 -1
- package/preset/odai/runtime/skill-bundle.d.mts.map +1 -1
- package/preset/odai/runtime/work-policy.d.mts.map +1 -1
- package/preset/odai/runtime/work-policy.mjs +8 -2
- package/preset/odai/runtime/work-policy.mjs.map +1 -1
- package/preset/odai/skills/odai-orchestration/SKILL.md +1 -1
- package/preset/odai/skills/odai-orchestration/assets/codex-agents/role.toml +1 -0
- package/preset/odai/skills/odai-orchestration/assets/routing-roles/controller.md +1 -1
- package/preset/odai/skills/odai-orchestration/assets/routing-roles/creative.md +9 -0
- package/preset/odai/skills/odai-orchestration/manifest.json +9 -4
- package/preset/odai/skills/odai-orchestration/references/install.md +31 -1
- package/preset/odai/skills/odai-orchestration/references/orchestration.md +1 -0
- package/preset/odai/skills/odai-orchestration/scripts/build-routing.mjs +78 -17
- package/preset/odai/skills/odai-orchestration/scripts/compose-contracts.d.mts +1 -1
- package/preset/odai/skills/odai-orchestration/scripts/compose-contracts.mjs +2 -2
- package/preset/odai/skills/odai-orchestration/scripts/install-routing.mjs +101 -23
- package/preset/odai/skills/odai-orchestration/scripts/local-cli.d.mts +75 -0
- package/preset/odai/skills/odai-orchestration/scripts/local-cli.mjs +409 -0
- package/preset/odai/skills/odai-orchestration/scripts/run-role.mjs +190 -92
- package/preset/odai/skills/odai-orchestration/scripts/verify-routing.mjs +3 -3
|
@@ -20,12 +20,13 @@ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
|
20
20
|
const builder = path.join(scriptDir, "build-routing.mjs");
|
|
21
21
|
const manifestName = "odai-routing.json";
|
|
22
22
|
const requiredRoles = ["controller", "planner", "reviewer"];
|
|
23
|
-
const configurableRoles = [...requiredRoles, "researcher", "frontend"];
|
|
23
|
+
const configurableRoles = [...requiredRoles, "researcher", "frontend", "creative"];
|
|
24
24
|
const args = parseArgs(process.argv.slice(2));
|
|
25
25
|
const roles = Object.freeze([
|
|
26
26
|
...requiredRoles,
|
|
27
27
|
...(args.researcherModel ? ["researcher"] : []),
|
|
28
28
|
...(args.frontendModel ? ["frontend"] : []),
|
|
29
|
+
...(args.creativeModel ? ["creative"] : []),
|
|
29
30
|
]);
|
|
30
31
|
const retiredByHost = {
|
|
31
32
|
codex: [
|
|
@@ -41,6 +42,12 @@ const retiredByHost = {
|
|
|
41
42
|
if (args.help) { printHelp(); process.exit(0); }
|
|
42
43
|
if (!new Set(["codex", "claude", "copilot"]).has(args.host)) fail(`不支持的 host:${args.host || "(missing)"}`);
|
|
43
44
|
if (!new Set(["project", "user"]).has(args.scope)) fail(`不支持的 scope:${args.scope}`);
|
|
45
|
+
for (const role of configurableRoles) {
|
|
46
|
+
if (!args[`${role}Cli`]) continue;
|
|
47
|
+
if (args.host !== "claude") fail(`--${role}-cli is only supported with --host claude`);
|
|
48
|
+
if (!["codex", "antigravity", "grok"].includes(args[`${role}Cli`])) fail(`--${role}-cli must be codex, antigravity, or grok`);
|
|
49
|
+
if (!args[`${role}Model`]) fail(`--${role}-cli 需要同时提供 --${role}-model`);
|
|
50
|
+
}
|
|
44
51
|
if (!args.yes) fail("安装、更新或卸载会持久化宿主配置;取得用户授权后传入 --yes");
|
|
45
52
|
|
|
46
53
|
const targetRoot = resolveTarget(args.host, args.scope, args.target);
|
|
@@ -65,6 +72,7 @@ function main() {
|
|
|
65
72
|
for (const role of requiredRoles) if (!args[`${role}Model`]) fail(`缺少 --${role}-model`);
|
|
66
73
|
if (args.researcherEffort && !args.researcherModel) fail("--researcher-effort 需要同时提供 --researcher-model");
|
|
67
74
|
if (args.frontendEffort && !args.frontendModel) fail("--frontend-effort 需要同时提供 --frontend-model");
|
|
75
|
+
if (args.creativeEffort && !args.creativeModel) fail("--creative-effort 需要同时提供 --creative-model");
|
|
68
76
|
if (!existsSync(builder)) fail(`缺少路由生成器:${builder}`);
|
|
69
77
|
|
|
70
78
|
const generatedRoot = mkdtempSync(path.join(tmpdir(), `odai-${args.host}-routing-install-`));
|
|
@@ -124,6 +132,7 @@ function buildAdapter(outputRoot, root) {
|
|
|
124
132
|
for (const role of roles) {
|
|
125
133
|
command.push(`--${role}-model`, args[`${role}Model`]);
|
|
126
134
|
if (args[`${role}Effort`]) command.push(`--${role}-effort`, args[`${role}Effort`]);
|
|
135
|
+
if (args[`${role}Cli`]) command.push(`--${role}-cli`, args[`${role}Cli`]);
|
|
127
136
|
}
|
|
128
137
|
if (args.host === "codex") command.push("--verifier-command", `node ${JSON.stringify(path.join(root, "odai-verify-routing.mjs"))}`);
|
|
129
138
|
const result = spawnSync(process.execPath, command, { encoding: "utf8", windowsHide: true });
|
|
@@ -138,7 +147,11 @@ function collectGeneratedFiles(root, layoutValue) {
|
|
|
138
147
|
for (const [sourceRelative, targetRelative] of layoutValue.generatedFiles) {
|
|
139
148
|
const source = path.join(root, args.host, sourceRelative);
|
|
140
149
|
if (!existsSync(source)) fail(`生成结果缺少文件:${sourceRelative}`);
|
|
141
|
-
|
|
150
|
+
let content = readFileSync(source);
|
|
151
|
+
if (args.host === "claude" && args.scope === "user" && targetRelative === "agents/odai-controller.md") {
|
|
152
|
+
content = Buffer.from(content.toString("utf8").replaceAll("node .claude/odai-run-role.mjs", claudeRunnerCommand()));
|
|
153
|
+
}
|
|
154
|
+
result.set(targetRelative, content);
|
|
142
155
|
}
|
|
143
156
|
return result;
|
|
144
157
|
}
|
|
@@ -163,7 +176,7 @@ function originalFileRecord(file) {
|
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
function mergeCodexConfig(original, generated) {
|
|
166
|
-
if (/^\s*\[agents\.odai_(?:researcher|planner|executor|reviewer|frontend)\]\s*$/m.test(original)) {
|
|
179
|
+
if (/^\s*\[agents\.odai_(?:researcher|planner|executor|reviewer|frontend|creative)\]\s*$/m.test(original)) {
|
|
167
180
|
fail("既有 Codex 配置已声明 odai 角色但不受当前安装清单管理");
|
|
168
181
|
}
|
|
169
182
|
const generatedFeatures = generated.search(/^\s*\[features\]\s*$/m);
|
|
@@ -245,6 +258,11 @@ function hostLayout(host, scope) {
|
|
|
245
258
|
[".codex/role-contracts/odai-frontend.md", "role-contracts/odai-frontend.md"],
|
|
246
259
|
];
|
|
247
260
|
const frontend = roles.includes("frontend") ? frontendFiles : [];
|
|
261
|
+
const creativeFiles = [
|
|
262
|
+
[".codex/agents/odai-creative.toml", "agents/odai-creative.toml"],
|
|
263
|
+
[".codex/role-contracts/odai-creative.md", "role-contracts/odai-creative.md"],
|
|
264
|
+
];
|
|
265
|
+
const creative = roles.includes("creative") ? creativeFiles : [];
|
|
248
266
|
const common = [
|
|
249
267
|
[".codex/config.toml", "config.toml"],
|
|
250
268
|
[".codex/odai-run-role.mjs", "odai-run-role.mjs"], [".codex/odai-verify-routing.mjs", "odai-verify-routing.mjs"],
|
|
@@ -255,21 +273,30 @@ function hostLayout(host, scope) {
|
|
|
255
273
|
[".codex/role-contracts/odai-reviewer.md", "role-contracts/odai-reviewer.md"],
|
|
256
274
|
...researcher,
|
|
257
275
|
...frontend,
|
|
276
|
+
...creative,
|
|
258
277
|
];
|
|
259
278
|
const generatedFiles = common;
|
|
260
279
|
return {
|
|
261
|
-
knownFiles: [...new Set([...common, ...researcherFiles, ...frontendFiles].map(([, target]) => target)), "hooks.json", "odai-route-hook.mjs", "odai-run-routing.mjs", "agents/odai-executor.toml", "role-contracts/odai-executor.md"],
|
|
280
|
+
knownFiles: [...new Set([...common, ...researcherFiles, ...frontendFiles, ...creativeFiles].map(([, target]) => target)), "hooks.json", "odai-route-hook.mjs", "odai-run-routing.mjs", "agents/odai-executor.toml", "role-contracts/odai-executor.md"],
|
|
262
281
|
managedFiles: generatedFiles.map(([, target]) => target),
|
|
263
282
|
generatedFiles,
|
|
264
283
|
settings: null,
|
|
265
284
|
};
|
|
266
285
|
}
|
|
267
|
-
if (host === "claude")
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
if (host === "claude") {
|
|
287
|
+
const executors = ["odai-run-role.mjs", "odai-local-cli.mjs"];
|
|
288
|
+
const local = roles.filter(role => args[`${role}Cli`]);
|
|
289
|
+
const managedFiles = roles.map(role => `${args[`${role}Cli`] ? "role-contracts" : "agents"}/odai-${role}.md`);
|
|
290
|
+
if (local.length) managedFiles.push(...executors);
|
|
291
|
+
return {
|
|
292
|
+
knownFiles: [...configurableRoles.map(role => `agents/odai-${role}.md`),
|
|
293
|
+
...configurableRoles.filter(role => role !== "controller").map(role => `role-contracts/odai-${role}.md`), ...executors],
|
|
294
|
+
managedFiles,
|
|
295
|
+
generatedFiles: managedFiles.map(file => [`.claude/${file}`, file]),
|
|
296
|
+
settings: { file: scope === "project" ? "settings.local.json" : "settings.json", key: "agent", value: "odai-controller",
|
|
297
|
+
permission: local.length ? `Bash(${claudeRunnerCommand()}:*)` : null },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
273
300
|
return {
|
|
274
301
|
knownFiles: configurableRoles.map((role) => `agents/odai-${role}.agent.md`),
|
|
275
302
|
managedFiles: roles.map((role) => `agents/odai-${role}.agent.md`),
|
|
@@ -283,22 +310,67 @@ function planSettings(root, layoutValue, manifest) {
|
|
|
283
310
|
const spec = layoutValue.settings;
|
|
284
311
|
const file = path.join(root, spec.file);
|
|
285
312
|
const current = readJsonObject(file);
|
|
313
|
+
let installed = manifest?.settings;
|
|
286
314
|
if (manifest) {
|
|
287
|
-
const installed = manifest.settings;
|
|
288
315
|
if (!installed || installed.file !== spec.file || installed.key !== spec.key || installed.installed !== spec.value) fail("旧路由缺少可恢复的宿主设置记录");
|
|
289
316
|
if (current[spec.key] !== spec.value) fail(`宿主设置 ${spec.key} 已被外部修改`);
|
|
290
|
-
|
|
317
|
+
restorePermission(current, installed.permission);
|
|
318
|
+
} else {
|
|
319
|
+
const present = Object.hasOwn(current, spec.key);
|
|
320
|
+
if (present && current[spec.key] !== spec.value) fail(`目标已有非 odai 管理的宿主设置 ${spec.key}`);
|
|
321
|
+
installed = { file: spec.file, key: spec.key, installed: spec.value, fileExistedBefore: existsSync(file), previous: { present, value: present ? current[spec.key] : null } };
|
|
291
322
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
323
|
+
const record = { ...installed };
|
|
324
|
+
delete record.permission;
|
|
325
|
+
if (spec.permission) {
|
|
326
|
+
const permissionsPresent = Object.hasOwn(current, "permissions");
|
|
327
|
+
if (permissionsPresent && !isObject(current.permissions)) fail("宿主 permissions 必须是对象");
|
|
328
|
+
const permissions = current.permissions || {};
|
|
329
|
+
const allowPresent = Object.hasOwn(permissions, "allow");
|
|
330
|
+
if (allowPresent && (!Array.isArray(permissions.allow) || permissions.allow.some(rule => typeof rule !== "string"))) fail("宿主 permissions.allow 必须是字符串数组");
|
|
331
|
+
const allow = permissions.allow || [];
|
|
332
|
+
record.permission = { rule: spec.permission, added: !allow.includes(spec.permission), permissionsPresent, allowPresent };
|
|
333
|
+
current.permissions = { ...permissions, allow: record.permission.added ? [...allow, spec.permission] : [...allow] };
|
|
334
|
+
}
|
|
335
|
+
current[spec.key] = spec.value;
|
|
336
|
+
return { file: spec.file, value: current, manifest: record };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
340
|
+
|
|
341
|
+
function claudeRunnerCommand() {
|
|
342
|
+
return args.scope === "project" ? "node .claude/odai-run-role.mjs"
|
|
343
|
+
: `node '${path.join(configRoot, "odai-run-role.mjs").replaceAll("'", "'\\''")}'`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function assertPermission(current, record) {
|
|
347
|
+
if (!record) return;
|
|
348
|
+
if (record.rule !== `Bash(${claudeRunnerCommand()}:*)` || typeof record.added !== "boolean"
|
|
349
|
+
|| typeof record.permissionsPresent !== "boolean" || typeof record.allowPresent !== "boolean") fail("旧路由的权限恢复记录无效");
|
|
350
|
+
if (!isObject(current.permissions) || !Array.isArray(current.permissions.allow)
|
|
351
|
+
|| !current.permissions.allow.includes(record.rule)) fail("宿主设置 permissions.allow 已被外部修改");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function restorePermission(current, record) {
|
|
355
|
+
if (!record) return;
|
|
356
|
+
assertPermission(current, record);
|
|
357
|
+
if (!record.added) return;
|
|
358
|
+
current.permissions.allow = current.permissions.allow.filter(rule => rule !== record.rule);
|
|
359
|
+
if (!record.allowPresent && current.permissions.allow.length === 0) delete current.permissions.allow;
|
|
360
|
+
if (!record.permissionsPresent && Object.keys(current.permissions).length === 0) delete current.permissions;
|
|
299
361
|
}
|
|
300
362
|
|
|
301
363
|
function assertManagedState(root, layoutValue, manifest) {
|
|
364
|
+
if (args.host === "claude") {
|
|
365
|
+
const localRoles = new Set([
|
|
366
|
+
...roles.filter(role => args[`${role}Cli`]),
|
|
367
|
+
...Object.entries(manifest?.mapping || {}).filter(([, route]) => route.backend === "local-cli").map(([role]) => role),
|
|
368
|
+
]);
|
|
369
|
+
for (const role of configurableRoles.filter(role => localRoles.has(role))) {
|
|
370
|
+
const nativeFile = `agents/odai-${role}.md`;
|
|
371
|
+
if (existsSync(path.join(root, nativeFile)) && !manifest?.files?.[nativeFile]) fail(`本机 CLI 职责存在非 odai 托管的原生代理:${nativeFile}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
302
374
|
if (!manifest) {
|
|
303
375
|
const mergeable = args.host === "codex" ? new Set(["config.toml"]) : new Set();
|
|
304
376
|
const conflicts = layoutValue.managedFiles.filter((relative) => !mergeable.has(relative) && existsSync(path.join(root, relative)));
|
|
@@ -316,6 +388,7 @@ function assertManagedState(root, layoutValue, manifest) {
|
|
|
316
388
|
|| installedSetting.installed !== expectedSetting.value) {
|
|
317
389
|
fail("旧路由缺少可恢复的宿主设置记录");
|
|
318
390
|
}
|
|
391
|
+
assertPermission(readJsonObject(path.join(root, installedSetting.file)), installedSetting.permission);
|
|
319
392
|
} else if (installedSetting) {
|
|
320
393
|
fail("旧路由包含当前宿主不支持的设置记录");
|
|
321
394
|
}
|
|
@@ -388,6 +461,7 @@ function uninstall(root, manifestPathValue, manifest) {
|
|
|
388
461
|
function planSettingRestore(root, spec) {
|
|
389
462
|
const current = readJsonObject(path.join(root, spec.file));
|
|
390
463
|
if (current[spec.key] !== spec.installed) fail(`宿主设置 ${spec.key} 已被外部修改,拒绝删除`);
|
|
464
|
+
restorePermission(current, spec.permission);
|
|
391
465
|
if (spec.previous?.present) current[spec.key] = spec.previous.value; else delete current[spec.key];
|
|
392
466
|
return {
|
|
393
467
|
relative: spec.file,
|
|
@@ -466,7 +540,10 @@ function parseArgs(values) {
|
|
|
466
540
|
const result = { host: "", scope: "project", target: "", uninstall: false, yes: false, help: false };
|
|
467
541
|
for (const role of configurableRoles) { result[`${role}Model`] = ""; result[`${role}Effort`] = ""; }
|
|
468
542
|
const fields = new Map([["--host", "host"], ["--scope", "scope"], ["--target", "target"], ["--governance-root", "governanceRoot"]]);
|
|
469
|
-
for (const role of configurableRoles) {
|
|
543
|
+
for (const role of configurableRoles) {
|
|
544
|
+
fields.set(`--${role}-model`, `${role}Model`); fields.set(`--${role}-effort`, `${role}Effort`);
|
|
545
|
+
if (role !== "controller") fields.set(`--${role}-cli`, `${role}Cli`);
|
|
546
|
+
}
|
|
470
547
|
for (let index = 0; index < values.length; index += 1) {
|
|
471
548
|
const value = values[index];
|
|
472
549
|
if (value === "--help" || value === "-h") result.help = true;
|
|
@@ -478,18 +555,19 @@ function parseArgs(values) {
|
|
|
478
555
|
return result;
|
|
479
556
|
}
|
|
480
557
|
|
|
481
|
-
function hasMappingArgs(value) { return configurableRoles.some((role) => value[`${role}Model`] || value[`${role}Effort`]); }
|
|
558
|
+
function hasMappingArgs(value) { return configurableRoles.some((role) => value[`${role}Model`] || value[`${role}Effort`] || value[`${role}Cli`]); }
|
|
482
559
|
function printHelp() {
|
|
483
560
|
console.log(`由 odai 在取得用户授权后安装、更新或卸载宿主路由。
|
|
484
561
|
|
|
485
562
|
Usage:
|
|
486
563
|
node skills/odai-orchestration/scripts/install-routing.mjs --host <codex|claude|copilot> --scope <project|user> [--target <path>] \\
|
|
487
564
|
--controller-model <model> --planner-model <model> --reviewer-model <model> \\
|
|
488
|
-
[--researcher-model <model>] [--frontend-model <model>] [--<role>-effort <effort>] [--governance-root <path>] --yes
|
|
565
|
+
[--researcher-model <model>] [--frontend-model <model>] [--creative-model <model>] [--<role>-effort <effort>] [--governance-root <path>] --yes
|
|
566
|
+
Claude Code only: [--<role>-cli <codex|antigravity|grok>] (non-controller roles)
|
|
489
567
|
|
|
490
568
|
node skills/odai-orchestration/scripts/install-routing.mjs --host <codex|claude|copilot> --scope <project|user> [--target <path>] --uninstall --yes
|
|
491
569
|
|
|
492
|
-
安装后用户只需正常使用 odai。总控是唯一持续任务线程并负责实施整合;planner、reviewer 以及可选 researcher、frontend 只在能改变结果时启动。researcher 与
|
|
570
|
+
安装后用户只需正常使用 odai。总控是唯一持续任务线程并负责实施整合;planner、reviewer 以及可选 researcher、frontend、creative 只在能改变结果时启动。researcher、frontend 与 creative 映射默认不配置。更新会安全移除旧版 advisor、implementer、worker、executor 和 stage runner 托管文件。`);
|
|
493
571
|
}
|
|
494
572
|
|
|
495
573
|
function returnResult(value) { process.stdout.write(`${JSON.stringify(value, null, 2)}\n`); }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export interface LocalCliRoute {
|
|
4
|
+
backend: "local-cli";
|
|
5
|
+
cli: "claude" | "codex" | "antigravity" | "grok";
|
|
6
|
+
model: string;
|
|
7
|
+
/** Validated by the selected CLI adapter. */
|
|
8
|
+
reasoningEffort?: string;
|
|
9
|
+
timeoutSeconds?: number;
|
|
10
|
+
provider?: never;
|
|
11
|
+
maxTokens?: never;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LocalCliInvocation {
|
|
15
|
+
command: string;
|
|
16
|
+
args: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LocalCliParsedResult {
|
|
20
|
+
status: "completed" | "error";
|
|
21
|
+
text: string;
|
|
22
|
+
actualModel?: string;
|
|
23
|
+
modelVerified: boolean;
|
|
24
|
+
/** Unverified successful results stay completed so the host can choose its policy. */
|
|
25
|
+
modelVerification: "verified" | "mismatch" | "missing";
|
|
26
|
+
error?: string;
|
|
27
|
+
usage?: Record<string, unknown>;
|
|
28
|
+
/** Claude's reported API-equivalent cost; not evidence of a subscription charge. */
|
|
29
|
+
cliReportedCostUsd?: number;
|
|
30
|
+
reasoningEffortVerification: "verified" | "mismatch" | "requested-only";
|
|
31
|
+
actualReasoningEffort?: string;
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
deniedActionsCount?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LocalCliExecutionResult extends Omit<LocalCliParsedResult, "status"> {
|
|
37
|
+
status: "completed" | "error" | "timeout" | "cancelled";
|
|
38
|
+
durationMs: number;
|
|
39
|
+
/** Actual process exit code, including zero on parsed-output failure; null if unavailable. */
|
|
40
|
+
exitCode: number | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ExecuteLocalCliOptions {
|
|
44
|
+
route: LocalCliRoute;
|
|
45
|
+
prompt: string;
|
|
46
|
+
cwd: string;
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
env?: NodeJS.ProcessEnv;
|
|
49
|
+
/** Test seam. Injected spawns use child.kill unless killProcess is also supplied. */
|
|
50
|
+
spawnProcess?: typeof spawn;
|
|
51
|
+
/** POSIX signal seam, called with a negative process-group ID; defaults to process.kill for native spawn. */
|
|
52
|
+
killProcess?: (pid: number, signal: "SIGTERM" | "SIGKILL") => unknown;
|
|
53
|
+
/** Time between SIGTERM and SIGKILL, default 1000 milliseconds. */
|
|
54
|
+
killGraceMs?: number;
|
|
55
|
+
/** Maximum stdout bytes, default 16 MiB. Stderr retains only its last 8 KiB. */
|
|
56
|
+
maxOutputBytes?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface LocalCliContext {
|
|
60
|
+
cwd?: string;
|
|
61
|
+
prompt?: string;
|
|
62
|
+
promptFile?: string;
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Throws TypeError for invalid routes; preserves omitted timeoutSeconds. */
|
|
67
|
+
export function validateLocalCliRoute(value: unknown): LocalCliRoute;
|
|
68
|
+
export function buildLocalCliInvocation(route: LocalCliRoute, env?: NodeJS.ProcessEnv, context?: LocalCliContext): LocalCliInvocation;
|
|
69
|
+
export function parseLocalCliResult(stdout: string, requestedRoute: string | LocalCliRoute, context?: LocalCliContext): LocalCliParsedResult;
|
|
70
|
+
/**
|
|
71
|
+
* Input validation rejects; process, parse, timeout and cancellation failures return a result.
|
|
72
|
+
* Native POSIX execution terminates the entire process group. Windows only terminates
|
|
73
|
+
* the direct child; process-tree cleanup on Windows is not verified.
|
|
74
|
+
*/
|
|
75
|
+
export function executeLocalCli(options: ExecuteLocalCliOptions): Promise<LocalCliExecutionResult>;
|