orchestrator-workflow 0.27.0 → 0.29.0
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/CHANGELOG.md +57 -0
- package/INSTALL-AGENT.md +85 -58
- package/README.md +149 -53
- package/assets/agents/explorer.md +4 -3
- package/assets/agents/implementer.md +4 -0
- package/assets/agents/reviewer.md +4 -0
- package/assets/agents-md-section.md +16 -4
- package/assets/skill/SKILL.md +21 -9
- package/dist/cli-apply.d.ts +2 -0
- package/dist/cli-init.d.ts +20 -0
- package/dist/cli-init.js +29 -0
- package/dist/cli-inputs.d.ts +32 -35
- package/dist/cli-inputs.js +80 -67
- package/dist/cli.js +116 -15
- package/dist/codex.d.ts +8 -0
- package/dist/codex.js +52 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +29 -5
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/init.d.ts +34 -7
- package/dist/init.js +128 -28
- package/dist/operator-manifest.d.ts +5 -1
- package/dist/operator-manifest.js +33 -1
- package/dist/routing-state.d.ts +60 -0
- package/dist/routing-state.js +277 -0
- package/dist/routing.d.ts +103 -0
- package/dist/routing.js +254 -0
- package/dist/uninstall.js +2 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -5,10 +5,13 @@ import { Command } from "commander";
|
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import { PACKAGE_VERSION } from "./assets.js";
|
|
7
7
|
import { buildApplyInitInputs } from "./cli-apply.js";
|
|
8
|
+
import { buildInitInitInputs } from "./cli-init.js";
|
|
8
9
|
import { resolveInitInputs } from "./cli-inputs.js";
|
|
9
10
|
import { HARNESSES, detectHarnesses } from "./detect.js";
|
|
10
11
|
import { DEFAULT_MODELS, PROFILES } from "./models.js";
|
|
11
12
|
import { MANIFEST_PATH, readInstalledManifest, runInit } from "./init.js";
|
|
13
|
+
import { parseRouting } from "./routing.js";
|
|
14
|
+
import { codexCatalogWarnings, legacyOpencodeFallbacks, parseOpencodeModelMaps, mergeRoutingStateLayers, } from "./routing-state.js";
|
|
12
15
|
import { OPERATOR_MANIFEST_FILENAME, OperatorManifestLockTimeoutError, applyRegistrationFailureMessage, createOperatorManifest, operatorManifestState, readOperatorManifest, resolveOperatorHome, safeRealpath, updateOperatorManifest, upsertOperatorTarget, } from "./operator-manifest.js";
|
|
13
16
|
import { runUninstall } from "./uninstall.js";
|
|
14
17
|
import { adoptExitCodeForStatus, adoptJsonExtras, inspectTarget, runDoctor, statOrClassify, suppressSuccessLine, targetReportToJson, } from "./doctor.js";
|
|
@@ -42,6 +45,20 @@ function requireDirectory(dir) {
|
|
|
42
45
|
}
|
|
43
46
|
return targetDir;
|
|
44
47
|
}
|
|
48
|
+
function readJsonOption(path, option) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(resolve(path), "utf8"));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
54
|
+
throw new Error(`Could not read ${option} JSON file ${path}: ${reason}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function routingOption(path) {
|
|
58
|
+
return path === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: parseRouting(readJsonOption(path, "--routing"));
|
|
61
|
+
}
|
|
45
62
|
/**
|
|
46
63
|
* `resolveInitInputs` was extracted from `init` and typed against `init`'s
|
|
47
64
|
* per-repo `Manifest` (kit/version/files/installedAt included), since that
|
|
@@ -63,6 +80,8 @@ function defaultsAsManifest(defaults) {
|
|
|
63
80
|
models: { ...DEFAULT_MODELS, ...defaults.models },
|
|
64
81
|
profile: defaults.profile,
|
|
65
82
|
tiers: defaults.tiers,
|
|
83
|
+
routing: defaults.routing,
|
|
84
|
+
...parseOpencodeModelMaps(defaults),
|
|
66
85
|
files: {},
|
|
67
86
|
installedAt: "",
|
|
68
87
|
};
|
|
@@ -79,6 +98,8 @@ function defaultsEqual(a, b) {
|
|
|
79
98
|
profile: d.profile,
|
|
80
99
|
tiers: d.tiers,
|
|
81
100
|
models: Object.fromEntries(Object.entries(d.models).sort(([x], [y]) => x.localeCompare(y))),
|
|
101
|
+
routing: d.routing ?? {},
|
|
102
|
+
...parseOpencodeModelMaps(d),
|
|
82
103
|
});
|
|
83
104
|
return normalize(a) === normalize(b);
|
|
84
105
|
}
|
|
@@ -95,6 +116,8 @@ program
|
|
|
95
116
|
.option("-f, --force", "overwrite kit-owned files that have local edits")
|
|
96
117
|
.option("--harness <list>", `comma-separated harnesses (${HARNESSES.join(", ")}), or "none" alone for templates-only mode (.ai/workflow/** and .ai/runs/.gitkeep only, no AGENTS.md/CLAUDE.md/harness files); default: detected`)
|
|
97
118
|
.option("--models <spec>", 'per-role model overrides, e.g. "implementer=sonnet,reviewer=opus"')
|
|
119
|
+
.option("--routing <json-file>", "harness/role/tier routing patch JSON")
|
|
120
|
+
.option("--codex-catalog <json-file>", "optional offline Codex capability catalog JSON to validate before writing")
|
|
98
121
|
.option("--profile <profile>", `subagent role profile (${PROFILES.join(", ")}); default: full, or the previously installed profile on a re-run`)
|
|
99
122
|
.option("--opencode-provider <id>", "opencode provider id for alias resolution (e.g. github-copilot); auto-detected when omitted")
|
|
100
123
|
.option("--tiers", "also render per-role effort-tier subagent variants (<role>-<tier>.md); default: off, or the previously installed value on a re-run")
|
|
@@ -104,6 +127,19 @@ program
|
|
|
104
127
|
if (!targetDir)
|
|
105
128
|
return;
|
|
106
129
|
const interactive = !opts.yes && isInteractive();
|
|
130
|
+
let routing;
|
|
131
|
+
let codexCatalog;
|
|
132
|
+
try {
|
|
133
|
+
routing = routingOption(opts.routing);
|
|
134
|
+
codexCatalog = opts.codexCatalog
|
|
135
|
+
? readJsonOption(opts.codexCatalog, "--codex-catalog")
|
|
136
|
+
: undefined;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error(error instanceof Error ? error.message : error);
|
|
140
|
+
process.exitCode = 2;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
107
143
|
// Say where files will land BEFORE anything is written; an accidental
|
|
108
144
|
// cwd (e.g. $HOME) is the most likely operator mistake.
|
|
109
145
|
console.log(`Installing into ${targetDir}`);
|
|
@@ -124,17 +160,16 @@ program
|
|
|
124
160
|
: "none recorded";
|
|
125
161
|
console.log(`Found existing install (${version.startsWith("unknown") ? version : `v${version}`}, harnesses: ${installedFor}, profile: ${previous.profile}, tiers: ${previous.tiers})`);
|
|
126
162
|
}
|
|
127
|
-
const { harnesses, profile, models, tiers, opencodeModels, opencodeClassModels, warnings, } = await resolveInitInputs(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
163
|
+
const { harnesses, profile, models, tiers, opencodeModels, opencodeClassModels, routing: resolvedRouting, warnings, } = await resolveInitInputs(
|
|
164
|
+
// The sticky-branch wiring itself (neither `stickyPreChecked` nor
|
|
165
|
+
// `stickyAnnotateDetected` overridden, so `resolveInitInputs`'s own
|
|
166
|
+
// `?? []` / `?? detected` defaults apply) is pinned inside
|
|
167
|
+
// `buildInitInitInputs` and covered by a dedicated test
|
|
168
|
+
// (`test/cli-init.test.ts`), not by this call site.
|
|
169
|
+
buildInitInitInputs(detected, previous, interactive, {
|
|
170
|
+
...opts,
|
|
171
|
+
routing,
|
|
172
|
+
}));
|
|
138
173
|
for (const w of warnings) {
|
|
139
174
|
process.stderr.write(`${w}\n`);
|
|
140
175
|
}
|
|
@@ -147,6 +182,9 @@ program
|
|
|
147
182
|
opencodeModels,
|
|
148
183
|
tiers,
|
|
149
184
|
opencodeClassModels,
|
|
185
|
+
routing: resolvedRouting,
|
|
186
|
+
routingMode: "replace",
|
|
187
|
+
codexCatalog,
|
|
150
188
|
});
|
|
151
189
|
showPaths("Created", report.written);
|
|
152
190
|
showPaths("Updated", report.updated);
|
|
@@ -162,6 +200,8 @@ program
|
|
|
162
200
|
.option("-y, --yes", "accept all defaults and skip prompts")
|
|
163
201
|
.option("--harness <list>", `comma-separated harnesses (${HARNESSES.join(", ")}); default: previously stored, or claude`)
|
|
164
202
|
.option("--models <spec>", 'per-role model overrides, e.g. "implementer=sonnet,reviewer=opus"')
|
|
203
|
+
.option("--routing <json-file>", "harness/role/tier routing patch JSON")
|
|
204
|
+
.option("--codex-catalog <json-file>", "optional offline Codex capability catalog JSON to validate before saving")
|
|
165
205
|
.option("--profile <profile>", `subagent role profile (${PROFILES.join(", ")}); default: full, or the previously stored profile on a re-run`)
|
|
166
206
|
.option("--opencode-provider <id>", "opencode provider id for alias resolution (e.g. github-copilot); auto-detected when omitted")
|
|
167
207
|
.option("--tiers", "select per-role effort-tier subagent variants by default; default: off, or the previously stored value on a re-run")
|
|
@@ -169,6 +209,19 @@ program
|
|
|
169
209
|
.action(async (opts) => {
|
|
170
210
|
const interactive = !opts.yes && isInteractive();
|
|
171
211
|
const home = resolveOperatorHome();
|
|
212
|
+
let routingPatch;
|
|
213
|
+
let codexCatalog;
|
|
214
|
+
try {
|
|
215
|
+
routingPatch = routingOption(opts.routing);
|
|
216
|
+
codexCatalog = opts.codexCatalog
|
|
217
|
+
? readJsonOption(opts.codexCatalog, "--codex-catalog")
|
|
218
|
+
: undefined;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
console.error(error instanceof Error ? error.message : error);
|
|
222
|
+
process.exitCode = 2;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
172
225
|
console.log(`Operator home: ${home}`);
|
|
173
226
|
const existing = readOperatorManifest(home);
|
|
174
227
|
if (existing) {
|
|
@@ -180,7 +233,7 @@ program
|
|
|
180
233
|
// No target repository exists to detect harnesses against; "claude"
|
|
181
234
|
// is the same shipped fallback `init` uses for a fresh install with
|
|
182
235
|
// nothing detected and nothing previously recorded.
|
|
183
|
-
const { harnesses, profile, models, tiers, warnings } = await resolveInitInputs({
|
|
236
|
+
const { harnesses, profile, models, tiers, routing, warnings, opencodeModels, opencodeClassModels, } = await resolveInitInputs({
|
|
184
237
|
// No target directory exists to detect against: the stored
|
|
185
238
|
// harnesses are the baseline, and only a first-ever setup falls
|
|
186
239
|
// back to claude, so a codex-only default is not widened silently.
|
|
@@ -189,16 +242,30 @@ program
|
|
|
189
242
|
: ["claude"],
|
|
190
243
|
interactive,
|
|
191
244
|
previous: defaultsAsManifest(existing?.defaults),
|
|
192
|
-
opts,
|
|
245
|
+
opts: { ...opts, routing: routingPatch },
|
|
193
246
|
});
|
|
194
247
|
for (const w of warnings) {
|
|
195
248
|
process.stderr.write(`${w}\n`);
|
|
196
249
|
}
|
|
250
|
+
{
|
|
251
|
+
try {
|
|
252
|
+
for (const warning of codexCatalogWarnings(routing, { harnesses, profile, tiers }, codexCatalog)) {
|
|
253
|
+
process.stderr.write(`Warning: ${warning}\n`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error(error instanceof Error ? error.message : error);
|
|
258
|
+
process.exitCode = 2;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
197
262
|
const newDefaults = {
|
|
198
263
|
harnesses,
|
|
199
264
|
profile,
|
|
200
265
|
tiers,
|
|
201
266
|
models,
|
|
267
|
+
routing,
|
|
268
|
+
...legacyOpencodeFallbacks({ opencodeModels, opencodeClassModels }),
|
|
202
269
|
};
|
|
203
270
|
// The prompts and resolution above ran against `existing`, an
|
|
204
271
|
// unlocked read taken before this point; another process (an
|
|
@@ -413,6 +480,7 @@ function buildApplyPrevious(repoManifest, operatorDefaults, sync) {
|
|
|
413
480
|
const tiers = sync
|
|
414
481
|
? operatorDefaults.tiers
|
|
415
482
|
: (repoManifest?.tiers ?? operatorDefaults.tiers);
|
|
483
|
+
const { routing, ...compatibility } = mergeRoutingStateLayers(operatorDefaults, ...(!sync && repoManifest ? [repoManifest] : []));
|
|
416
484
|
return {
|
|
417
485
|
kit: "orchestrator-workflow",
|
|
418
486
|
version: PACKAGE_VERSION,
|
|
@@ -427,6 +495,8 @@ function buildApplyPrevious(repoManifest, operatorDefaults, sync) {
|
|
|
427
495
|
models,
|
|
428
496
|
profile,
|
|
429
497
|
tiers,
|
|
498
|
+
...(routing !== undefined ? { routing } : {}),
|
|
499
|
+
...compatibility,
|
|
430
500
|
files: {},
|
|
431
501
|
installedAt: "",
|
|
432
502
|
};
|
|
@@ -439,6 +509,8 @@ program
|
|
|
439
509
|
.option("-f, --force", "overwrite kit-owned files that have local edits")
|
|
440
510
|
.option("--harness <list>", `comma-separated harnesses (${HARNESSES.join(", ")}); default: the target's recorded harnesses, else the operator defaults, else detected`)
|
|
441
511
|
.option("--models <spec>", 'per-role model overrides, e.g. "implementer=sonnet,reviewer=opus"')
|
|
512
|
+
.option("--routing <json-file>", "harness/role/tier routing patch JSON")
|
|
513
|
+
.option("--codex-catalog <json-file>", "optional offline Codex capability catalog JSON to validate before writing")
|
|
442
514
|
.option("--profile <profile>", `subagent role profile (${PROFILES.join(", ")}); default: the target's recorded profile, else the operator default`)
|
|
443
515
|
.option("--opencode-provider <id>", "opencode provider id for alias resolution (e.g. github-copilot); auto-detected when omitted")
|
|
444
516
|
.option("--tiers", "also render per-role effort-tier subagent variants (<role>-<tier>.md); default: the target's recorded value, else the operator default")
|
|
@@ -448,6 +520,19 @@ program
|
|
|
448
520
|
.option("--pin <version>", "set or replace the target's recorded kit-version pin and apply this operator install regardless of any existing pin")
|
|
449
521
|
.option("--unpin", "clear the target's recorded kit-version pin and apply this operator install")
|
|
450
522
|
.action(async (opts) => {
|
|
523
|
+
let routingPatch;
|
|
524
|
+
let codexCatalog;
|
|
525
|
+
try {
|
|
526
|
+
routingPatch = routingOption(opts.routing);
|
|
527
|
+
codexCatalog = opts.codexCatalog
|
|
528
|
+
? readJsonOption(opts.codexCatalog, "--codex-catalog")
|
|
529
|
+
: undefined;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
console.error(error instanceof Error ? error.message : error);
|
|
533
|
+
process.exitCode = 2;
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
451
536
|
// --pin and --unpin express opposite intents (set a pin vs clear it);
|
|
452
537
|
// accepting both silently would make the effective pin depend on
|
|
453
538
|
// internal option-resolution order, so this is a usage error rather
|
|
@@ -539,7 +624,7 @@ program
|
|
|
539
624
|
}
|
|
540
625
|
const chosenHarnesses = resolveApplyHarnesses(targetDir, repoManifest, operatorManifest.defaults);
|
|
541
626
|
const previous = buildApplyPrevious(repoManifest, operatorManifest.defaults, Boolean(opts.sync));
|
|
542
|
-
const { harnesses, profile, models, tiers, opencodeModels, opencodeClassModels, warnings, } = await resolveInitInputs(
|
|
627
|
+
const { harnesses, profile, models, tiers, opencodeModels, opencodeClassModels, routing: resolvedRouting, warnings, } = await resolveInitInputs(
|
|
543
628
|
// `previous` is always defined here (`buildApplyPrevious` returns a
|
|
544
629
|
// synthetic object even for a target with no manifest of its own),
|
|
545
630
|
// so `previousIsRecordedManifest` cannot be `Boolean(previous)`; it
|
|
@@ -560,7 +645,10 @@ program
|
|
|
560
645
|
// `stickyAnnotateDetected`) is pinned inside `buildApplyInitInputs`
|
|
561
646
|
// rather than inlined here (agent-tasks fe834823, fix round 3,
|
|
562
647
|
// review finding 1).
|
|
563
|
-
buildApplyInitInputs(targetDir, chosenHarnesses, previous, interactive,
|
|
648
|
+
buildApplyInitInputs(targetDir, chosenHarnesses, previous, interactive,
|
|
649
|
+
// Explicit routing is parsed before the first target write and is
|
|
650
|
+
// the final merge layer after repo/operator sticky resolution.
|
|
651
|
+
{ ...opts, routing: routingPatch }, Boolean(repoManifest)));
|
|
564
652
|
for (const w of warnings) {
|
|
565
653
|
process.stderr.write(`${w}\n`);
|
|
566
654
|
}
|
|
@@ -591,6 +679,9 @@ program
|
|
|
591
679
|
opencodeModels,
|
|
592
680
|
tiers,
|
|
593
681
|
opencodeClassModels,
|
|
682
|
+
routing: resolvedRouting,
|
|
683
|
+
routingMode: "replace",
|
|
684
|
+
codexCatalog,
|
|
594
685
|
pin,
|
|
595
686
|
});
|
|
596
687
|
showPaths("Created", report.written);
|
|
@@ -789,6 +880,12 @@ function printTargetDetail(target, operatorVersion) {
|
|
|
789
880
|
if (target.divergence.models) {
|
|
790
881
|
console.log(` models: ${target.divergentModelRoles.join(", ")}`);
|
|
791
882
|
}
|
|
883
|
+
if (target.divergence.routing) {
|
|
884
|
+
console.log(" routing: repo and operator selections differ");
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const gap of target.routingComparisonGaps ?? []) {
|
|
888
|
+
console.log(` Routing comparison incomplete: ${gap}`);
|
|
792
889
|
}
|
|
793
890
|
const showsVersionLagDetail = (target.status === "version-lag" ||
|
|
794
891
|
((target.status === "divergent" || target.status === "drift") &&
|
|
@@ -826,6 +923,10 @@ function operatorDefaultsFromRepoManifest(repoManifest) {
|
|
|
826
923
|
profile: repoManifest.profile,
|
|
827
924
|
tiers: repoManifest.tiers,
|
|
828
925
|
models: { ...repoManifest.models },
|
|
926
|
+
...parseOpencodeModelMaps(repoManifest),
|
|
927
|
+
...(repoManifest.routing !== undefined
|
|
928
|
+
? { routing: repoManifest.routing }
|
|
929
|
+
: {}),
|
|
829
930
|
};
|
|
830
931
|
}
|
|
831
932
|
/**
|
package/dist/codex.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Role, Tier } from "./models.js";
|
|
2
|
+
import type { ModelSelection } from "./routing.js";
|
|
3
|
+
/**
|
|
4
|
+
* Produces a standalone Codex subagent definition. The reviewer intentionally
|
|
5
|
+
* inherits its parent's sandbox: its canonical prompt prohibits source edits,
|
|
6
|
+
* while inherited access lets it create test/build output in temporary paths.
|
|
7
|
+
*/
|
|
8
|
+
export declare function composeCodexAgent(role: Role, selection: ModelSelection, tier?: Tier): string;
|
package/dist/codex.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readAgentAsset } from "./assets.js";
|
|
2
|
+
function tomlString(value) {
|
|
3
|
+
let escaped = "";
|
|
4
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
5
|
+
const code = value.charCodeAt(index);
|
|
6
|
+
if (code === 0x22)
|
|
7
|
+
escaped += '\\"';
|
|
8
|
+
else if (code === 0x5c)
|
|
9
|
+
escaped += "\\\\";
|
|
10
|
+
else if (code === 0x08)
|
|
11
|
+
escaped += "\\b";
|
|
12
|
+
else if (code === 0x09)
|
|
13
|
+
escaped += "\\t";
|
|
14
|
+
else if (code === 0x0a)
|
|
15
|
+
escaped += "\\n";
|
|
16
|
+
else if (code === 0x0c)
|
|
17
|
+
escaped += "\\f";
|
|
18
|
+
else if (code === 0x0d)
|
|
19
|
+
escaped += "\\r";
|
|
20
|
+
else if (code < 0x20 || code === 0x7f) {
|
|
21
|
+
escaped += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
escaped += value[index];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return `"${escaped}"`;
|
|
28
|
+
}
|
|
29
|
+
function descriptionForTier(description, tier) {
|
|
30
|
+
return tier === undefined
|
|
31
|
+
? description
|
|
32
|
+
: `${description} (Effort tier: ${tier}.)`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Produces a standalone Codex subagent definition. The reviewer intentionally
|
|
36
|
+
* inherits its parent's sandbox: its canonical prompt prohibits source edits,
|
|
37
|
+
* while inherited access lets it create test/build output in temporary paths.
|
|
38
|
+
*/
|
|
39
|
+
export function composeCodexAgent(role, selection, tier) {
|
|
40
|
+
const asset = readAgentAsset(role);
|
|
41
|
+
const lines = [
|
|
42
|
+
`name = ${tomlString(tier === undefined ? asset.name : `${asset.name}-${tier}`)}`,
|
|
43
|
+
`description = ${tomlString(descriptionForTier(asset.description, tier))}`,
|
|
44
|
+
`model = ${tomlString(selection.model)}`,
|
|
45
|
+
`model_reasoning_effort = ${tomlString(selection.effort)}`,
|
|
46
|
+
];
|
|
47
|
+
if (role === "explorer" || role === "advisor") {
|
|
48
|
+
lines.push('sandbox_mode = "read-only"');
|
|
49
|
+
}
|
|
50
|
+
lines.push(`developer_instructions = ${tomlString(asset.body.trimEnd())}`);
|
|
51
|
+
return `${lines.join("\n")}\n`;
|
|
52
|
+
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface TargetDivergence {
|
|
|
24
24
|
profile: boolean;
|
|
25
25
|
tiers: boolean;
|
|
26
26
|
models: boolean;
|
|
27
|
+
/** Present only when explicit effective routing differs. */
|
|
28
|
+
routing?: boolean;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Per-target report. Only `path`, `status`, `installedVersion`, `pin`,
|
|
@@ -40,6 +42,8 @@ export interface TargetReport {
|
|
|
40
42
|
pin: string | null;
|
|
41
43
|
divergence: TargetDivergence | null;
|
|
42
44
|
driftFiles: string[] | null;
|
|
45
|
+
/** Selected legacy opencode leaves that cannot be compared offline. */
|
|
46
|
+
routingComparisonGaps?: string[];
|
|
43
47
|
/** Human-output-only: the repo's own profile, or null when unknown. */
|
|
44
48
|
repoProfile: Profile | null;
|
|
45
49
|
/** Human-output-only: the operator default profile, for the comparison line. */
|
|
@@ -81,6 +85,8 @@ export interface TargetReportJson {
|
|
|
81
85
|
pin: string | null;
|
|
82
86
|
divergence: TargetDivergence | null;
|
|
83
87
|
driftFiles: string[] | null;
|
|
88
|
+
/** Selected legacy opencode leaves that cannot be compared offline. */
|
|
89
|
+
routingComparisonGaps?: string[];
|
|
84
90
|
versionLag: boolean;
|
|
85
91
|
reason: string | null;
|
|
86
92
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -3,7 +3,8 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { PACKAGE_VERSION } from "./assets.js";
|
|
5
5
|
import { MANIFEST_PATH, readInstalledManifest } from "./init.js";
|
|
6
|
-
import { DEFAULT_MODELS,
|
|
6
|
+
import { DEFAULT_MODELS, rolesForProfile } from "./models.js";
|
|
7
|
+
import { compareRoutingState } from "./routing-state.js";
|
|
7
8
|
import { OPERATOR_MANIFEST_FILENAME, operatorManifestState, updateOperatorManifest, } from "./operator-manifest.js";
|
|
8
9
|
export function targetReportToJson(report) {
|
|
9
10
|
return {
|
|
@@ -13,6 +14,9 @@ export function targetReportToJson(report) {
|
|
|
13
14
|
pin: report.pin,
|
|
14
15
|
divergence: report.divergence,
|
|
15
16
|
driftFiles: report.driftFiles,
|
|
17
|
+
...(report.routingComparisonGaps?.length
|
|
18
|
+
? { routingComparisonGaps: report.routingComparisonGaps }
|
|
19
|
+
: {}),
|
|
16
20
|
versionLag: report.versionLag,
|
|
17
21
|
reason: report.reason,
|
|
18
22
|
};
|
|
@@ -167,17 +171,31 @@ export function inspectTarget(target, operator, kitVersion) {
|
|
|
167
171
|
if (manifestStat.kind === "error") {
|
|
168
172
|
return baseReport(target, operator, "unverifiable", "directory not accessible");
|
|
169
173
|
}
|
|
170
|
-
|
|
174
|
+
let manifest;
|
|
175
|
+
try {
|
|
176
|
+
manifest = readInstalledManifest(target.path);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Strict reinstall validation must not abort inspection of other targets.
|
|
180
|
+
return baseReport(target, operator, "unverifiable", "manifest unreadable");
|
|
181
|
+
}
|
|
171
182
|
if (!manifest) {
|
|
172
183
|
return baseReport(target, operator, "unverifiable", "manifest unreadable");
|
|
173
184
|
}
|
|
174
185
|
const driftFiles = computeDriftFiles(target.path, manifest);
|
|
175
|
-
const divergentModelRoles =
|
|
176
|
-
resolvedModel(
|
|
186
|
+
const divergentModelRoles = rolesForProfile(manifest.profile).filter((role) => manifest.harnesses.some((harness) => harness === "claude" || harness === "opencode") &&
|
|
187
|
+
resolvedModel(manifest.models, role) !==
|
|
188
|
+
resolvedModel(operator.defaults.models, role));
|
|
189
|
+
const routingComparison = compareRoutingState(manifest, operator.defaults, {
|
|
190
|
+
harnesses: manifest.harnesses,
|
|
191
|
+
profile: manifest.profile,
|
|
192
|
+
tiers: manifest.tiers,
|
|
193
|
+
});
|
|
177
194
|
const divergence = {
|
|
178
195
|
profile: manifest.profile !== operator.defaults.profile,
|
|
179
196
|
tiers: manifest.tiers !== operator.defaults.tiers,
|
|
180
197
|
models: divergentModelRoles.length > 0,
|
|
198
|
+
...(routingComparison.differs ? { routing: true } : {}),
|
|
181
199
|
};
|
|
182
200
|
// A recorded pin suppresses version-lag only when the pin equals the
|
|
183
201
|
// repo's own installed version: that is the expected, deliberate-stay
|
|
@@ -196,7 +214,10 @@ export function inspectTarget(target, operator, kitVersion) {
|
|
|
196
214
|
if (driftFiles.length > 0) {
|
|
197
215
|
status = "drift";
|
|
198
216
|
}
|
|
199
|
-
else if (divergence.profile ||
|
|
217
|
+
else if (divergence.profile ||
|
|
218
|
+
divergence.tiers ||
|
|
219
|
+
divergence.models ||
|
|
220
|
+
divergence.routing) {
|
|
200
221
|
status = "divergent";
|
|
201
222
|
}
|
|
202
223
|
else if (versionLag) {
|
|
@@ -212,6 +233,9 @@ export function inspectTarget(target, operator, kitVersion) {
|
|
|
212
233
|
pin: hasPin ? manifest.pin : null,
|
|
213
234
|
divergence,
|
|
214
235
|
driftFiles: driftFiles.length > 0 ? driftFiles : null,
|
|
236
|
+
...(routingComparison.gaps.length > 0
|
|
237
|
+
? { routingComparisonGaps: routingComparison.gaps }
|
|
238
|
+
: {}),
|
|
215
239
|
repoProfile: manifest.profile,
|
|
216
240
|
operatorProfile: operator.defaults.profile,
|
|
217
241
|
repoTiers: manifest.tiers,
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,6 @@ export { CLASS_MODELS, DEFAULT_MODELS, DEFAULT_PROFILE, DEFAULT_TIER, MODEL_ALIA
|
|
|
8
8
|
export type { ModelAlias, ModelClass, Profile, Role, Tier } from "./models.js";
|
|
9
9
|
export type { Report } from "./writers.js";
|
|
10
10
|
export { PACKAGE_VERSION } from "./assets.js";
|
|
11
|
+
export { defaultCodexRouting, mergeRouting, parseRouting, validateCodexCatalog, } from "./routing.js";
|
|
12
|
+
export type { HarnessRouting, ModelSelection } from "./routing.js";
|
|
13
|
+
export { composeCodexAgent } from "./codex.js";
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,5 @@ export { runUninstall } from "./uninstall.js";
|
|
|
3
3
|
export { detectHarnesses, parseHarnessList, parseHarnessOption, HARNESSES, } from "./detect.js";
|
|
4
4
|
export { CLASS_MODELS, DEFAULT_MODELS, DEFAULT_PROFILE, DEFAULT_TIER, MODEL_ALIASES, MODEL_CLASSES, PROFILES, ROLES, ROLE_TIERS, TIER_DEFS, claudeModelValue, isProfile, opencodeModelValue, parseModelsSpec, parseProfile, rolesForProfile, } from "./models.js";
|
|
5
5
|
export { PACKAGE_VERSION } from "./assets.js";
|
|
6
|
+
export { defaultCodexRouting, mergeRouting, parseRouting, validateCodexCatalog, } from "./routing.js";
|
|
7
|
+
export { composeCodexAgent } from "./codex.js";
|
package/dist/init.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Harness } from "./detect.js";
|
|
2
2
|
import type { ModelClass, Profile, Role } from "./models.js";
|
|
3
|
+
import type { HarnessRouting } from "./routing.js";
|
|
4
|
+
import type { OpencodeModelMaps } from "./routing-state.js";
|
|
3
5
|
import type { Report } from "./writers.js";
|
|
4
6
|
export interface InitOptions {
|
|
5
7
|
targetDir: string;
|
|
@@ -15,11 +17,14 @@ export interface InitOptions {
|
|
|
15
17
|
/**
|
|
16
18
|
* Resolved fully-qualified opencode model ids per role, or `undefined` to
|
|
17
19
|
* omit the `model:` frontmatter line (subagent inherits the session model).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
20
|
+
* Qualified values persist in routing; legacy bare IDs persist in the
|
|
21
|
+
* manifest compatibility map of the same name. A present map records
|
|
22
|
+
* inheritance for missing keys unless routing supplies that selection.
|
|
23
|
+
* When absent, prior
|
|
24
|
+
* selections survive; otherwise `opencodeModelValue(models[role])` passes
|
|
25
|
+
* through a qualified id or leaves bare aliases inheriting the session model.
|
|
21
26
|
*/
|
|
22
|
-
opencodeModels?: Record<Role, string | undefined
|
|
27
|
+
opencodeModels?: Partial<Record<Role, string | undefined>>;
|
|
23
28
|
/**
|
|
24
29
|
* Renders additional per-role effort-tier subagent variants
|
|
25
30
|
* (`<role>-<tier>.md`) alongside the default agent file. Defaults to
|
|
@@ -33,9 +38,29 @@ export interface InitOptions {
|
|
|
33
38
|
* that variant. Only consulted when `tiers` is true and the `opencode`
|
|
34
39
|
* harness is selected; mirrors `opencodeModels` but keyed by model class
|
|
35
40
|
* instead of role, since a tier variant's model is chosen by class, not
|
|
36
|
-
* by the role's own preselected model.
|
|
41
|
+
* by the role's own preselected model. Resolved ids are persisted per tier
|
|
42
|
+
* and retained on a later install that omits this field. Legacy bare IDs
|
|
43
|
+
* persist in the manifest compatibility map of the same name.
|
|
37
44
|
*/
|
|
38
|
-
opencodeClassModels?: Record<ModelClass, string | undefined
|
|
45
|
+
opencodeClassModels?: Partial<Record<ModelClass, string | undefined>>;
|
|
46
|
+
/**
|
|
47
|
+
* Per-harness role/tier selections. This is an additive, partial map: the
|
|
48
|
+
* installer preserves previous leaves and fills remaining omissions from
|
|
49
|
+
* deterministic harness defaults. An explicit leaf wins over legacy inputs.
|
|
50
|
+
*/
|
|
51
|
+
routing?: HarnessRouting;
|
|
52
|
+
/**
|
|
53
|
+
* "patch" (default) preserves previous routing leaves. "replace" accepts
|
|
54
|
+
* an authoritative resolved map and excludes all previous routing, as used
|
|
55
|
+
* by CLI resolution and apply --sync. Omitted leaves still use defaults.
|
|
56
|
+
*/
|
|
57
|
+
routingMode?: "patch" | "replace";
|
|
58
|
+
/**
|
|
59
|
+
* Optional, explicitly supplied Codex capability catalog. The installer
|
|
60
|
+
* never contacts a live service; when this is present it is validated
|
|
61
|
+
* before any kit-owned file is written.
|
|
62
|
+
*/
|
|
63
|
+
codexCatalog?: unknown;
|
|
39
64
|
/**
|
|
40
65
|
* Repo kit-version pin (distinct from the actually-installed `version`),
|
|
41
66
|
* so a later `apply` command can gate on it. A `string` sets a new
|
|
@@ -47,7 +72,7 @@ export interface InitOptions {
|
|
|
47
72
|
pin?: string | null;
|
|
48
73
|
}
|
|
49
74
|
export declare const MANIFEST_PATH: string;
|
|
50
|
-
export interface Manifest {
|
|
75
|
+
export interface Manifest extends OpencodeModelMaps {
|
|
51
76
|
kit: string;
|
|
52
77
|
version: string;
|
|
53
78
|
harnesses: Harness[];
|
|
@@ -56,6 +81,8 @@ export interface Manifest {
|
|
|
56
81
|
profile: Profile;
|
|
57
82
|
/** Whether per-role effort-tier subagent variants were rendered. */
|
|
58
83
|
tiers: boolean;
|
|
84
|
+
/** Exact resolved harness/role/tier selections from the install. */
|
|
85
|
+
routing?: HarnessRouting;
|
|
59
86
|
/**
|
|
60
87
|
* sha256 of every kit-owned file as installed. This is how a re-run tells
|
|
61
88
|
* "upstream changed, safe to update" apart from "user edited, conflict".
|