agentic-workflow-manager 9.11.12 → 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.
Files changed (76) hide show
  1. package/dist/src/commands/doctor.js +23 -1
  2. package/dist/src/commands/hooks/claude.js +61 -8
  3. package/dist/src/commands/hooks/resync.js +1 -1
  4. package/dist/src/commands/job/index.js +39 -4
  5. package/dist/src/commands/model-policy/index.js +253 -0
  6. package/dist/src/commands/plan/index.js +53 -9
  7. package/dist/src/commands/preflight/checks.js +18 -0
  8. package/dist/src/commands/watch/apply.js +62 -4
  9. package/dist/src/commands/watch/supervisor.js +63 -3
  10. package/dist/src/core/admission/index.js +20 -6
  11. package/dist/src/core/journal/types.js +21 -5
  12. package/dist/src/core/model-policy/capabilities-v2.js +152 -0
  13. package/dist/src/core/model-policy/capabilities.js +7 -1
  14. package/dist/src/core/model-policy/claude-hook-events.js +120 -0
  15. package/dist/src/core/model-policy/event-store.js +221 -0
  16. package/dist/src/core/model-policy/hook-diagnostics.js +94 -0
  17. package/dist/src/core/model-policy/journal.js +113 -18
  18. package/dist/src/core/model-policy/local-claude-scope.js +41 -0
  19. package/dist/src/core/model-policy/local-codex-scope.js +33 -0
  20. package/dist/src/core/model-policy/local-event-scope.js +13 -0
  21. package/dist/src/core/model-policy/machine-key.js +56 -0
  22. package/dist/src/core/model-policy/native-claude-agent-definition.js +82 -0
  23. package/dist/src/core/model-policy/native-claude-config.js +107 -0
  24. package/dist/src/core/model-policy/native-claude-machine.js +58 -0
  25. package/dist/src/core/model-policy/native-claude-transcript.js +80 -0
  26. package/dist/src/core/model-policy/native-claude.js +41 -0
  27. package/dist/src/core/model-policy/native-codex-machine.js +116 -0
  28. package/dist/src/core/model-policy/native-codex-rollout.js +111 -0
  29. package/dist/src/core/model-policy/native-codex.js +302 -0
  30. package/dist/src/core/model-policy/native-routing-observe.js +106 -0
  31. package/dist/src/core/model-policy/native-routing-proof.js +52 -0
  32. package/dist/src/core/model-policy/native-runtime.js +86 -0
  33. package/dist/src/core/model-policy/resolve.js +96 -6
  34. package/dist/src/core/model-policy/selection-policy-digest.js +22 -0
  35. package/dist/src/core/model-policy/setup-readiness.js +77 -0
  36. package/dist/src/providers/index.js +3 -1
  37. package/dist/tests/commands/doctor-routing.test.js +22 -0
  38. package/dist/tests/commands/doctor.test.js +5 -0
  39. package/dist/tests/commands/hooks/install.test.js +6 -0
  40. package/dist/tests/commands/hooks/resync.test.js +3 -0
  41. package/dist/tests/commands/hooks/status.test.js +12 -1
  42. package/dist/tests/commands/job/routing-file.test.js +32 -0
  43. package/dist/tests/commands/job/routing-report-cli.test.js +4 -3
  44. package/dist/tests/commands/model-policy/claude-hook-flow.test.js +104 -0
  45. package/dist/tests/commands/model-policy/index.test.js +179 -0
  46. package/dist/tests/commands/preflight/preflight.test.js +65 -0
  47. package/dist/tests/commands/watch/apply.test.js +84 -0
  48. package/dist/tests/commands/watch/dispatch-routing-identity.test.js +23 -0
  49. package/dist/tests/core/admission/index.test.js +21 -0
  50. package/dist/tests/core/model-policy/capabilities-v2.test.js +42 -0
  51. package/dist/tests/core/model-policy/claude-hook-events.test.js +40 -0
  52. package/dist/tests/core/model-policy/event-store-claude.test.js +64 -0
  53. package/dist/tests/core/model-policy/event-store.test.js +151 -0
  54. package/dist/tests/core/model-policy/hook-diagnostics.test.js +34 -0
  55. package/dist/tests/core/model-policy/journal.test.js +112 -1
  56. package/dist/tests/core/model-policy/local-claude-scope.test.js +31 -0
  57. package/dist/tests/core/model-policy/machine-key.test.js +38 -0
  58. package/dist/tests/core/model-policy/native-claude-agent-definition.test.js +31 -0
  59. package/dist/tests/core/model-policy/native-claude-config.test.js +53 -0
  60. package/dist/tests/core/model-policy/native-claude-machine.test.js +18 -0
  61. package/dist/tests/core/model-policy/native-claude-transcript.test.js +45 -0
  62. package/dist/tests/core/model-policy/native-claude.test.js +21 -0
  63. package/dist/tests/core/model-policy/native-codex-machine.test.js +34 -0
  64. package/dist/tests/core/model-policy/native-codex-rollout.test.js +44 -0
  65. package/dist/tests/core/model-policy/native-codex.test.js +133 -0
  66. package/dist/tests/core/model-policy/native-routing-observe.test.js +60 -0
  67. package/dist/tests/core/model-policy/native-routing-proof.test.js +30 -0
  68. package/dist/tests/core/model-policy/native-runtime.test.js +14 -0
  69. package/dist/tests/core/model-policy/resolve-event.test.js +65 -0
  70. package/dist/tests/core/model-policy/setup-readiness.test.js +83 -0
  71. package/dist/tests/providers/index.test.js +1 -1
  72. package/package.json +2 -2
  73. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  74. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  75. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  76. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,80 @@
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.isNativeClaudeTranscriptObservation = isNativeClaudeTranscriptObservation;
7
+ exports.readClaudeSubagentTranscript = readClaudeSubagentTranscript;
8
+ const crypto_1 = require("crypto");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const native_bridge_1 = require("../secure-fs/native-bridge");
12
+ const MAX_BYTES = 16 * 1024 * 1024;
13
+ const ASSISTANT_LINE = /"type"\s*:\s*"assistant"/;
14
+ const observedTranscripts = new WeakMap();
15
+ function isNativeClaudeTranscriptObservation(value) {
16
+ return !!value && typeof value === 'object' && observedTranscripts.get(value) === JSON.stringify(value);
17
+ }
18
+ /** Private JSONL format: any format change or mixed-model trace fails closed.
19
+ * Usage is deliberately unknown because repeated assistant chunks can duplicate
20
+ * the provider usage object. No prompt or transcript text is returned. */
21
+ function readClaudeSubagentTranscript(input) {
22
+ if (!input || typeof input !== 'object' || typeof input.configDir !== 'string' || typeof input.file !== 'string'
23
+ || !path_1.default.isAbsolute(input.configDir) || !path_1.default.isAbsolute(input.file) || path_1.default.normalize(input.configDir) !== input.configDir
24
+ || path_1.default.normalize(input.file) !== input.file || input.configDir.includes('\0') || input.file.includes('\0')
25
+ || typeof input.agentId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(input.agentId))
26
+ throw new Error('Claude transcript identity or paths are invalid');
27
+ const relative = path_1.default.relative(input.configDir, input.file);
28
+ const segments = relative.split(path_1.default.sep);
29
+ if (segments.length < 5 || segments[0] !== 'projects' || segments[segments.length - 2] !== 'subagents'
30
+ || segments[segments.length - 1] !== `agent-${input.agentId}.jsonl`
31
+ || segments.some(segment => segment === '..' || segment === ''))
32
+ throw new Error('Claude transcript is outside the native subagents tree');
33
+ for (let dir = path_1.default.dirname(input.file);; dir = path_1.default.dirname(dir)) {
34
+ const stat = fs_1.default.lstatSync(dir);
35
+ if (!stat.isDirectory() || stat.isSymbolicLink())
36
+ throw new Error('Claude transcript has an unsafe or symlinked ancestor');
37
+ if (dir === input.configDir)
38
+ break;
39
+ if (dir === path_1.default.dirname(dir))
40
+ throw new Error('Claude config root is invalid');
41
+ }
42
+ const stat = fs_1.default.lstatSync(input.file);
43
+ if (!stat.isFile() || stat.isSymbolicLink())
44
+ throw new Error('Claude transcript is unsafe or symlinked');
45
+ const bytes = native_bridge_1.secureFs.readRegularFile(input.file, MAX_BYTES).bytes;
46
+ const lines = new TextDecoder('utf-8', { fatal: true }).decode(bytes).split('\n');
47
+ let actualModel;
48
+ let observedAt;
49
+ let count = 0;
50
+ for (const line of lines) {
51
+ if (!ASSISTANT_LINE.test(line))
52
+ continue;
53
+ if (line.length > 256 * 1024 || ++count > 10000)
54
+ throw new Error('Claude transcript assistant metadata exceeds limit');
55
+ const event = JSON.parse(line);
56
+ if (!event || typeof event !== 'object' || Array.isArray(event))
57
+ throw new Error('Claude transcript assistant event is invalid');
58
+ const item = event;
59
+ if (item.type !== 'assistant' || !item.message || typeof item.message !== 'object' || Array.isArray(item.message))
60
+ throw new Error('Claude transcript assistant event is invalid');
61
+ const model = item.message.model;
62
+ if (typeof model !== 'string' || model.length === 0 || model.length > 128 || /[\u0000-\u001f\u007f-\u009f]/.test(model))
63
+ throw new Error('Claude transcript model is invalid');
64
+ if (actualModel !== undefined && actualModel !== model)
65
+ throw new Error('Claude transcript contains mixed or multiple models');
66
+ if (typeof item.timestamp !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(item.timestamp)
67
+ || !Number.isFinite(Date.parse(item.timestamp)))
68
+ throw new Error('Claude transcript timestamp is invalid');
69
+ actualModel = model;
70
+ const time = new Date(item.timestamp).toISOString();
71
+ if (!observedAt || time > observedAt)
72
+ observedAt = time;
73
+ }
74
+ if (!actualModel || !observedAt)
75
+ throw new Error('Claude transcript has no assistant model evidence');
76
+ const result = { provenance: 'claude-subagent-transcript', agentId: input.agentId, actualModel, observedAt,
77
+ evidenceDigest: (0, crypto_1.createHash)('sha256').update(bytes).digest('hex'), tokenUsage: 'unknown' };
78
+ observedTranscripts.set(result, JSON.stringify(result));
79
+ return result;
80
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isNativeClaudeSubagentLifecycle = isNativeClaudeSubagentLifecycle;
4
+ exports.normalizeClaudeSubagentLifecycle = normalizeClaudeSubagentLifecycle;
5
+ const observedLifecycles = new WeakMap();
6
+ function isNativeClaudeSubagentLifecycle(value) {
7
+ return !!value && typeof value === 'object' && observedLifecycles.get(value) === JSON.stringify(value);
8
+ }
9
+ function record(value, name) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value))
11
+ throw new Error(`${name} must be an object`);
12
+ return value;
13
+ }
14
+ function boundedText(value, name, max = 128) {
15
+ if (typeof value !== 'string' || value.length === 0 || value.length > max || /[\u0000-\u001f\u007f-\u009f]/.test(value))
16
+ throw new Error(`${name} is invalid`);
17
+ return value;
18
+ }
19
+ /** A matching native hook pair proves lifecycle, not the effective model.
20
+ * Call only on payloads captured by the installed Claude Code hooks. */
21
+ function normalizeClaudeSubagentLifecycle(startValue, stopValue) {
22
+ const start = record(startValue, 'Claude SubagentStart hook');
23
+ const stop = record(stopValue, 'Claude SubagentStop hook');
24
+ if (start.hook_event_name !== 'SubagentStart' || stop.hook_event_name !== 'SubagentStop')
25
+ throw new Error('Claude subagent hook event kind is invalid');
26
+ const sessionId = boundedText(start.session_id, 'Claude session id');
27
+ const agentId = boundedText(start.agent_id, 'Claude agent id');
28
+ const agentType = boundedText(start.agent_type, 'Claude agent type');
29
+ const stopSessionId = boundedText(stop.session_id, 'Claude stop session id');
30
+ const stopAgentId = boundedText(stop.agent_id, 'Claude stop agent id');
31
+ const stopAgentType = boundedText(stop.agent_type, 'Claude stop agent type');
32
+ boundedText(stop.agent_transcript_path, 'Claude agent transcript path', 4096);
33
+ if (typeof stop.stop_hook_active !== 'boolean')
34
+ throw new Error('Claude stop hook active flag is invalid');
35
+ if (sessionId !== stopSessionId || agentId !== stopAgentId || agentType !== stopAgentType)
36
+ throw new Error('Claude subagent lifecycle identity mismatch');
37
+ const lifecycle = { provenance: 'claude-code-subagent-hooks', sessionId, agentId, agentType,
38
+ nativeDispatchVerified: true, acceptedSelectionVerified: false, actualModelVerified: false, tokenUsage: 'unknown' };
39
+ observedLifecycles.set(lifecycle, JSON.stringify(lifecycle));
40
+ return lifecycle;
41
+ }
@@ -0,0 +1,116 @@
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.queryCodexMachineFacts = queryCodexMachineFacts;
7
+ const crypto_1 = require("crypto");
8
+ const child_process_1 = require("child_process");
9
+ const path_1 = __importDefault(require("path"));
10
+ function record(value, label) {
11
+ if (!value || typeof value !== 'object' || Array.isArray(value))
12
+ throw new Error(`${label} must be an object`);
13
+ return value;
14
+ }
15
+ function canonical(value, depth = 0) {
16
+ if (depth > 32)
17
+ throw new Error('Codex configuration nesting exceeds limit');
18
+ if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
19
+ return JSON.stringify(value);
20
+ if (Array.isArray(value))
21
+ return `[${value.map(item => canonical(item, depth + 1)).join(',')}]`;
22
+ const item = record(value, 'Codex configuration value');
23
+ return `{${Object.keys(item).sort().map(key => `${JSON.stringify(key)}:${canonical(item[key], depth + 1)}`).join(',')}}`;
24
+ }
25
+ function digest(key, value) { return (0, crypto_1.createHmac)('sha256', key).update(value, 'utf8').digest('hex'); }
26
+ /** Explicit no-inference query. HMAC prevents offline guessing of email and
27
+ * configuration values; the caller owns one stable per-machine key. */
28
+ function queryCodexMachineFacts(input) {
29
+ if (!input || typeof input !== 'object' || typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
30
+ || !Array.isArray(input.args) || input.args.length > 16 || input.args.some(arg => typeof arg !== 'string' || arg.length > 65536)
31
+ || !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000
32
+ || typeof input.cwd !== 'string' || !path_1.default.isAbsolute(input.cwd) || !Buffer.isBuffer(input.key) || input.key.length !== 32)
33
+ throw new Error('Codex machine query is invalid');
34
+ return new Promise((resolve, reject) => {
35
+ const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false, cwd: input.cwd });
36
+ let settled = false;
37
+ let buffer = '';
38
+ let expectedId = 1;
39
+ let configDigest;
40
+ const finish = (error, result) => {
41
+ if (settled)
42
+ return;
43
+ settled = true;
44
+ clearTimeout(timer);
45
+ child.kill();
46
+ if (error)
47
+ reject(error);
48
+ else
49
+ resolve(result);
50
+ };
51
+ const send = (request) => { if (!settled)
52
+ child.stdin.write(`${JSON.stringify(request)}\n`); };
53
+ const timer = setTimeout(() => finish(new Error('Codex machine query timed out')), input.timeoutMs);
54
+ child.on('error', () => finish(new Error('Codex app-server could not be started')));
55
+ child.stdin.on('error', () => finish(new Error('Codex app-server input failed')));
56
+ child.on('exit', () => finish(new Error('Codex app-server exited before machine response')));
57
+ child.stdout.setEncoding('utf8');
58
+ child.stdout.on('data', (chunk) => {
59
+ if (settled)
60
+ return;
61
+ buffer += chunk;
62
+ if (buffer.length > 2 * 1024 * 1024)
63
+ return finish(new Error('Codex machine response exceeds 2 MiB'));
64
+ for (;;) {
65
+ const end = buffer.indexOf('\n');
66
+ if (end < 0 || settled)
67
+ break;
68
+ const line = buffer.slice(0, end).trim();
69
+ buffer = buffer.slice(end + 1);
70
+ if (!line)
71
+ continue;
72
+ let message;
73
+ try {
74
+ message = record(JSON.parse(line), 'Codex protocol response');
75
+ }
76
+ catch {
77
+ return finish(new Error('Codex protocol returned invalid JSON'));
78
+ }
79
+ if (message.id === undefined)
80
+ continue;
81
+ if (message.id !== expectedId)
82
+ return finish(new Error('Codex protocol response id mismatch'));
83
+ if (message.error !== undefined)
84
+ return finish(new Error('Codex app-server rejected machine query'));
85
+ try {
86
+ const result = record(message.result, 'Codex machine result');
87
+ if (expectedId === 1) {
88
+ expectedId = 2;
89
+ send({ method: 'initialized' });
90
+ send({ id: expectedId, method: 'config/read', params: { cwd: input.cwd, includeLayers: false } });
91
+ }
92
+ else if (expectedId === 2) {
93
+ configDigest = digest(input.key, canonical(record(result.config, 'Codex effective config')));
94
+ expectedId = 3;
95
+ send({ id: expectedId, method: 'account/read', params: { refreshToken: false } });
96
+ }
97
+ else {
98
+ if (typeof result.requiresOpenaiAuth !== 'boolean')
99
+ throw new Error('Codex account response is invalid');
100
+ const account = result.account === null ? null : record(result.account, 'Codex account');
101
+ let accountScopeDigest = null;
102
+ if (account?.type === 'chatgpt' && typeof account.email === 'string' && account.email.length > 0 && account.email.length <= 320 && typeof account.planType === 'string') {
103
+ accountScopeDigest = digest(input.key, `chatgpt\0${account.email}\0${account.planType}`);
104
+ }
105
+ finish(undefined, { provenance: 'codex-app-server-config-account', configDigest: configDigest,
106
+ accountState: accountScopeDigest ? 'identified' : 'unverified', accountScopeDigest, inferenceDispatched: false });
107
+ }
108
+ }
109
+ catch {
110
+ finish(new Error('Codex machine response is invalid'));
111
+ }
112
+ }
113
+ });
114
+ send({ id: 1, method: 'initialize', params: { clientInfo: { name: 'awm', title: 'AWM machine discovery', version: '1.0.0' } } });
115
+ });
116
+ }
@@ -0,0 +1,111 @@
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.normalizeCodexRolloutSelection = normalizeCodexRolloutSelection;
7
+ exports.readCodexRolloutSelection = readCodexRolloutSelection;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const native_bridge_1 = require("../secure-fs/native-bridge");
11
+ function record(value, name) {
12
+ if (!value || typeof value !== 'object' || Array.isArray(value))
13
+ throw new Error(`${name} must be an object`);
14
+ return value;
15
+ }
16
+ function boundedText(value, name) {
17
+ if (typeof value !== 'string' || value.length === 0 || value.length > 128 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
18
+ throw new Error(`${name} is invalid`);
19
+ return value;
20
+ }
21
+ /** Normalize only the two metadata records from a locally verified Codex
22
+ * rollout. The private format is version-sensitive; unknown shapes remain
23
+ * UNTESTED rather than being promoted from thread configuration. */
24
+ function normalizeCodexRolloutSelection(events, expected) {
25
+ if (!expected || typeof expected !== 'object')
26
+ throw new Error('Codex rollout identity is required');
27
+ const parentThreadId = boundedText(expected.parentThreadId, 'Codex parent thread id');
28
+ const childThreadId = boundedText(expected.childThreadId, 'Codex child thread id');
29
+ const turnId = boundedText(expected.turnId, 'Codex turn id');
30
+ if (!Array.isArray(events) || events.length === 0 || events.length > 10000)
31
+ throw new Error('Codex rollout events are invalid');
32
+ let cliVersion;
33
+ let selection;
34
+ let observedAt;
35
+ for (const raw of events) {
36
+ const event = record(raw, 'Codex rollout event');
37
+ if (event.type === 'session_meta') {
38
+ if (cliVersion !== undefined)
39
+ throw new Error('Codex rollout has duplicate session metadata');
40
+ const payload = record(event.payload, 'Codex session metadata');
41
+ if (boundedText(payload.id, 'Codex metadata child id') !== childThreadId)
42
+ throw new Error('Codex rollout child id mismatch');
43
+ const source = record(payload.source, 'Codex session source');
44
+ const subagent = record(source.subagent, 'Codex subagent source');
45
+ const spawn = record(subagent.thread_spawn, 'Codex thread spawn source');
46
+ if (boundedText(spawn.parent_thread_id, 'Codex metadata parent id') !== parentThreadId)
47
+ throw new Error('Codex rollout parent id mismatch');
48
+ cliVersion = boundedText(payload.cli_version, 'Codex rollout CLI version');
49
+ if (!/^\d+\.\d+\.\d+$/.test(cliVersion))
50
+ throw new Error('Codex rollout CLI version is invalid');
51
+ }
52
+ if (event.type === 'turn_context') {
53
+ const payload = record(event.payload, 'Codex turn context');
54
+ if (boundedText(payload.turn_id, 'Codex turn context id') !== turnId)
55
+ continue;
56
+ if (selection !== undefined)
57
+ throw new Error('Codex rollout has duplicate turn context');
58
+ if (typeof event.timestamp !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(event.timestamp)
59
+ || !Number.isFinite(Date.parse(event.timestamp)))
60
+ throw new Error('Codex turn timestamp is invalid');
61
+ observedAt = new Date(event.timestamp).toISOString();
62
+ selection = { selector: { kind: 'model', id: boundedText(payload.model, 'Codex turn model') },
63
+ effort: { kind: 'explicit', value: boundedText(payload.effort, 'Codex turn effort') } };
64
+ }
65
+ }
66
+ if (cliVersion === undefined)
67
+ throw new Error('Codex rollout session metadata is missing');
68
+ if (selection === undefined || observedAt === undefined)
69
+ throw new Error('Codex rollout turn context is missing');
70
+ return { provenance: 'codex-rollout-turn-context', cliVersion, observedAt, configuredSelection: selection, actualModelVerified: false };
71
+ }
72
+ const MAX_ROLLOUT_BYTES = 16 * 1024 * 1024;
73
+ const METADATA_LINE = /"type"\s*:\s*"(?:session_meta|turn_context)"/;
74
+ /** Read only the validated local Codex sessions tree; return no transcript or
75
+ * account data. A private-format change fails closed as UNTESTED to callers. */
76
+ function readCodexRolloutSelection(input) {
77
+ if (!input || typeof input !== 'object' || typeof input.codexHome !== 'string' || typeof input.file !== 'string'
78
+ || !path_1.default.isAbsolute(input.codexHome) || !path_1.default.isAbsolute(input.file)
79
+ || path_1.default.normalize(input.codexHome) !== input.codexHome || path_1.default.normalize(input.file) !== input.file
80
+ || input.codexHome.includes('\0') || input.file.includes('\0'))
81
+ throw new Error('Codex rollout paths are invalid');
82
+ const sessions = path_1.default.join(input.codexHome, 'sessions');
83
+ const relative = path_1.default.relative(sessions, input.file);
84
+ if (!relative || relative === '..' || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative))
85
+ throw new Error('Codex rollout is outside sessions');
86
+ for (let candidate = path_1.default.dirname(input.file);; candidate = path_1.default.dirname(candidate)) {
87
+ const stat = fs_1.default.lstatSync(candidate);
88
+ if (!stat.isDirectory() || stat.isSymbolicLink())
89
+ throw new Error('Codex rollout has an unsafe or symlinked ancestor');
90
+ if (candidate === input.codexHome)
91
+ break;
92
+ if (candidate === path_1.default.dirname(candidate))
93
+ throw new Error('Codex rollout root is invalid');
94
+ }
95
+ const stat = fs_1.default.lstatSync(input.file);
96
+ if (!stat.isFile() || stat.isSymbolicLink())
97
+ throw new Error('Codex rollout is unsafe or symlinked');
98
+ const bytes = native_bridge_1.secureFs.readRegularFile(input.file, MAX_ROLLOUT_BYTES).bytes;
99
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
100
+ const events = [];
101
+ for (const line of text.split('\n')) {
102
+ if (!METADATA_LINE.test(line))
103
+ continue;
104
+ if (line.length > 256 * 1024)
105
+ throw new Error('Codex rollout metadata line exceeds 256 KiB');
106
+ events.push(JSON.parse(line));
107
+ if (events.length > 10000)
108
+ throw new Error('Codex rollout has too many metadata events');
109
+ }
110
+ return normalizeCodexRolloutSelection(events, input.expected);
111
+ }
@@ -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
+ }