agentic-workflow-manager 9.11.12 → 9.12.1
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/gate.js +6 -1
- package/dist/src/commands/job/index.js +61 -5
- package/dist/src/commands/model-policy/index.js +253 -0
- package/dist/src/commands/plan/index.js +53 -9
- package/dist/src/commands/preflight/checks.js +18 -0
- package/dist/src/commands/watch/apply.js +69 -5
- package/dist/src/commands/watch/archive-unused.js +2 -0
- package/dist/src/commands/watch/index.js +24 -0
- package/dist/src/commands/watch/init.js +2 -1
- package/dist/src/commands/watch/request-recovery.js +153 -0
- package/dist/src/commands/watch/supervisor.js +70 -6
- package/dist/src/core/admission/index.js +20 -6
- package/dist/src/core/journal/requests.js +1 -0
- package/dist/src/core/journal/types.js +50 -6
- 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 +113 -18
- 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 +96 -6
- 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 +4 -3
- 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/preflight/preflight.test.js +65 -0
- package/dist/tests/commands/watch/apply.test.js +84 -0
- package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
- package/dist/tests/commands/watch/request-recovery.test.js +253 -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 +112 -1
- 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/setup-readiness.test.js +83 -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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeCodexRolloutSelection = void 0;
|
|
4
|
+
exports.isNativeCodexChildObservation = isNativeCodexChildObservation;
|
|
5
|
+
exports.normalizeCodexChildThreadObservation = normalizeCodexChildThreadObservation;
|
|
6
|
+
exports.normalizeCodexModelCatalog = normalizeCodexModelCatalog;
|
|
7
|
+
exports.queryCodexChildThreadObservation = queryCodexChildThreadObservation;
|
|
8
|
+
exports.queryCodexModelCatalog = queryCodexModelCatalog;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const native_codex_rollout_1 = require("./native-codex-rollout");
|
|
11
|
+
var native_codex_rollout_2 = require("./native-codex-rollout");
|
|
12
|
+
Object.defineProperty(exports, "normalizeCodexRolloutSelection", { enumerable: true, get: function () { return native_codex_rollout_2.normalizeCodexRolloutSelection; } });
|
|
13
|
+
const nativeChildObservations = new WeakMap();
|
|
14
|
+
function isNativeCodexChildObservation(value) {
|
|
15
|
+
return !!value && typeof value === 'object' && nativeChildObservations.get(value) === JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
function record(value, name) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
throw new Error(`${name} must be an object`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function boundedText(value, name) {
|
|
23
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 128 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
|
|
24
|
+
throw new Error(`${name} is invalid`);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/** Only call on a result obtained from native `thread/read` with includeTurns.
|
|
28
|
+
* A child with a completed turn proves native child execution and exposes its
|
|
29
|
+
* persisted configuration, but not the accepted per-turn or backend model. */
|
|
30
|
+
function normalizeCodexChildThreadObservation(value, expectedParentThreadId) {
|
|
31
|
+
const parent = boundedText(expectedParentThreadId, 'expected parent thread id');
|
|
32
|
+
const response = record(value, 'Codex thread/read response');
|
|
33
|
+
const thread = record(response.thread, 'Codex child thread');
|
|
34
|
+
const childThreadId = boundedText(thread.id, 'Codex child thread id');
|
|
35
|
+
const parentThreadId = boundedText(thread.parentThreadId, 'Codex parent thread id');
|
|
36
|
+
if (parentThreadId !== parent || childThreadId === parent)
|
|
37
|
+
throw new Error('Codex child parent thread mismatch');
|
|
38
|
+
const model = boundedText(thread.model, 'Codex child configured model');
|
|
39
|
+
const effort = boundedText(thread.reasoningEffort, 'Codex child configured effort');
|
|
40
|
+
if (!Array.isArray(thread.turns) || thread.turns.length === 0 || thread.turns.length > 256)
|
|
41
|
+
throw new Error('Codex child turns are invalid');
|
|
42
|
+
const completed = thread.turns.find((raw) => {
|
|
43
|
+
const turn = record(raw, 'Codex child turn');
|
|
44
|
+
boundedText(turn.id, 'Codex child turn id');
|
|
45
|
+
if (!Array.isArray(turn.items) || turn.items.length > 4096)
|
|
46
|
+
throw new Error('Codex child turn items are invalid');
|
|
47
|
+
if (typeof turn.status !== 'string')
|
|
48
|
+
throw new Error('Codex child turn status is invalid');
|
|
49
|
+
return turn.status === 'completed';
|
|
50
|
+
});
|
|
51
|
+
if (!completed)
|
|
52
|
+
throw new Error('Codex child has no completed turn');
|
|
53
|
+
return { provenance: 'codex-app-server-thread-read', parentThreadId, childThreadId, turnId: completed.id,
|
|
54
|
+
nativeDispatchVerified: true, configuredSelection: { selector: { kind: 'model', id: model }, effort: { kind: 'explicit', value: effort } },
|
|
55
|
+
acceptedSelectionVerified: false, actualModelVerified: false, tokenUsage: 'unknown' };
|
|
56
|
+
}
|
|
57
|
+
/** Normalize only the provider's catalog. This cannot attest dispatch or actual backend identity. */
|
|
58
|
+
function normalizeCodexModelCatalog(value) {
|
|
59
|
+
const response = record(value, 'Codex model/list response');
|
|
60
|
+
if (!Array.isArray(response.data) || response.data.length > 256)
|
|
61
|
+
throw new Error('Codex model catalog is invalid');
|
|
62
|
+
const selections = [];
|
|
63
|
+
const modelIds = new Set();
|
|
64
|
+
const selectionKeys = new Set();
|
|
65
|
+
for (const [index, raw] of response.data.entries()) {
|
|
66
|
+
const model = record(raw, `Codex model[${index}]`);
|
|
67
|
+
const catalogId = boundedText(model.id, `Codex model[${index}].id`);
|
|
68
|
+
const slug = boundedText(model.model, `Codex model[${index}].model`);
|
|
69
|
+
if (modelIds.has(catalogId))
|
|
70
|
+
throw new Error(`Codex catalog contains duplicate model id ${catalogId}`);
|
|
71
|
+
modelIds.add(catalogId);
|
|
72
|
+
if (typeof model.hidden !== 'boolean')
|
|
73
|
+
throw new Error(`Codex model[${index}].hidden is invalid`);
|
|
74
|
+
if (!Array.isArray(model.supportedReasoningEfforts) || model.supportedReasoningEfforts.length > 20)
|
|
75
|
+
throw new Error(`Codex model[${index}] supported efforts are invalid`);
|
|
76
|
+
if (model.hidden)
|
|
77
|
+
continue;
|
|
78
|
+
for (const [effortIndex, rawEffort] of model.supportedReasoningEfforts.entries()) {
|
|
79
|
+
const option = record(rawEffort, `Codex model[${index}] effort[${effortIndex}]`);
|
|
80
|
+
const effort = boundedText(option.reasoningEffort, `Codex model[${index}] effort[${effortIndex}]`);
|
|
81
|
+
boundedText(option.description, `Codex model[${index}] effort description`);
|
|
82
|
+
const key = `${slug}\0${effort}`;
|
|
83
|
+
if (selectionKeys.has(key))
|
|
84
|
+
throw new Error(`Codex catalog contains duplicate selection ${slug}/${effort}`);
|
|
85
|
+
selectionKeys.add(key);
|
|
86
|
+
selections.push({ selector: { kind: 'model', id: slug }, effort: { kind: 'explicit', value: effort } });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { provenance: 'native-catalog', selections, nativeDispatchVerified: false, actualModelVerified: false };
|
|
90
|
+
}
|
|
91
|
+
/** Explicit, read-only native observation of an existing child. It never starts
|
|
92
|
+
* a thread or turn, and only returns the bounded normalized fields. */
|
|
93
|
+
function queryCodexChildThreadObservation(input) {
|
|
94
|
+
if (!input || typeof input !== 'object' || typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
|
|
95
|
+
|| !Array.isArray(input.args) || input.args.length > 16 || input.args.some(arg => typeof arg !== 'string' || arg.length > 65536)
|
|
96
|
+
|| !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000)
|
|
97
|
+
throw new Error('Codex child thread query is invalid');
|
|
98
|
+
const parentThreadId = boundedText(input.parentThreadId, 'parent thread id');
|
|
99
|
+
const childThreadId = boundedText(input.childThreadId, 'child thread id');
|
|
100
|
+
if (parentThreadId === childThreadId)
|
|
101
|
+
throw new Error('Codex child and parent thread ids must differ');
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false });
|
|
104
|
+
let settled = false;
|
|
105
|
+
let buffer = '';
|
|
106
|
+
let expectedId = 1;
|
|
107
|
+
const finish = (error, observed) => {
|
|
108
|
+
if (settled)
|
|
109
|
+
return;
|
|
110
|
+
settled = true;
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
child.kill();
|
|
113
|
+
if (error)
|
|
114
|
+
reject(error);
|
|
115
|
+
else {
|
|
116
|
+
nativeChildObservations.set(observed, JSON.stringify(observed));
|
|
117
|
+
resolve(observed);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const send = (request) => { if (!settled)
|
|
121
|
+
child.stdin.write(`${JSON.stringify(request)}\n`); };
|
|
122
|
+
const timer = setTimeout(() => finish(new Error('Codex child thread query timed out')), input.timeoutMs);
|
|
123
|
+
child.on('error', () => finish(new Error('Codex app-server could not be started')));
|
|
124
|
+
child.stdin.on('error', () => finish(new Error('Codex app-server input failed')));
|
|
125
|
+
child.on('exit', () => finish(new Error('Codex app-server exited before thread/read response')));
|
|
126
|
+
child.stdout.setEncoding('utf8');
|
|
127
|
+
child.stdout.on('data', (chunk) => {
|
|
128
|
+
if (settled)
|
|
129
|
+
return;
|
|
130
|
+
buffer += chunk;
|
|
131
|
+
if (buffer.length > 2 * 1024 * 1024)
|
|
132
|
+
return finish(new Error('Codex thread/read response exceeds 2 MiB'));
|
|
133
|
+
for (;;) {
|
|
134
|
+
const end = buffer.indexOf('\n');
|
|
135
|
+
if (end < 0 || settled)
|
|
136
|
+
break;
|
|
137
|
+
const line = buffer.slice(0, end).trim();
|
|
138
|
+
buffer = buffer.slice(end + 1);
|
|
139
|
+
if (!line)
|
|
140
|
+
continue;
|
|
141
|
+
let message;
|
|
142
|
+
try {
|
|
143
|
+
message = record(JSON.parse(line), 'Codex protocol response');
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return finish(new Error('Codex protocol returned invalid JSON'));
|
|
147
|
+
}
|
|
148
|
+
if (message.id === undefined)
|
|
149
|
+
continue;
|
|
150
|
+
if (message.id !== expectedId)
|
|
151
|
+
return finish(new Error('Codex protocol response id mismatch'));
|
|
152
|
+
if (message.error !== undefined)
|
|
153
|
+
return finish(new Error('Codex app-server rejected thread/read request'));
|
|
154
|
+
if (expectedId === 1) {
|
|
155
|
+
try {
|
|
156
|
+
record(message.result, 'Codex initialize result');
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return finish(new Error('Codex protocol initialize response is invalid'));
|
|
160
|
+
}
|
|
161
|
+
send({ method: 'initialized' });
|
|
162
|
+
expectedId = 2;
|
|
163
|
+
send({ id: expectedId, method: 'thread/read', params: { threadId: childThreadId, includeTurns: true } });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const result = normalizeCodexChildThreadObservation(message.result, parentThreadId);
|
|
168
|
+
if (result.childThreadId !== childThreadId)
|
|
169
|
+
throw new Error('Codex child thread id mismatch');
|
|
170
|
+
if (input.codexHome === undefined) {
|
|
171
|
+
finish(undefined, result);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const response = record(message.result, 'Codex thread/read result');
|
|
175
|
+
const thread = record(response.thread, 'Codex child thread');
|
|
176
|
+
if (typeof thread.path !== 'string')
|
|
177
|
+
throw new Error('Codex child rollout path is unavailable');
|
|
178
|
+
const turn = (0, native_codex_rollout_1.readCodexRolloutSelection)({ codexHome: input.codexHome, file: thread.path,
|
|
179
|
+
expected: { parentThreadId, childThreadId, turnId: result.turnId } });
|
|
180
|
+
if (turn.configuredSelection.selector.id !== result.configuredSelection.selector.id
|
|
181
|
+
|| turn.configuredSelection.effort.kind !== result.configuredSelection.effort.kind
|
|
182
|
+
|| (turn.configuredSelection.effort.kind === 'explicit' && result.configuredSelection.effort.kind === 'explicit'
|
|
183
|
+
&& turn.configuredSelection.effort.value !== result.configuredSelection.effort.value))
|
|
184
|
+
throw new Error('Codex child selection changed after the observed turn');
|
|
185
|
+
finish(undefined, { ...result, configuredSelection: turn.configuredSelection, acceptedSelectionVerified: true, childRuntimeVersion: turn.cliVersion, observedAt: turn.observedAt });
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
finish(error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
send({ id: 1, method: 'initialize', params: { clientInfo: { name: 'awm', title: 'AWM native observation', version: '1.0.0' } } });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** Explicit no-inference App Server discovery. Never invoked by doctor/preflight. */
|
|
196
|
+
function queryCodexModelCatalog(input) {
|
|
197
|
+
if (!input || typeof input !== 'object'
|
|
198
|
+
|| typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
|
|
199
|
+
|| !Array.isArray(input.args) || input.args.length > 16
|
|
200
|
+
|| input.args.some(arg => typeof arg !== 'string' || arg.length > 65536)
|
|
201
|
+
|| !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000) {
|
|
202
|
+
throw new Error('Codex catalog query is invalid');
|
|
203
|
+
}
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false });
|
|
206
|
+
let settled = false;
|
|
207
|
+
let buffer = '';
|
|
208
|
+
let nextRequestId = 2;
|
|
209
|
+
let pendingRequestId = 1;
|
|
210
|
+
let pages = 0;
|
|
211
|
+
const models = [];
|
|
212
|
+
const finish = (error, result) => {
|
|
213
|
+
if (settled)
|
|
214
|
+
return;
|
|
215
|
+
settled = true;
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
child.kill();
|
|
218
|
+
if (error)
|
|
219
|
+
reject(error);
|
|
220
|
+
else
|
|
221
|
+
resolve(result);
|
|
222
|
+
};
|
|
223
|
+
const send = (message) => {
|
|
224
|
+
if (settled)
|
|
225
|
+
return;
|
|
226
|
+
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
227
|
+
};
|
|
228
|
+
const timer = setTimeout(() => finish(new Error('Codex catalog query timed out')), input.timeoutMs);
|
|
229
|
+
child.on('error', () => finish(new Error('Codex app-server could not be started')));
|
|
230
|
+
child.stdin.on('error', () => finish(new Error('Codex app-server input failed')));
|
|
231
|
+
child.on('exit', () => finish(new Error('Codex app-server exited before catalog response')));
|
|
232
|
+
child.stdout.setEncoding('utf8');
|
|
233
|
+
child.stdout.on('data', (chunk) => {
|
|
234
|
+
if (settled)
|
|
235
|
+
return;
|
|
236
|
+
buffer += chunk;
|
|
237
|
+
if (buffer.length > 2 * 1024 * 1024)
|
|
238
|
+
return finish(new Error('Codex protocol response exceeds 2 MiB'));
|
|
239
|
+
for (;;) {
|
|
240
|
+
const end = buffer.indexOf('\n');
|
|
241
|
+
if (end < 0 || settled)
|
|
242
|
+
break;
|
|
243
|
+
const line = buffer.slice(0, end).trim();
|
|
244
|
+
buffer = buffer.slice(end + 1);
|
|
245
|
+
if (!line)
|
|
246
|
+
continue;
|
|
247
|
+
let message;
|
|
248
|
+
try {
|
|
249
|
+
message = record(JSON.parse(line), 'Codex protocol response');
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return finish(new Error('Codex protocol returned invalid JSON'));
|
|
253
|
+
}
|
|
254
|
+
if (message.id === undefined)
|
|
255
|
+
continue; // asynchronous notification
|
|
256
|
+
if (message.id !== pendingRequestId)
|
|
257
|
+
return finish(new Error('Codex protocol response id mismatch'));
|
|
258
|
+
if (message.error !== undefined)
|
|
259
|
+
return finish(new Error('Codex app-server rejected catalog request'));
|
|
260
|
+
if (pendingRequestId === 1) {
|
|
261
|
+
try {
|
|
262
|
+
record(message.result, 'Codex initialize result');
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return finish(new Error('Codex protocol initialize response is invalid'));
|
|
266
|
+
}
|
|
267
|
+
send({ method: 'initialized' });
|
|
268
|
+
pendingRequestId = nextRequestId++;
|
|
269
|
+
send({ id: pendingRequestId, method: 'model/list', params: { includeHidden: false, limit: 100 } });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
let page;
|
|
273
|
+
try {
|
|
274
|
+
page = record(message.result, 'Codex model/list result');
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return finish(new Error('Codex protocol model/list response is invalid'));
|
|
278
|
+
}
|
|
279
|
+
if (!Array.isArray(page.data))
|
|
280
|
+
return finish(new Error('Codex protocol model/list data is invalid'));
|
|
281
|
+
models.push(...page.data);
|
|
282
|
+
pages += 1;
|
|
283
|
+
if (models.length > 256 || pages > 8)
|
|
284
|
+
return finish(new Error('Codex model catalog exceeds discovery limit'));
|
|
285
|
+
if (page.nextCursor === null || page.nextCursor === undefined) {
|
|
286
|
+
try {
|
|
287
|
+
finish(undefined, normalizeCodexModelCatalog({ data: models }));
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
finish(error);
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (typeof page.nextCursor !== 'string' || page.nextCursor.length === 0 || page.nextCursor.length > 1024)
|
|
295
|
+
return finish(new Error('Codex protocol cursor is invalid'));
|
|
296
|
+
pendingRequestId = nextRequestId++;
|
|
297
|
+
send({ id: pendingRequestId, method: 'model/list', params: { includeHidden: false, limit: 100, cursor: page.nextCursor } });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
send({ id: 1, method: 'initialize', params: { clientInfo: { name: 'awm', title: 'AWM discovery', version: '1.0.0' } } });
|
|
301
|
+
});
|
|
302
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.observeNativeRoutingChild = observeNativeRoutingChild;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const paths_1 = require("../paths");
|
|
11
|
+
const capabilities_v2_1 = require("./capabilities-v2");
|
|
12
|
+
const event_store_1 = require("./event-store");
|
|
13
|
+
const local_event_scope_1 = require("./local-event-scope");
|
|
14
|
+
const machine_key_1 = require("./machine-key");
|
|
15
|
+
const capabilities_1 = require("./capabilities");
|
|
16
|
+
const native_codex_1 = require("./native-codex");
|
|
17
|
+
const native_routing_proof_1 = require("./native-routing-proof");
|
|
18
|
+
const selection_policy_digest_1 = require("./selection-policy-digest");
|
|
19
|
+
const store_1 = require("./store");
|
|
20
|
+
function sameSelection(left, right) {
|
|
21
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
22
|
+
}
|
|
23
|
+
/** A bounded, read-mostly native observation for an already-dispatched child.
|
|
24
|
+
* Codex may refresh only its matching claim; Claude's Stop hook is the writer.
|
|
25
|
+
* No inference is started here. */
|
|
26
|
+
async function observeNativeRoutingChild(input) {
|
|
27
|
+
if (!input || !input.attempt?.nativeEvidenceRequired || !input.attempt.reservedAt || input.attempt.state !== 'reserved'
|
|
28
|
+
|| typeof input.nativeAgentId !== 'string' || input.nativeAgentId.length === 0 || input.nativeAgentId.length > 128
|
|
29
|
+
|| typeof input.cwd !== 'string' || !path_1.default.isAbsolute(input.cwd) || !(input.now instanceof Date) || !Number.isFinite(input.now.getTime())
|
|
30
|
+
|| !Array.isArray(input.consumedEventDigests) || input.consumedEventDigests.length > 4096
|
|
31
|
+
|| input.consumedEventDigests.some(item => typeof item !== 'string' || !/^[a-f0-9]{64}$/.test(item)))
|
|
32
|
+
throw new Error('native routing observation is invalid');
|
|
33
|
+
const key = (0, machine_key_1.readMachineKey)((0, paths_1.awmHome)());
|
|
34
|
+
if (!key)
|
|
35
|
+
throw new Error('native routing machine key is missing');
|
|
36
|
+
const envelope = input.attempt.envelope;
|
|
37
|
+
const local = await (0, local_event_scope_1.queryLocalEventScope)(input.cwd, envelope.runtime.target, envelope.runtime.kind);
|
|
38
|
+
if (local.state !== 'current' || JSON.stringify(local.scope.runtime) !== JSON.stringify(envelope.runtime))
|
|
39
|
+
throw new Error('native routing runtime or account scope drifted');
|
|
40
|
+
const policy = (0, store_1.readEffectivePolicy)(input.cwd);
|
|
41
|
+
if (policy.state !== 'approved' || policy.policy.contentDigest !== envelope.policyDigest)
|
|
42
|
+
throw new Error('native routing policy drifted');
|
|
43
|
+
const mapping = policy.policy.content.mappings.find(row => row.target === envelope.runtime.target && row.runtimeKind === envelope.runtime.kind);
|
|
44
|
+
if (!mapping)
|
|
45
|
+
throw new Error('native routing mapping is absent');
|
|
46
|
+
const receipt = (0, event_store_1.readStoredEventReceipt)((0, capabilities_1.validateRuntimeKey)(envelope.runtime));
|
|
47
|
+
if (receipt.state !== 'present')
|
|
48
|
+
throw new Error('native routing receipt is absent or invalid');
|
|
49
|
+
const currentness = (0, capabilities_v2_1.evaluateEventReceipt)(receipt.receipt, local.scope, envelope.resolved, (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, envelope.resolved), input.now);
|
|
50
|
+
if (currentness.state !== 'current')
|
|
51
|
+
throw new Error(`native routing receipt drifted: ${currentness.reason}`);
|
|
52
|
+
let observed;
|
|
53
|
+
let observedAt;
|
|
54
|
+
let eventDigest;
|
|
55
|
+
let source;
|
|
56
|
+
let actualModel;
|
|
57
|
+
if (envelope.runtime.target === 'codex') {
|
|
58
|
+
if (typeof input.parentThreadId !== 'string' || input.parentThreadId.length === 0 || input.parentThreadId.length > 128)
|
|
59
|
+
throw new Error('Codex parent thread identity is required');
|
|
60
|
+
const child = await (0, native_codex_1.queryCodexChildThreadObservation)({ command: 'codex', args: ['app-server', '--stdio'], timeoutMs: 10000,
|
|
61
|
+
parentThreadId: input.parentThreadId, childThreadId: input.nativeAgentId,
|
|
62
|
+
codexHome: path_1.default.resolve(process.env.CODEX_HOME ?? path_1.default.join(os_1.default.homedir(), '.codex')) });
|
|
63
|
+
if (!child.acceptedSelectionVerified || child.childRuntimeVersion !== envelope.runtime.version || !child.observedAt)
|
|
64
|
+
throw new Error('Codex child turn selection is not natively verified');
|
|
65
|
+
observed = child.configuredSelection;
|
|
66
|
+
observedAt = child.observedAt;
|
|
67
|
+
const codexObservedMs = Date.parse(observedAt);
|
|
68
|
+
if (!Number.isFinite(codexObservedMs) || codexObservedMs < Date.parse(input.attempt.reservedAt)
|
|
69
|
+
|| codexObservedMs > input.now.getTime() + 60_000 || input.now.getTime() - codexObservedMs > 15 * 60_000)
|
|
70
|
+
throw new Error('Codex native child turn is stale or predates reservation');
|
|
71
|
+
eventDigest = (0, crypto_1.createHash)('sha256').update(JSON.stringify({ parent: child.parentThreadId, child: child.childThreadId,
|
|
72
|
+
turn: child.turnId, selection: observed, runtimeVersion: child.childRuntimeVersion,
|
|
73
|
+
accountScopeDigest: local.scope.runtime.accountScopeDigest })).digest('hex');
|
|
74
|
+
source = 'codex-turn-context';
|
|
75
|
+
if (input.consumedEventDigests.includes(eventDigest))
|
|
76
|
+
throw new Error('Codex native child turn was already consumed');
|
|
77
|
+
if (sameSelection(observed, envelope.resolved)) {
|
|
78
|
+
(0, event_store_1.publishCodexObservation)({ scope: local.scope, observation: child, expectedSelection: envelope.resolved,
|
|
79
|
+
mappingDigest: (0, selection_policy_digest_1.selectionPolicyDigest)(mapping, envelope.resolved), now: input.now });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (envelope.runtime.target === 'claude-code') {
|
|
83
|
+
const claim = receipt.receipt.claims.find(item => sameSelection(item.selection, envelope.resolved));
|
|
84
|
+
const agentDigest = (0, event_store_1.claudeAgentIdDigest)(input.nativeAgentId, key);
|
|
85
|
+
const event = claim?.nativeEvents?.find(item => item.nativeAgentIdDigest === agentDigest)
|
|
86
|
+
?? (claim?.nativeAgentIdDigest === agentDigest ? { eventDigest: claim.eventDigest, observedAt: claim.observedAt } : undefined);
|
|
87
|
+
if (!claim || claim.source !== 'claude-assistant-transcript' || claim.nativeAgentType !== envelope.nativeAgentType || !event)
|
|
88
|
+
throw new Error('Claude Stop hook does not match this routed native child');
|
|
89
|
+
observed = claim.selection;
|
|
90
|
+
observedAt = event.observedAt;
|
|
91
|
+
eventDigest = event.eventDigest;
|
|
92
|
+
source = 'claude-assistant-transcript';
|
|
93
|
+
if (claim.actualModel !== 'unverified')
|
|
94
|
+
actualModel = claim.actualModel.id;
|
|
95
|
+
if (input.consumedEventDigests.includes(eventDigest))
|
|
96
|
+
throw new Error('Claude native child event was already consumed');
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
throw new Error('native routing provider is unsupported');
|
|
100
|
+
const observedMs = Date.parse(observedAt);
|
|
101
|
+
if (!Number.isFinite(observedMs) || observedMs < Date.parse(input.attempt.reservedAt)
|
|
102
|
+
|| observedMs > input.now.getTime() + 60_000 || input.now.getTime() - observedMs > 15 * 60_000)
|
|
103
|
+
throw new Error('native routing event is stale or predates reservation');
|
|
104
|
+
return (0, native_routing_proof_1.signNativeRoutingProof)({ attemptId: input.attempt.id, nativeAgentId: input.nativeAgentId, source,
|
|
105
|
+
eventDigest, observedAt, selection: observed, ...(actualModel === undefined ? {} : { actualModel }) }, key);
|
|
106
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signNativeRoutingProof = signNativeRoutingProof;
|
|
4
|
+
exports.verifyNativeRoutingProof = verifyNativeRoutingProof;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const types_1 = require("../journal/types");
|
|
7
|
+
const DOMAIN = 'AWM native routing attempt/v1\0';
|
|
8
|
+
function validBody(value) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
return false;
|
|
11
|
+
const row = value;
|
|
12
|
+
return Object.keys(row).length === (row.actualModel === undefined ? 6 : 7) && Object.keys(row).every(key => ['attemptId', 'nativeAgentId', 'source', 'eventDigest', 'observedAt', 'selection', 'actualModel'].includes(key))
|
|
13
|
+
&& typeof row.attemptId === 'string' && row.attemptId.length > 0 && row.attemptId.length <= 128
|
|
14
|
+
&& typeof row.nativeAgentId === 'string' && row.nativeAgentId.length > 0 && row.nativeAgentId.length <= 128
|
|
15
|
+
&& (row.source === 'codex-turn-context' || row.source === 'claude-assistant-transcript')
|
|
16
|
+
&& typeof row.eventDigest === 'string' && /^[a-f0-9]{64}$/.test(row.eventDigest)
|
|
17
|
+
&& typeof row.observedAt === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(row.observedAt)
|
|
18
|
+
&& Number.isFinite(Date.parse(row.observedAt)) && (0, types_1.isRoutingSelection)(row.selection)
|
|
19
|
+
&& (row.actualModel === undefined || (row.source === 'claude-assistant-transcript' && typeof row.actualModel === 'string' && row.actualModel.length > 0 && row.actualModel.length <= 128));
|
|
20
|
+
}
|
|
21
|
+
function signature(body, key) {
|
|
22
|
+
if (!Buffer.isBuffer(key) || key.length !== 32)
|
|
23
|
+
throw new Error('native routing machine key is invalid');
|
|
24
|
+
return (0, crypto_1.createHmac)('sha256', key).update(DOMAIN).update(JSON.stringify(body)).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
function signNativeRoutingProof(input, key) {
|
|
27
|
+
if (!validBody(input))
|
|
28
|
+
throw new Error('native routing proof body is invalid');
|
|
29
|
+
const body = { attemptId: input.attemptId, nativeAgentId: input.nativeAgentId, source: input.source,
|
|
30
|
+
eventDigest: input.eventDigest, observedAt: input.observedAt, selection: input.selection,
|
|
31
|
+
...(input.actualModel === undefined ? {} : { actualModel: input.actualModel }) };
|
|
32
|
+
return { schema: 'native-routing-proof/v1', ...body, mac: signature(body, key) };
|
|
33
|
+
}
|
|
34
|
+
function verifyNativeRoutingProof(value, expected, key, now) {
|
|
35
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || !expected || typeof expected !== 'object'
|
|
36
|
+
|| !(now instanceof Date) || !Number.isFinite(now.getTime()))
|
|
37
|
+
return false;
|
|
38
|
+
const row = value;
|
|
39
|
+
if (Object.keys(row).length !== (row.actualModel === undefined ? 8 : 9) || row.schema !== 'native-routing-proof/v1' || typeof row.mac !== 'string' || !/^[a-f0-9]{64}$/.test(row.mac))
|
|
40
|
+
return false;
|
|
41
|
+
const body = { attemptId: row.attemptId, nativeAgentId: row.nativeAgentId, source: row.source,
|
|
42
|
+
eventDigest: row.eventDigest, observedAt: row.observedAt, selection: row.selection,
|
|
43
|
+
...(row.actualModel === undefined ? {} : { actualModel: row.actualModel }) };
|
|
44
|
+
if (!validBody(body) || body.attemptId !== expected.attemptId || body.nativeAgentId !== expected.nativeAgentId
|
|
45
|
+
|| !(0, types_1.isRoutingSelection)(expected.selection)
|
|
46
|
+
|| typeof expected.reservedAt !== 'string' || !Number.isFinite(Date.parse(expected.reservedAt)))
|
|
47
|
+
return false;
|
|
48
|
+
const observedMs = Date.parse(body.observedAt);
|
|
49
|
+
if (observedMs < Date.parse(expected.reservedAt) || observedMs > now.getTime() + 60_000 || now.getTime() - observedMs > 15 * 60_000)
|
|
50
|
+
return false;
|
|
51
|
+
return (0, crypto_1.timingSafeEqual)(Buffer.from(row.mac, 'hex'), Buffer.from(signature(body, key), 'hex'));
|
|
52
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queryRuntimeExecutable = queryRuntimeExecutable;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function executablePath(command) {
|
|
12
|
+
if (typeof command !== 'string' || command.length === 0 || command.length > 4096 || /[\u0000-\u001f\u007f-\u009f]/.test(command))
|
|
13
|
+
throw new Error('runtime executable command is invalid');
|
|
14
|
+
const names = process.platform === 'win32' && path_1.default.extname(command) === '' ? [command, `${command}.exe`] : [command];
|
|
15
|
+
const candidates = path_1.default.isAbsolute(command) ? names : (process.env.PATH ?? '').split(path_1.default.delimiter).filter(Boolean).flatMap(dir => names.map(name => path_1.default.join(dir, name)));
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
try {
|
|
18
|
+
fs_1.default.accessSync(candidate, fs_1.default.constants.X_OK);
|
|
19
|
+
const real = fs_1.default.realpathSync(candidate);
|
|
20
|
+
if (!fs_1.default.statSync(real).isFile())
|
|
21
|
+
continue;
|
|
22
|
+
return real;
|
|
23
|
+
}
|
|
24
|
+
catch { /* keep looking */ }
|
|
25
|
+
}
|
|
26
|
+
throw new Error('runtime executable is unavailable');
|
|
27
|
+
}
|
|
28
|
+
function versionOf(file, timeoutMs) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const child = (0, child_process_1.spawn)(file, ['--version'], { stdio: ['ignore', 'pipe', 'pipe'], shell: false });
|
|
31
|
+
let settled = false;
|
|
32
|
+
let output = '';
|
|
33
|
+
const finish = (error, version) => {
|
|
34
|
+
if (settled)
|
|
35
|
+
return;
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
if (error) {
|
|
39
|
+
child.kill();
|
|
40
|
+
reject(error);
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
resolve(version);
|
|
44
|
+
};
|
|
45
|
+
const timer = setTimeout(() => finish(new Error('runtime version query timed out')), timeoutMs);
|
|
46
|
+
child.on('error', () => finish(new Error('runtime executable could not start')));
|
|
47
|
+
child.stdout.setEncoding('utf8');
|
|
48
|
+
child.stdout.on('data', (chunk) => { output += chunk; if (output.length > 4096)
|
|
49
|
+
finish(new Error('runtime version output exceeds limit')); });
|
|
50
|
+
child.on('exit', code => {
|
|
51
|
+
if (code !== 0)
|
|
52
|
+
return finish(new Error('runtime version command failed'));
|
|
53
|
+
const match = output.match(/\b(?:v)?(\d+\.\d+\.\d+)\b/);
|
|
54
|
+
if (!match)
|
|
55
|
+
return finish(new Error('runtime version is unavailable'));
|
|
56
|
+
finish(undefined, match[1]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function hashFile(file) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const before = fs_1.default.statSync(file);
|
|
63
|
+
if (!before.isFile() || before.size > 512 * 1024 * 1024)
|
|
64
|
+
return reject(new Error('runtime executable file is invalid or too large'));
|
|
65
|
+
const hash = (0, crypto_1.createHash)('sha256');
|
|
66
|
+
const stream = fs_1.default.createReadStream(file);
|
|
67
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
68
|
+
stream.on('error', () => reject(new Error('runtime executable could not be read')));
|
|
69
|
+
stream.on('end', () => {
|
|
70
|
+
const after = fs_1.default.statSync(file);
|
|
71
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs)
|
|
72
|
+
return reject(new Error('runtime executable changed while hashing'));
|
|
73
|
+
resolve(hash.digest('hex'));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Fingerprint the executable reached by PATH and query only --version. */
|
|
78
|
+
async function queryRuntimeExecutable(input) {
|
|
79
|
+
if (!input || typeof input !== 'object' || !Array.isArray(input.args) || input.args.length !== 1 || input.args[0] !== '--version'
|
|
80
|
+
|| !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000)
|
|
81
|
+
throw new Error('runtime version query is invalid');
|
|
82
|
+
const file = executablePath(input.command);
|
|
83
|
+
const version = await versionOf(file, input.timeoutMs);
|
|
84
|
+
const binaryDigest = await hashFile(file);
|
|
85
|
+
return { version, binaryDigest, inferenceDispatched: false };
|
|
86
|
+
}
|