agentic-workflow-manager 9.11.11 → 9.12.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/dist/src/commands/doctor.js +23 -1
- package/dist/src/commands/hooks/claude.js +61 -8
- package/dist/src/commands/hooks/resync.js +1 -1
- package/dist/src/commands/job/index.js +39 -4
- package/dist/src/commands/model-policy/index.js +253 -0
- package/dist/src/commands/plan/index.js +53 -12
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +71 -5
- package/dist/src/commands/watch/supervisor.js +63 -3
- package/dist/src/core/admission/index.js +20 -6
- package/dist/src/core/journal/types.js +21 -5
- package/dist/src/core/model-policy/capabilities-v2.js +152 -0
- package/dist/src/core/model-policy/capabilities.js +7 -1
- package/dist/src/core/model-policy/claude-hook-events.js +120 -0
- package/dist/src/core/model-policy/event-store.js +221 -0
- package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
- package/dist/src/core/model-policy/journal.js +125 -16
- package/dist/src/core/model-policy/local-claude-scope.js +41 -0
- package/dist/src/core/model-policy/local-codex-scope.js +33 -0
- package/dist/src/core/model-policy/local-event-scope.js +13 -0
- package/dist/src/core/model-policy/machine-key.js +56 -0
- package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
- package/dist/src/core/model-policy/native-claude-config.js +107 -0
- package/dist/src/core/model-policy/native-claude-machine.js +58 -0
- package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
- package/dist/src/core/model-policy/native-claude.js +41 -0
- package/dist/src/core/model-policy/native-codex-machine.js +116 -0
- package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
- package/dist/src/core/model-policy/native-codex.js +302 -0
- package/dist/src/core/model-policy/native-routing-observe.js +106 -0
- package/dist/src/core/model-policy/native-routing-proof.js +52 -0
- package/dist/src/core/model-policy/native-runtime.js +86 -0
- package/dist/src/core/model-policy/resolve.js +117 -3
- package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
- package/dist/src/core/model-policy/setup-readiness.js +77 -0
- package/dist/src/providers/index.js +3 -1
- package/dist/tests/commands/doctor-routing.test.js +22 -0
- package/dist/tests/commands/doctor.test.js +5 -0
- package/dist/tests/commands/hooks/install.test.js +6 -0
- package/dist/tests/commands/hooks/resync.test.js +3 -0
- package/dist/tests/commands/hooks/status.test.js +12 -1
- package/dist/tests/commands/job/routing-file.test.js +32 -0
- package/dist/tests/commands/job/routing-report-cli.test.js +12 -5
- package/dist/tests/commands/job/routing.test.js +2 -2
- package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
- package/dist/tests/commands/model-policy/index.test.js +179 -0
- package/dist/tests/commands/plan/index.test.js +2 -2
- package/dist/tests/commands/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +116 -10
- package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
- package/dist/tests/core/admission/index.test.js +21 -0
- package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
- package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
- package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
- package/dist/tests/core/model-policy/event-store.test.js +151 -0
- package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
- package/dist/tests/core/model-policy/journal.test.js +165 -12
- package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
- package/dist/tests/core/model-policy/machine-key.test.js +38 -0
- package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
- package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
- package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
- package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
- package/dist/tests/core/model-policy/native-claude.test.js +21 -0
- package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
- package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
- package/dist/tests/core/model-policy/native-codex.test.js +133 -0
- package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
- package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
- package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
- package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
- package/dist/tests/core/model-policy/resolve.test.js +17 -0
- package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
- package/dist/tests/helpers/routing-approval.js +14 -0
- package/dist/tests/providers/index.test.js +1 -1
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.renderReport = renderReport;
|
|
7
|
+
exports.attachRoutingSetupGuidance = attachRoutingSetupGuidance;
|
|
7
8
|
exports.renderProviderReport = renderProviderReport;
|
|
8
9
|
exports.runDoctor = runDoctor;
|
|
9
10
|
exports.registerDoctorCommand = registerDoctorCommand;
|
|
@@ -20,6 +21,8 @@ const render_html_1 = require("../core/dashboard/render-html");
|
|
|
20
21
|
const render_terminal_1 = require("../core/dashboard/render-terminal");
|
|
21
22
|
const write_html_1 = require("../core/dashboard/write-html");
|
|
22
23
|
const bundles_1 = require("../core/bundles");
|
|
24
|
+
const store_1 = require("../core/model-policy/store");
|
|
25
|
+
const setup_readiness_1 = require("../core/model-policy/setup-readiness");
|
|
23
26
|
function glyph(status) {
|
|
24
27
|
if (status === 'ok')
|
|
25
28
|
return picocolors_1.default.green('✔');
|
|
@@ -76,7 +79,26 @@ const CHECK_LABELS = {
|
|
|
76
79
|
'hook.trust': 'hook SessionStart',
|
|
77
80
|
'guidance.project': 'project guidance',
|
|
78
81
|
'constitution.delivery': 'constitution delivery',
|
|
82
|
+
'routing.machine': 'model routing on this machine',
|
|
79
83
|
};
|
|
84
|
+
/** Adds a setup prompt without changing existing provider health or mutating the report. */
|
|
85
|
+
function attachRoutingSetupGuidance(report, guidance) {
|
|
86
|
+
if (!report || !Array.isArray(report.providers) || !Array.isArray(guidance))
|
|
87
|
+
throw new Error('doctor routing setup input is invalid');
|
|
88
|
+
const providers = report.providers.map(provider => {
|
|
89
|
+
const checks = [...provider.checks];
|
|
90
|
+
for (const item of guidance) {
|
|
91
|
+
if (item.state === 'policy-invalid' || item.target !== provider.id)
|
|
92
|
+
continue;
|
|
93
|
+
if (item.state === 'needs-evidence')
|
|
94
|
+
checks.push({ id: 'routing.machine', state: 'pending', detail: `native routing evidence not checked for ${item.runtimeKind}`, remediationCode: item.command });
|
|
95
|
+
else
|
|
96
|
+
checks.push({ id: 'routing.machine', state: 'supported', detail: `sealed selection coverage for ${item.runtimeKind} only; current runtime/account/config scope is checked at routed dispatch` });
|
|
97
|
+
}
|
|
98
|
+
return { ...provider, checks };
|
|
99
|
+
});
|
|
100
|
+
return { ...report, providers };
|
|
101
|
+
}
|
|
80
102
|
const OK_STATES = ['supported', 'healthy', 'shared', 'delivered'];
|
|
81
103
|
const PENDING_STATES = ['pending', 'pending-trust'];
|
|
82
104
|
const WARN_STATES = ['stale'];
|
|
@@ -179,7 +201,7 @@ function runDoctor(opts = {}) {
|
|
|
179
201
|
try {
|
|
180
202
|
const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets, reporter });
|
|
181
203
|
const providers = ctx.providers ?? [];
|
|
182
|
-
report = { providers, overall: (0, checks_1.computeProviderOverall)(providers) };
|
|
204
|
+
report = attachRoutingSetupGuidance({ providers, overall: (0, checks_1.computeProviderOverall)(providers) }, (0, setup_readiness_1.routingSetupGuidance)((0, store_1.readEffectivePolicy)(opts.cwd ?? process.cwd())));
|
|
183
205
|
}
|
|
184
206
|
catch (err) {
|
|
185
207
|
process.stderr.write(`awm doctor: internal error: ${err.message}\n`);
|
|
@@ -61,6 +61,32 @@ function isAwmEntry(entry, scriptsDir, matcher) {
|
|
|
61
61
|
Array.isArray(entry?.hooks) &&
|
|
62
62
|
entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
|
|
63
63
|
}
|
|
64
|
+
function routingCommand(event) { return `awm model-policy hook-event --event ${event}`; }
|
|
65
|
+
function routingEntry(event) {
|
|
66
|
+
return { matcher: '.+', hooks: [{ type: 'command', command: routingCommand(event), async: false }] };
|
|
67
|
+
}
|
|
68
|
+
function isRoutingEntry(entry, event) {
|
|
69
|
+
return entry?.matcher === '.+' && Array.isArray(entry?.hooks) && entry.hooks.some((hook) => hook?.type === 'command' && hook.command === routingCommand(event) && hook.async === false);
|
|
70
|
+
}
|
|
71
|
+
function mergeRoutingEntries(settings) {
|
|
72
|
+
let changed = false;
|
|
73
|
+
for (const [eventName, event] of [['SubagentStart', 'start'], ['SubagentStop', 'stop']]) {
|
|
74
|
+
if (!Array.isArray(settings.hooks[eventName]))
|
|
75
|
+
settings.hooks[eventName] = [];
|
|
76
|
+
const entries = settings.hooks[eventName];
|
|
77
|
+
const expected = routingEntry(event);
|
|
78
|
+
const index = entries.findIndex(entry => isRoutingEntry(entry, event));
|
|
79
|
+
if (index < 0) {
|
|
80
|
+
entries.push(expected);
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
else if (JSON.stringify(entries[index]) !== JSON.stringify(expected)) {
|
|
84
|
+
entries[index] = expected;
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return changed;
|
|
89
|
+
}
|
|
64
90
|
function installClaudeHook(options) {
|
|
65
91
|
const config = (0, providers_1.getSettingsMergeHookConfig)(options.agent);
|
|
66
92
|
// 1. Verify registry sources exist FIRST (before touching settings)
|
|
@@ -117,19 +143,24 @@ function installClaudeHook(options) {
|
|
|
117
143
|
}]
|
|
118
144
|
};
|
|
119
145
|
let status;
|
|
146
|
+
let sessionChanged = false;
|
|
120
147
|
if (awmEntryIdx >= 0) {
|
|
121
148
|
// Igual que en codex.ts: si la poda saco algo, hay que escribir aunque nuestra
|
|
122
149
|
// entrada ya este identica — si no, la limpieza se calcula y se tira.
|
|
123
|
-
if (
|
|
124
|
-
|
|
150
|
+
if (pruned || JSON.stringify(entries[awmEntryIdx]) !== JSON.stringify(newEntry)) {
|
|
151
|
+
entries[awmEntryIdx] = newEntry;
|
|
152
|
+
sessionChanged = true;
|
|
125
153
|
}
|
|
126
|
-
entries[awmEntryIdx] = newEntry;
|
|
127
154
|
status = 'installed';
|
|
128
155
|
}
|
|
129
156
|
else {
|
|
130
157
|
entries.push(newEntry);
|
|
158
|
+
sessionChanged = true;
|
|
131
159
|
status = 'installed';
|
|
132
160
|
}
|
|
161
|
+
const routingChanged = mergeRoutingEntries(settings);
|
|
162
|
+
if (!sessionChanged && !routingChanged)
|
|
163
|
+
return { status: 'already-up-to-date', scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath: null };
|
|
133
164
|
// 7. Write settings
|
|
134
165
|
fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
135
166
|
return { status, scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath };
|
|
@@ -153,13 +184,24 @@ function checkSettingsEntry(settingsPath, scriptsDir, matcher, eventName) {
|
|
|
153
184
|
}
|
|
154
185
|
return { ok: true, detail: settingsPath };
|
|
155
186
|
}
|
|
187
|
+
function checkRoutingCapture(settingsPath) {
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf8'));
|
|
190
|
+
if (isRoutingEntry((parsed?.hooks?.SubagentStart ?? []).find((entry) => isRoutingEntry(entry, 'start')), 'start')
|
|
191
|
+
&& isRoutingEntry((parsed?.hooks?.SubagentStop ?? []).find((entry) => isRoutingEntry(entry, 'stop')), 'stop'))
|
|
192
|
+
return { ok: true, detail: settingsPath };
|
|
193
|
+
}
|
|
194
|
+
catch { /* missing or invalid settings cannot provide native capture */ }
|
|
195
|
+
return { ok: false, detail: 'AWM native Claude routing hooks are missing; rerun awm hooks install --agent claude-code' };
|
|
196
|
+
}
|
|
156
197
|
function computeClaudeHookStatus(agent) {
|
|
157
198
|
const config = (0, providers_1.getSettingsMergeHookConfig)(agent);
|
|
158
199
|
const checks = {
|
|
159
200
|
bootstrapSkill: (0, shared_1.checkFile)(path_1.default.join(config.scriptsDir, 'using-awm.md')),
|
|
160
201
|
sessionStartScript: (0, shared_1.checkExecutable)(path_1.default.join(config.scriptsDir, 'session-start')),
|
|
161
202
|
runHookWrapper: (0, shared_1.checkExecutable)(path_1.default.join(config.scriptsDir, 'run-hook.cmd')),
|
|
162
|
-
settingsEntry: checkSettingsEntry(config.settingsPath, config.scriptsDir, config.matcher, config.eventName)
|
|
203
|
+
settingsEntry: checkSettingsEntry(config.settingsPath, config.scriptsDir, config.matcher, config.eventName),
|
|
204
|
+
routingCapture: checkRoutingCapture(config.settingsPath),
|
|
163
205
|
};
|
|
164
206
|
const allOk = Object.values(checks).every((c) => c.ok);
|
|
165
207
|
const settingsOnlyMissing = !checks.settingsEntry.ok && checks.bootstrapSkill.ok && checks.sessionStartScript.ok && checks.runHookWrapper.ok;
|
|
@@ -187,19 +229,30 @@ function uninstallClaudeHook(agent) {
|
|
|
187
229
|
const entries = settings?.hooks?.[config.eventName] ?? [];
|
|
188
230
|
const beforeLength = entries.length;
|
|
189
231
|
const filtered = entries.filter((e) => !isAwmEntry(e, config.scriptsDir, config.matcher));
|
|
190
|
-
|
|
232
|
+
const startEntries = settings?.hooks?.SubagentStart ?? [];
|
|
233
|
+
const stopEntries = settings?.hooks?.SubagentStop ?? [];
|
|
234
|
+
const filteredStart = startEntries.filter(entry => !isRoutingEntry(entry, 'start'));
|
|
235
|
+
const filteredStop = stopEntries.filter(entry => !isRoutingEntry(entry, 'stop'));
|
|
236
|
+
if (filtered.length === beforeLength && filteredStart.length === startEntries.length && filteredStop.length === stopEntries.length) {
|
|
191
237
|
return { status: 'not-installed', backupPath: null };
|
|
192
238
|
}
|
|
193
239
|
const backupPath = (0, shared_1.backupManagedFile)(config.settingsPath);
|
|
194
240
|
if (filtered.length === 0) {
|
|
195
241
|
delete settings.hooks[config.eventName];
|
|
196
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
197
|
-
delete settings.hooks;
|
|
198
|
-
}
|
|
199
242
|
}
|
|
200
243
|
else {
|
|
201
244
|
settings.hooks[config.eventName] = filtered;
|
|
202
245
|
}
|
|
246
|
+
if (filteredStart.length === 0)
|
|
247
|
+
delete settings.hooks.SubagentStart;
|
|
248
|
+
else
|
|
249
|
+
settings.hooks.SubagentStart = filteredStart;
|
|
250
|
+
if (filteredStop.length === 0)
|
|
251
|
+
delete settings.hooks.SubagentStop;
|
|
252
|
+
else
|
|
253
|
+
settings.hooks.SubagentStop = filteredStop;
|
|
254
|
+
if (Object.keys(settings.hooks).length === 0)
|
|
255
|
+
delete settings.hooks;
|
|
203
256
|
fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
204
257
|
return { status: 'uninstalled', backupPath };
|
|
205
258
|
}
|
|
@@ -36,7 +36,7 @@ function resyncInstalledHooks(registryRoot, targets = [...providers_1.AGENT_TARG
|
|
|
36
36
|
results.push({ agent, action: 'registry-missing' });
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
|
-
(0, claude_1.
|
|
39
|
+
(0, claude_1.installClaudeHook)({ agent: 'claude-code', registryRoot, installMethod: method });
|
|
40
40
|
results.push({ agent, action: 'resynced' });
|
|
41
41
|
break;
|
|
42
42
|
}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readBoundedJson = readBoundedJson;
|
|
6
7
|
exports.registerJobCommand = registerJobCommand;
|
|
7
8
|
const child_process_1 = require("child_process");
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -27,6 +28,8 @@ const journal_1 = require("../../core/model-policy/journal");
|
|
|
27
28
|
const types_1 = require("../../core/journal/types");
|
|
28
29
|
const json_1 = require("../../core/plan/json");
|
|
29
30
|
const fs_1 = __importDefault(require("fs"));
|
|
31
|
+
const native_routing_observe_1 = require("../../core/model-policy/native-routing-observe");
|
|
32
|
+
const native_bridge_1 = require("../../core/secure-fs/native-bridge");
|
|
30
33
|
function branchOf(cwd) {
|
|
31
34
|
// stdio explicito (ver EXEC_STDIO en journal/process.ts): evita el relay
|
|
32
35
|
// default de execFileSync del stderr de git hacia el stderr del llamante,
|
|
@@ -52,11 +55,18 @@ function readBoundedJson(file) {
|
|
|
52
55
|
const stat = fs_1.default.lstatSync(file);
|
|
53
56
|
if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 256 * 1024)
|
|
54
57
|
throw new Error('routing file must be a bounded non-symlink regular file');
|
|
58
|
+
let bytes;
|
|
55
59
|
try {
|
|
56
|
-
|
|
60
|
+
bytes = native_bridge_1.secureFs.readRegularFile(file, 256 * 1024).bytes;
|
|
57
61
|
}
|
|
58
62
|
catch {
|
|
59
|
-
throw new Error('routing file must
|
|
63
|
+
throw new Error('routing file must remain a bounded non-symlink regular file');
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
return (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error('routing file must contain UTF-8 JSON without duplicate keys');
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
/** Guard de entrada (R9.4): sin descriptor de track, es un no-op — el caso
|
|
@@ -175,7 +185,7 @@ function registerJobCommand(program) {
|
|
|
175
185
|
job.command('routing-observe')
|
|
176
186
|
.requiredOption('--generation <token>').requiredOption('--attempt <id>').requiredOption('--native-agent-id <id>')
|
|
177
187
|
.requiredOption('--observation-file <file>').option('--cwd <root>', 'repository root', '.').option('--json')
|
|
178
|
-
.action((opts) => {
|
|
188
|
+
.action(async (opts) => {
|
|
179
189
|
const observation = readBoundedJson(opts.observationFile);
|
|
180
190
|
if (typeof observation !== 'object' || observation === null || Array.isArray(observation))
|
|
181
191
|
throw new Error('routing-observe requires an observation object');
|
|
@@ -186,7 +196,32 @@ function registerJobCommand(program) {
|
|
|
186
196
|
const repo = path_1.default.resolve(opts.cwd);
|
|
187
197
|
const branch = branchOf(repo);
|
|
188
198
|
assertAuthenticatedCwd(repo, branch);
|
|
189
|
-
const
|
|
199
|
+
const read = (0, store_1.readJournal)(repo, branch);
|
|
200
|
+
if (read.corrupt || !read.state)
|
|
201
|
+
throw new Error('routing-observe requires a healthy journal');
|
|
202
|
+
const attempt = read.state.routingAttempts?.find(item => item.id === opts.attempt);
|
|
203
|
+
if (!attempt)
|
|
204
|
+
throw new Error('routing-observe attempt is unknown');
|
|
205
|
+
let nativeProof;
|
|
206
|
+
let reason = unavailableReason;
|
|
207
|
+
if (attempt.nativeEvidenceRequired && reason === undefined) {
|
|
208
|
+
try {
|
|
209
|
+
const parentThreadId = observation.parentThreadId;
|
|
210
|
+
if (read.state.routingAttempts?.some(item => item.id !== attempt.id && item.nativeEvidenceRequired && item.nativeAgentId === opts.nativeAgentId))
|
|
211
|
+
throw new Error('native child was already used by another attempt');
|
|
212
|
+
nativeProof = await (0, native_routing_observe_1.observeNativeRoutingChild)({ attempt, nativeAgentId: opts.nativeAgentId,
|
|
213
|
+
...(typeof parentThreadId === 'string' ? { parentThreadId } : {}), cwd: repo, now: new Date(),
|
|
214
|
+
consumedEventDigests: (read.state.routingAttempts ?? []).map(item => item.nativeEventDigest).filter((item) => item !== undefined) });
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
reason = 'PROVENANCE_MISSING';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const payload = { attemptId: opts.attempt, nativeAgentId: opts.nativeAgentId,
|
|
221
|
+
...(nativeProof ? { observed: nativeProof.selection, nativeProof } : attempt.nativeEvidenceRequired ? {} : observed === undefined ? {} : { observed }),
|
|
222
|
+
...(reason === undefined ? {} : { unavailableReason: reason }) };
|
|
223
|
+
const emitted = (0, requests_1.emitRequest)(repo, branch, { kind: 'routing-observe', generationToken: opts.generation,
|
|
224
|
+
idempotencyKey: crypto_1.default.createHash('sha256').update(`routing-observe:${opts.generation}:${opts.attempt}:${opts.nativeAgentId}:${JSON.stringify(payload)}`).digest('hex'), payload });
|
|
190
225
|
process.stdout.write(JSON.stringify({ requestId: emitted.requestId }) + '\n');
|
|
191
226
|
});
|
|
192
227
|
job.command('ps').action(() => {
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.registerModelPolicyCommand = registerModelPolicyCommand;
|
|
4
7
|
const crypto_1 = require("crypto");
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
5
10
|
const providers_1 = require("../../providers");
|
|
6
11
|
const store_1 = require("../../core/model-policy/store");
|
|
7
12
|
const validate_1 = require("../../core/model-policy/validate");
|
|
8
13
|
const capabilities_1 = require("../../core/model-policy/capabilities");
|
|
9
14
|
const digest_1 = require("../../core/model-policy/digest");
|
|
15
|
+
const native_codex_1 = require("../../core/model-policy/native-codex");
|
|
16
|
+
const native_codex_machine_1 = require("../../core/model-policy/native-codex-machine");
|
|
17
|
+
const native_runtime_1 = require("../../core/model-policy/native-runtime");
|
|
18
|
+
const native_claude_machine_1 = require("../../core/model-policy/native-claude-machine");
|
|
19
|
+
const native_claude_config_1 = require("../../core/model-policy/native-claude-config");
|
|
20
|
+
const machine_key_1 = require("../../core/model-policy/machine-key");
|
|
21
|
+
const event_store_1 = require("../../core/model-policy/event-store");
|
|
22
|
+
const capabilities_v2_1 = require("../../core/model-policy/capabilities-v2");
|
|
23
|
+
const paths_1 = require("../../core/paths");
|
|
24
|
+
const selection_policy_digest_1 = require("../../core/model-policy/selection-policy-digest");
|
|
25
|
+
const claude_hook_events_1 = require("../../core/model-policy/claude-hook-events");
|
|
26
|
+
const native_claude_transcript_1 = require("../../core/model-policy/native-claude-transcript");
|
|
27
|
+
const native_claude_agent_definition_1 = require("../../core/model-policy/native-claude-agent-definition");
|
|
28
|
+
const event_store_2 = require("../../core/model-policy/event-store");
|
|
29
|
+
const local_claude_scope_1 = require("../../core/model-policy/local-claude-scope");
|
|
30
|
+
const json_1 = require("../../core/plan/json");
|
|
31
|
+
const claude_1 = require("../hooks/claude");
|
|
32
|
+
const hook_diagnostics_1 = require("../../core/model-policy/hook-diagnostics");
|
|
10
33
|
const roles = ['implementer', 'specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'];
|
|
11
34
|
const implementerProfiles = ['mechanical', 'integration', 'judgment'];
|
|
12
35
|
const contract = { schema: 'routing-protocol/v1', supportedPlanSchemas: ['compact-slices/v1', 'compact-slices/v2'], roles, implementerProfiles, limits: { maxFileBytes: validate_1.MAX_POLICY_BYTES, maxMappings: validate_1.MAX_MAPPINGS, maxIdentifierChars: 128, maxDiagnostics: 20, maxDiagnosticChars: 4096 } };
|
|
@@ -14,6 +37,20 @@ const protocolDigest = (0, crypto_1.createHash)('sha256').update(JSON.stringify(
|
|
|
14
37
|
function text(value, label) { if (typeof value !== 'string' || value.length === 0 || value.startsWith('--') || value.length > 4096 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
|
|
15
38
|
throw new Error(`${label} requires a non-empty value without control characters`); }
|
|
16
39
|
function output(value, json) { process.stdout.write(json ? `${JSON.stringify(value)}\n` : `${JSON.stringify(value, null, 2)}\n`); }
|
|
40
|
+
function sameSelection(left, right) {
|
|
41
|
+
return left.selector.kind === right.selector.kind && left.selector.id === right.selector.id
|
|
42
|
+
&& left.effort.kind === right.effort.kind
|
|
43
|
+
&& (left.effort.kind !== 'explicit' || left.effort.value === right.effort.value);
|
|
44
|
+
}
|
|
45
|
+
async function hookStdin() {
|
|
46
|
+
let content = '';
|
|
47
|
+
for await (const chunk of process.stdin) {
|
|
48
|
+
content += Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
49
|
+
if (content.length > 64 * 1024)
|
|
50
|
+
throw new Error('Claude hook event exceeds 64 KiB');
|
|
51
|
+
}
|
|
52
|
+
return (0, json_1.parseJsonNoDuplicate)(content);
|
|
53
|
+
}
|
|
17
54
|
function registerModelPolicyCommand(program, deps = {}) {
|
|
18
55
|
if (!program || typeof program.command !== 'function')
|
|
19
56
|
throw new Error('program must be a Commander command');
|
|
@@ -25,6 +62,222 @@ function registerModelPolicyCommand(program, deps = {}) {
|
|
|
25
62
|
const command = program.command('model-policy').description('inspect and explicitly approve portable routing policy');
|
|
26
63
|
command.command('contract').option('--cwd <path>').option('--json').action((options) => { if (options.cwd !== undefined)
|
|
27
64
|
text(options.cwd, '--cwd'); output({ ...contract, protocolDigest }, options.json === true); });
|
|
65
|
+
command.command('discover').description('compare approved selections with the native catalog; no inference or capability approval').requiredOption('--provider <target>').option('--cwd <path>').option('--json').action(async (options) => {
|
|
66
|
+
text(options.provider, '--provider');
|
|
67
|
+
if (options.cwd !== undefined)
|
|
68
|
+
text(options.cwd, '--cwd');
|
|
69
|
+
if (!providers_1.AGENT_TARGETS.includes(options.provider))
|
|
70
|
+
throw new Error('--provider is invalid');
|
|
71
|
+
if (options.provider !== 'codex') {
|
|
72
|
+
output({ state: 'unverified', provider: options.provider, reason: 'no native catalog adapter is installed for this provider', inferenceDispatched: false }, options.json === true);
|
|
73
|
+
process.exitCode = 2;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const discovered = await (deps.queryCodexModelCatalog ?? native_codex_1.queryCodexModelCatalog)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000 });
|
|
77
|
+
const effective = read(options.cwd ?? process.cwd());
|
|
78
|
+
let policyCoverage;
|
|
79
|
+
if (effective.state === 'absent')
|
|
80
|
+
policyCoverage = { state: 'not-configured', missing: [] };
|
|
81
|
+
else if (effective.state === 'invalid')
|
|
82
|
+
policyCoverage = { state: 'policy-invalid', missing: [] };
|
|
83
|
+
else {
|
|
84
|
+
const requested = effective.policy.content.mappings.filter(mapping => mapping.target === 'codex')
|
|
85
|
+
.flatMap(mapping => [...Object.values(mapping.profiles), mapping.fullCapability]);
|
|
86
|
+
const missing = requested.filter((candidate, index) => !discovered.selections.some(available => sameSelection(candidate, available))
|
|
87
|
+
&& requested.findIndex(other => sameSelection(candidate, other)) === index);
|
|
88
|
+
policyCoverage = { state: missing.length ? 'missing' : requested.length ? 'complete' : 'not-configured', missing };
|
|
89
|
+
}
|
|
90
|
+
output({ state: 'catalog-only', provider: 'codex', ...discovered, policyCoverage, inferenceDispatched: false }, options.json === true);
|
|
91
|
+
if (policyCoverage.state !== 'complete')
|
|
92
|
+
process.exitCode = 2;
|
|
93
|
+
});
|
|
94
|
+
command.command('setup').description('inspect one-time machine routing enrollment; never dispatch inference').requiredOption('--provider <target>').option('--cwd <path>').option('--json').action(async (options) => {
|
|
95
|
+
text(options.provider, '--provider');
|
|
96
|
+
if (options.cwd !== undefined)
|
|
97
|
+
text(options.cwd, '--cwd');
|
|
98
|
+
if (!providers_1.AGENT_TARGETS.includes(options.provider))
|
|
99
|
+
throw new Error('--provider is invalid');
|
|
100
|
+
const effective = read(options.cwd ?? process.cwd());
|
|
101
|
+
if (effective.state !== 'approved') {
|
|
102
|
+
output({ state: effective.state === 'absent' ? 'policy-absent' : 'policy-invalid', provider: options.provider, inferenceDispatched: false, tokenUsage: 'unknown', remedy: effective.state === 'absent' ? 'Approve a model-policy/v1 candidate before machine enrollment.' : effective.reason }, options.json === true);
|
|
103
|
+
process.exitCode = 2;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const mappings = effective.policy.content.mappings.filter(mapping => mapping.target === options.provider);
|
|
107
|
+
if (mappings.length === 0) {
|
|
108
|
+
output({ state: 'mapping-absent', provider: options.provider, inferenceDispatched: false, tokenUsage: 'unknown', remedy: 'Approve a mapping for this provider.' }, options.json === true);
|
|
109
|
+
process.exitCode = 2;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const codex = options.provider === 'codex' ? await Promise.all([
|
|
113
|
+
(deps.queryCodexModelCatalog ?? native_codex_1.queryCodexModelCatalog)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000 }),
|
|
114
|
+
(deps.queryCodexMachineFacts ?? native_codex_machine_1.queryCodexMachineFacts)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000,
|
|
115
|
+
cwd: path_1.default.resolve(options.cwd ?? process.cwd()), key: (deps.ensureMachineKey ?? machine_key_1.ensureMachineKey)((0, paths_1.awmHome)()) }),
|
|
116
|
+
(deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'codex', args: ['--version'], timeoutMs: 10000 }),
|
|
117
|
+
]) : undefined;
|
|
118
|
+
let claude;
|
|
119
|
+
if (options.provider === 'claude-code') {
|
|
120
|
+
try {
|
|
121
|
+
const key = (deps.ensureMachineKey ?? machine_key_1.ensureMachineKey)((0, paths_1.awmHome)());
|
|
122
|
+
claude = await Promise.all([
|
|
123
|
+
(deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'claude', args: ['--version'], timeoutMs: 10000 }),
|
|
124
|
+
(deps.queryClaudeAuthStatus ?? native_claude_machine_1.queryClaudeAuthStatus)({ command: 'claude', args: ['auth', 'status'], timeoutMs: 10000, key }),
|
|
125
|
+
Promise.resolve((deps.readClaudeModelConfigDigest ?? native_claude_config_1.readClaudeModelConfigDigest)({ cwd: path_1.default.resolve(options.cwd ?? process.cwd()),
|
|
126
|
+
configDir: path_1.default.resolve(process.env.CLAUDE_CONFIG_DIR ?? path_1.default.join(os_1.default.homedir(), '.claude')), key, environment: process.env })),
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
output({ state: 'setup-pending', provider: 'claude-code', machine: { runtimeState: 'unavailable', accountState: 'unverified', configState: 'unverified' },
|
|
131
|
+
inferenceDispatched: false, tokenUsage: 'unknown', remedy: 'Install and authenticate Claude Code, then rerun awm model-policy setup --provider claude-code.' }, options.json === true);
|
|
132
|
+
process.exitCode = 2;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const catalog = codex?.[0];
|
|
137
|
+
const codexMachine = codex ? { runtimeVersion: codex[2].version, binaryDigest: codex[2].binaryDigest, configDigest: codex[1].configDigest,
|
|
138
|
+
accountState: codex[1].accountState, accountScopeDigest: codex[1].accountScopeDigest } : null;
|
|
139
|
+
const machine = codexMachine ?? (claude ? { runtimeVersion: claude[0].version, binaryDigest: claude[0].binaryDigest, accountState: claude[1].accountState,
|
|
140
|
+
accountScopeDigest: claude[1].accountScopeDigest, configState: 'local-fingerprint', configDigest: claude[2] } : null);
|
|
141
|
+
const scope = codexMachine?.accountState === 'identified' && codexMachine.accountScopeDigest ? { runtime: { target: 'codex', kind: 'native', version: codexMachine.runtimeVersion, accountScopeDigest: codexMachine.accountScopeDigest },
|
|
142
|
+
binaryDigest: codexMachine.binaryDigest, configDigest: codexMachine.configDigest } : claude?.[1].accountState === 'identified' && claude[1].accountScopeDigest ? {
|
|
143
|
+
runtime: { target: 'claude-code', kind: 'native', version: claude[0].version, accountScopeDigest: claude[1].accountScopeDigest },
|
|
144
|
+
binaryDigest: claude[0].binaryDigest, configDigest: claude[2]
|
|
145
|
+
} : null;
|
|
146
|
+
const receipt = scope ? (deps.readStoredEventReceipt ?? event_store_1.readStoredEventReceipt)(scope.runtime) : { state: 'absent' };
|
|
147
|
+
const readiness = (mapping, requested) => {
|
|
148
|
+
if (!scope || mapping.runtimeKind !== scope.runtime.kind || receipt.state !== 'present')
|
|
149
|
+
return { selection: 'untested', actualModel: 'untested' };
|
|
150
|
+
if ((0, capabilities_v2_1.evaluateEventReceipt)(receipt.receipt, scope, requested, (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, requested), new Date()).state !== 'current')
|
|
151
|
+
return { selection: 'untested', actualModel: 'untested' };
|
|
152
|
+
const claim = receipt.receipt.claims.find(item => sameSelection(item.selection, requested));
|
|
153
|
+
return { selection: 'verified', actualModel: requested.selector.kind === 'model' && claim?.actualModel !== 'unverified' && claim?.actualModel?.id === requested.selector.id ? 'observed' : 'untested' };
|
|
154
|
+
};
|
|
155
|
+
const selections = mappings.flatMap(mapping => [
|
|
156
|
+
...Object.entries(mapping.profiles).map(([role, requested]) => ({ runtimeKind: mapping.runtimeKind, role, requested, catalogAvailable: catalog ? catalog.selections.some(available => sameSelection(requested, available)) : null, dispatch: readiness(mapping, requested).selection, acceptedSelection: readiness(mapping, requested).selection, actualModel: readiness(mapping, requested).actualModel })),
|
|
157
|
+
{ runtimeKind: mapping.runtimeKind, role: 'fullCapability', requested: mapping.fullCapability, catalogAvailable: catalog ? catalog.selections.some(available => sameSelection(mapping.fullCapability, available)) : null, dispatch: readiness(mapping, mapping.fullCapability).selection, acceptedSelection: readiness(mapping, mapping.fullCapability).selection, actualModel: readiness(mapping, mapping.fullCapability).actualModel },
|
|
158
|
+
]);
|
|
159
|
+
const hookStatus = options.provider === 'claude-code' ? (0, claude_1.computeClaudeHookStatus)('claude-code').overall : null;
|
|
160
|
+
const hookDiagnostic = options.provider === 'claude-code' ? (0, hook_diagnostics_1.readRoutingHookStatus)() : null;
|
|
161
|
+
const ready = selections.length > 0 && selections.every(item => item.dispatch === 'verified' && (item.catalogAvailable === true || (options.provider === 'claude-code' && item.catalogAvailable === null)))
|
|
162
|
+
&& mappings.every(mapping => mapping.degradation.allowMissingObservedIdentity || selections.filter(item => item.runtimeKind === mapping.runtimeKind).every(item => item.actualModel === 'observed'))
|
|
163
|
+
&& (hookStatus === null || hookStatus === 'HEALTHY') && (hookDiagnostic === null || hookDiagnostic.state !== 'failed' && hookDiagnostic.state !== 'invalid');
|
|
164
|
+
const actualModelVerified = selections.length > 0 && selections.every(item => item.actualModel === 'observed');
|
|
165
|
+
output({ state: ready ? 'ready-operational' : 'setup-pending', provider: options.provider, machine, selections, hookStatus, hookDiagnostic, nativeDispatchVerified: ready, actualModelVerified, inferenceDispatched: false, tokenUsage: 'unknown', policyAction: mappings.some(mapping => !mapping.degradation.allowMissingObservedIdentity && selections.some(item => item.runtimeKind === mapping.runtimeKind && item.actualModel !== 'observed')) ? 'Review and explicitly approve allowMissingObservedIdentity only if operational/degraded routing without backend identity is acceptable.' : null, remedy: ready ? null : options.provider === 'codex' ? 'Catalog coverage is not native dispatch proof. Capture each missing selection from a recent completed native child with awm model-policy capture.' : hookStatus !== 'HEALTHY' ? 'Run awm hooks install --agent claude-code. For each approved runtime-default model create one named awm-* custom agent with explicit full model ID in frontmatter, dispatch it once, then rerun setup.' : hookDiagnostic?.state === 'failed' || hookDiagnostic?.state === 'invalid' ? 'Claude hook capture failed; inspect hookDiagnostic and rerun one native awm-* subagent after fixing its named cause.' : 'Claude native selection is UNTESTED; create a named awm-* custom agent with an explicit full model ID for each approved runtime-default selection and dispatch it once.' }, options.json === true);
|
|
166
|
+
if (!ready)
|
|
167
|
+
process.exitCode = 2;
|
|
168
|
+
});
|
|
169
|
+
command.command('capture').description('enroll a completed native child turn for an approved Codex selection; no inference').requiredOption('--provider <target>').requiredOption('--runtime-kind <kind>').requiredOption('--parent-thread-id <id>').requiredOption('--child-thread-id <id>').option('--cwd <path>').option('--json').action(async (options) => {
|
|
170
|
+
text(options.provider, '--provider');
|
|
171
|
+
text(options.runtimeKind, '--runtime-kind');
|
|
172
|
+
text(options.parentThreadId, '--parent-thread-id');
|
|
173
|
+
text(options.childThreadId, '--child-thread-id');
|
|
174
|
+
if (options.cwd !== undefined)
|
|
175
|
+
text(options.cwd, '--cwd');
|
|
176
|
+
if (options.provider !== 'codex' || !/^[a-z0-9][a-z0-9_-]{0,127}$/.test(options.runtimeKind))
|
|
177
|
+
throw new Error('native capture supports only a valid Codex runtime kind');
|
|
178
|
+
const cwd = path_1.default.resolve(options.cwd ?? process.cwd());
|
|
179
|
+
const effective = read(cwd);
|
|
180
|
+
if (effective.state !== 'approved')
|
|
181
|
+
throw new Error('native capture requires an approved routing policy');
|
|
182
|
+
const mapping = effective.policy.content.mappings.find(row => row.target === 'codex' && row.runtimeKind === options.runtimeKind);
|
|
183
|
+
if (!mapping)
|
|
184
|
+
throw new Error('approved Codex runtime mapping is absent');
|
|
185
|
+
const key = (deps.ensureMachineKey ?? machine_key_1.ensureMachineKey)((0, paths_1.awmHome)());
|
|
186
|
+
const [machine, runtime, observation] = await Promise.all([
|
|
187
|
+
(deps.queryCodexMachineFacts ?? native_codex_machine_1.queryCodexMachineFacts)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000, cwd, key }),
|
|
188
|
+
(deps.queryRuntimeExecutable ?? native_runtime_1.queryRuntimeExecutable)({ command: 'codex', args: ['--version'], timeoutMs: 10000 }),
|
|
189
|
+
(deps.queryCodexChildThreadObservation ?? native_codex_1.queryCodexChildThreadObservation)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000,
|
|
190
|
+
parentThreadId: options.parentThreadId, childThreadId: options.childThreadId, codexHome: path_1.default.resolve(process.env.CODEX_HOME ?? path_1.default.join(os_1.default.homedir(), '.codex')) }),
|
|
191
|
+
]);
|
|
192
|
+
if (machine.accountState !== 'identified' || !machine.accountScopeDigest)
|
|
193
|
+
throw new Error('Codex account scope is unverified; native capture cannot certify this machine');
|
|
194
|
+
const selections = [...Object.values(mapping.profiles), mapping.fullCapability];
|
|
195
|
+
const expectedSelection = selections.find(candidate => sameSelection(candidate, observation.configuredSelection));
|
|
196
|
+
if (!expectedSelection)
|
|
197
|
+
throw new Error('Codex native child selection is not approved for this runtime');
|
|
198
|
+
const mappingDigest = (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, expectedSelection);
|
|
199
|
+
const receipt = (deps.publishCodexObservation ?? event_store_1.publishCodexObservation)({ scope: { runtime: { target: 'codex', kind: options.runtimeKind, version: runtime.version, accountScopeDigest: machine.accountScopeDigest },
|
|
200
|
+
binaryDigest: runtime.binaryDigest, configDigest: machine.configDigest }, observation, expectedSelection, mappingDigest, now: new Date() });
|
|
201
|
+
output({ state: 'captured', provider: 'codex', selection: expectedSelection, receipt, actualModelVerified: false, tokenUsage: 'unknown', inferenceDispatched: false }, options.json === true);
|
|
202
|
+
});
|
|
203
|
+
command.command('hook-event').description('collect Claude native subagent lifecycle metadata; no inference').requiredOption('--event <start-or-stop>').option('--cwd <path>').option('--json').action(async (options) => {
|
|
204
|
+
if (options.event !== 'start' && options.event !== 'stop')
|
|
205
|
+
throw new Error('--event must be start or stop');
|
|
206
|
+
if (options.cwd !== undefined)
|
|
207
|
+
text(options.cwd, '--cwd');
|
|
208
|
+
const cwd = path_1.default.resolve(options.cwd ?? process.cwd());
|
|
209
|
+
const effective = read(cwd);
|
|
210
|
+
if (effective.state !== 'approved') {
|
|
211
|
+
if (options.json)
|
|
212
|
+
output({ state: 'ignored', reason: 'approved policy absent', inferenceDispatched: false }, true);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const mappings = effective.policy.content.mappings.filter(row => row.target === 'claude-code' && row.runtimeKind === 'native');
|
|
216
|
+
if (mappings.length !== 1) {
|
|
217
|
+
if (options.json)
|
|
218
|
+
output({ state: 'ignored', reason: 'unique Claude native mapping absent', inferenceDispatched: false }, true);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let stage = 'HOOK_INPUT_INVALID';
|
|
222
|
+
try {
|
|
223
|
+
const value = await hookStdin();
|
|
224
|
+
const agentType = value && typeof value === 'object' && !Array.isArray(value) ? value.agent_type : undefined;
|
|
225
|
+
if (typeof agentType === 'string' && !agentType.startsWith('awm-')) {
|
|
226
|
+
if (options.json)
|
|
227
|
+
output({ state: 'ignored', reason: 'non-AWM agent type', inferenceDispatched: false }, true);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (options.event === 'start') {
|
|
231
|
+
stage = 'HOOK_START_UNVERIFIED';
|
|
232
|
+
(0, claude_hook_events_1.recordClaudeHookStart)(value, new Date());
|
|
233
|
+
(0, hook_diagnostics_1.recordRoutingHookStatus)('start', 'success');
|
|
234
|
+
if (options.json)
|
|
235
|
+
output({ state: 'recorded', event: 'SubagentStart', inferenceDispatched: false }, true);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const now = new Date();
|
|
239
|
+
stage = 'HOOK_PAIR_UNVERIFIED';
|
|
240
|
+
const pair = (0, claude_hook_events_1.readClaudeHookPair)(value, now);
|
|
241
|
+
const configDir = path_1.default.resolve(process.env.CLAUDE_CONFIG_DIR ?? path_1.default.join(os_1.default.homedir(), '.claude'));
|
|
242
|
+
stage = 'HOOK_TRANSCRIPT_UNVERIFIED';
|
|
243
|
+
const transcript = (0, native_claude_transcript_1.readClaudeSubagentTranscript)({ configDir, file: pair.transcriptPath, agentId: pair.lifecycle.agentId });
|
|
244
|
+
stage = 'HOOK_DECLARED_MODEL_UNVERIFIED';
|
|
245
|
+
const declaredAgent = (0, native_claude_agent_definition_1.readClaudeAgentDeclaredModel)({ cwd, configDir, agentType: pair.lifecycle.agentType });
|
|
246
|
+
if (declaredAgent.modelId !== transcript.actualModel)
|
|
247
|
+
throw new Error('Claude declared model differs from native child model');
|
|
248
|
+
const mapping = mappings[0];
|
|
249
|
+
const selections = [...Object.values(mapping.profiles), mapping.fullCapability];
|
|
250
|
+
const expectedSelection = selections.find(candidate => candidate.selector.kind === 'model' && candidate.selector.id === transcript.actualModel && candidate.effort.kind === 'runtime-default');
|
|
251
|
+
if (!expectedSelection) {
|
|
252
|
+
if (options.json)
|
|
253
|
+
output({ state: 'ignored', reason: 'Claude subagent selection does not match an approved runtime-default mapping', actualModel: transcript.actualModel, inferenceDispatched: false }, true);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
stage = 'HOOK_SCOPE_UNVERIFIED';
|
|
257
|
+
const local = await (0, local_claude_scope_1.queryLocalClaudeScope)(cwd, mapping.runtimeKind, { requireHookHealthy: false });
|
|
258
|
+
if (local.state !== 'current')
|
|
259
|
+
throw new Error('Claude native scope is unverified');
|
|
260
|
+
stage = 'HOOK_RECEIPT_REJECTED';
|
|
261
|
+
const receipt = (0, event_store_2.publishClaudeObservation)({ scope: local.scope, lifecycle: pair.lifecycle, transcript, declaredAgent, expectedSelection,
|
|
262
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, expectedSelection), now });
|
|
263
|
+
(0, hook_diagnostics_1.recordRoutingHookStatus)('stop', 'success');
|
|
264
|
+
if (options.json)
|
|
265
|
+
output({ state: 'captured', provider: 'claude-code', selection: expectedSelection, receipt,
|
|
266
|
+
actualModelVerified: true, tokenUsage: 'unknown', inferenceDispatched: false }, true);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
let alert = true;
|
|
270
|
+
try {
|
|
271
|
+
alert = (0, hook_diagnostics_1.recordRoutingHookStatus)(options.event, stage);
|
|
272
|
+
}
|
|
273
|
+
catch { /* stderr still exposes failure */ }
|
|
274
|
+
if (alert)
|
|
275
|
+
process.stderr.write(`AWM Claude routing capture: ${stage}; inspect awm model-policy setup --provider claude-code --json\n`);
|
|
276
|
+
if (options.json)
|
|
277
|
+
output({ state: 'failed', reasonCode: stage, inferenceDispatched: false }, true);
|
|
278
|
+
// A hook is observability, never a reason to block the native work.
|
|
279
|
+
}
|
|
280
|
+
});
|
|
28
281
|
// Approval demands --expected-digest so that it stays an explicit operator
|
|
29
282
|
// act. Without a way to observe the digest first, that safeguard blocked the
|
|
30
283
|
// legitimate path instead of gating it, and the only way through was to
|