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,56 @@
|
|
|
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.readMachineKey = readMachineKey;
|
|
7
|
+
exports.ensureMachineKey = ensureMachineKey;
|
|
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 KEY_NAME = 'routing-machine.key';
|
|
13
|
+
function keyPath(root) {
|
|
14
|
+
if (typeof root !== 'string' || !path_1.default.isAbsolute(root) || path_1.default.normalize(root) !== root || root.includes('\0'))
|
|
15
|
+
throw new Error('routing machine root must be an absolute normalized path');
|
|
16
|
+
for (let dir = root;; dir = path_1.default.dirname(dir)) {
|
|
17
|
+
const stat = fs_1.default.lstatSync(dir);
|
|
18
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
19
|
+
throw new Error('routing machine root has unsafe or symlinked ancestor');
|
|
20
|
+
if (dir === path_1.default.dirname(dir))
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
return path_1.default.join(root, KEY_NAME);
|
|
24
|
+
}
|
|
25
|
+
/** Read-only status path; never initializes machine state during diagnostics. */
|
|
26
|
+
function readMachineKey(root) {
|
|
27
|
+
const file = keyPath(root);
|
|
28
|
+
try {
|
|
29
|
+
const stat = fs_1.default.lstatSync(file);
|
|
30
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
31
|
+
throw new Error('routing machine key is unsafe or symlinked');
|
|
32
|
+
if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)
|
|
33
|
+
throw new Error('routing machine key permissions are unsafe');
|
|
34
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 32).bytes;
|
|
35
|
+
if (bytes.length !== 32)
|
|
36
|
+
throw new Error('routing machine key length is invalid');
|
|
37
|
+
return Buffer.from(bytes);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT')
|
|
41
|
+
return null;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Only explicit setup may call this producer. No secret bytes are reported. */
|
|
46
|
+
function ensureMachineKey(root) {
|
|
47
|
+
keyPath(root);
|
|
48
|
+
return native_bridge_1.secureFs.withProjectLease(root, () => {
|
|
49
|
+
const existing = readMachineKey(root);
|
|
50
|
+
if (existing)
|
|
51
|
+
return existing;
|
|
52
|
+
const bytes = (0, crypto_1.randomBytes)(32);
|
|
53
|
+
native_bridge_1.secureFs.writeProjectTransaction(root, KEY_NAME, bytes, { mode: 'create', createParents: false });
|
|
54
|
+
return bytes;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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.isNativeClaudeAgentDeclaration = isNativeClaudeAgentDeclaration;
|
|
7
|
+
exports.readClaudeAgentDeclaredModel = readClaudeAgentDeclaredModel;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
11
|
+
const native_bridge_1 = require("../secure-fs/native-bridge");
|
|
12
|
+
const observedDeclarations = new WeakMap();
|
|
13
|
+
function isNativeClaudeAgentDeclaration(value) {
|
|
14
|
+
return !!value && typeof value === 'object' && observedDeclarations.get(value) === JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
function safeDirectory(dir) {
|
|
17
|
+
for (let current = dir;; current = path_1.default.dirname(current)) {
|
|
18
|
+
const stat = fs_1.default.lstatSync(current);
|
|
19
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
20
|
+
throw new Error('Claude agent definition has an unsafe ancestor');
|
|
21
|
+
if (current === path_1.default.dirname(current))
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** An explicit custom-agent model is necessary, but the effective model must
|
|
26
|
+
* still be independently observed in the native child transcript. Built-in
|
|
27
|
+
* agents, aliases and an inherited/default model cannot enroll a selection. */
|
|
28
|
+
function readClaudeAgentDeclaredModel(input) {
|
|
29
|
+
if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
|
|
30
|
+
|| path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
|
|
31
|
+
|| input.cwd.includes('\0') || input.configDir.includes('\0')
|
|
32
|
+
|| typeof input.agentType !== 'string' || !/^awm-[A-Za-z0-9][A-Za-z0-9_-]{0,123}$/.test(input.agentType))
|
|
33
|
+
throw new Error('Claude agent definition identity is invalid');
|
|
34
|
+
const dirs = [path_1.default.join(input.configDir, 'agents'), path_1.default.join(input.cwd, '.claude', 'agents')];
|
|
35
|
+
const declared = [];
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
try {
|
|
38
|
+
safeDirectory(dir);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT')
|
|
42
|
+
continue;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md'));
|
|
46
|
+
if (names.length > 256)
|
|
47
|
+
throw new Error('Claude agent definitions exceed limit');
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
const file = path_1.default.join(dir, name);
|
|
50
|
+
const stat = fs_1.default.lstatSync(file);
|
|
51
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
52
|
+
throw new Error('Claude agent definition is unsafe');
|
|
53
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
54
|
+
const content = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
55
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
56
|
+
if (!match)
|
|
57
|
+
continue;
|
|
58
|
+
let frontmatter;
|
|
59
|
+
try {
|
|
60
|
+
frontmatter = js_yaml_1.default.load(match[1]);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error('Claude agent frontmatter is invalid');
|
|
64
|
+
}
|
|
65
|
+
if (!frontmatter || typeof frontmatter !== 'object' || Array.isArray(frontmatter))
|
|
66
|
+
continue;
|
|
67
|
+
const row = frontmatter;
|
|
68
|
+
if (row.name !== input.agentType)
|
|
69
|
+
continue;
|
|
70
|
+
if (typeof row.description !== 'string' || !row.description.trim())
|
|
71
|
+
throw new Error('Claude agent description is missing');
|
|
72
|
+
if (typeof row.model !== 'string' || !/^claude-[a-z0-9][a-z0-9.-]{1,126}$/.test(row.model))
|
|
73
|
+
throw new Error('Claude agent requires an explicit full model ID; aliases and inherit are unverified');
|
|
74
|
+
declared.push(row.model);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (declared.length !== 1)
|
|
78
|
+
throw new Error(declared.length ? 'Claude agent definition is ambiguous' : 'Claude agent has no explicit custom definition');
|
|
79
|
+
const result = { agentType: input.agentType, modelId: declared[0] };
|
|
80
|
+
observedDeclarations.set(result, JSON.stringify(result));
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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.readClaudeModelConfigDigest = readClaudeModelConfigDigest;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
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
|
+
const json_1 = require("../plan/json");
|
|
12
|
+
const MODEL_KEYS = ['model', 'effortLevel', 'availableModels', 'enforceAvailableModels', 'modelOverrides'];
|
|
13
|
+
const ENV_KEYS = ['ANTHROPIC_MODEL', 'ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_SONNET_MODEL', 'ANTHROPIC_DEFAULT_HAIKU_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL', 'CLAUDE_CODE_SUBAGENT_MODEL_FORCE'];
|
|
14
|
+
function safeDirectory(dir) {
|
|
15
|
+
for (let current = dir;; current = path_1.default.dirname(current)) {
|
|
16
|
+
const stat = fs_1.default.lstatSync(current);
|
|
17
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
18
|
+
throw new Error('Claude settings have an unsafe or symlinked ancestor');
|
|
19
|
+
if (current === path_1.default.dirname(current))
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function settings(file) {
|
|
24
|
+
let stat;
|
|
25
|
+
try {
|
|
26
|
+
stat = fs_1.default.lstatSync(file);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT')
|
|
30
|
+
return null;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
safeDirectory(path_1.default.dirname(file));
|
|
34
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
35
|
+
throw new Error('Claude settings file is unsafe or symlinked');
|
|
36
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
37
|
+
const value = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
38
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
39
|
+
throw new Error('Claude settings must be an object');
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function agentDefinitions(dir) {
|
|
43
|
+
let stat;
|
|
44
|
+
try {
|
|
45
|
+
stat = fs_1.default.lstatSync(dir);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code === 'ENOENT')
|
|
49
|
+
return [];
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
safeDirectory(path_1.default.dirname(dir));
|
|
53
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
54
|
+
throw new Error('Claude agents directory is unsafe or symlinked');
|
|
55
|
+
const names = fs_1.default.readdirSync(dir).filter(name => name.endsWith('.md')).sort();
|
|
56
|
+
if (names.length > 256)
|
|
57
|
+
throw new Error('Claude agent definitions exceed limit');
|
|
58
|
+
return names.map(name => {
|
|
59
|
+
const file = path_1.default.join(dir, name);
|
|
60
|
+
const entry = fs_1.default.lstatSync(file);
|
|
61
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
62
|
+
throw new Error('Claude agent definition is unsafe or symlinked');
|
|
63
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, 1024 * 1024).bytes;
|
|
64
|
+
return [name, (0, crypto_1.createHash)('sha256').update(bytes).digest('hex')];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Fingerprints only locally visible model controls. Managed/server-side policy
|
|
68
|
+
* is not represented here; a native child observation remains mandatory. */
|
|
69
|
+
function readClaudeModelConfigDigest(input) {
|
|
70
|
+
if (!input || typeof input !== 'object' || !path_1.default.isAbsolute(input.cwd) || !path_1.default.isAbsolute(input.configDir)
|
|
71
|
+
|| path_1.default.normalize(input.cwd) !== input.cwd || path_1.default.normalize(input.configDir) !== input.configDir
|
|
72
|
+
|| input.cwd.includes('\0') || input.configDir.includes('\0') || !Buffer.isBuffer(input.key) || input.key.length !== 32
|
|
73
|
+
|| !input.environment || typeof input.environment !== 'object' || Array.isArray(input.environment))
|
|
74
|
+
throw new Error('Claude model config input is invalid');
|
|
75
|
+
safeDirectory(input.cwd);
|
|
76
|
+
safeDirectory(input.configDir);
|
|
77
|
+
const files = [path_1.default.join(input.configDir, 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.json'), path_1.default.join(input.cwd, '.claude', 'settings.local.json')];
|
|
78
|
+
const projection = files.map(file => {
|
|
79
|
+
const value = settings(file);
|
|
80
|
+
if (!value)
|
|
81
|
+
return null;
|
|
82
|
+
const model = {};
|
|
83
|
+
for (const key of MODEL_KEYS)
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
85
|
+
model[key] = value[key];
|
|
86
|
+
if (value.env && typeof value.env === 'object' && !Array.isArray(value.env)) {
|
|
87
|
+
const env = {};
|
|
88
|
+
for (const key of ENV_KEYS)
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(value.env, key))
|
|
90
|
+
env[key] = value.env[key];
|
|
91
|
+
if (Object.keys(env).length)
|
|
92
|
+
model.env = env;
|
|
93
|
+
}
|
|
94
|
+
return model;
|
|
95
|
+
});
|
|
96
|
+
const environment = {};
|
|
97
|
+
for (const key of ENV_KEYS) {
|
|
98
|
+
const value = input.environment[key];
|
|
99
|
+
if (value !== undefined) {
|
|
100
|
+
if (typeof value !== 'string' || value.length > 4096)
|
|
101
|
+
throw new Error('Claude model environment is invalid');
|
|
102
|
+
environment[key] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const agents = [agentDefinitions(path_1.default.join(input.configDir, 'agents')), agentDefinitions(path_1.default.join(input.cwd, '.claude', 'agents'))];
|
|
106
|
+
return (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify({ projection, agents, environment })).digest('hex');
|
|
107
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryClaudeAuthStatus = queryClaudeAuthStatus;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
/** `claude auth status` is documented as a JSON, no-inference command. The
|
|
7
|
+
* account shape varies by auth method; incomplete identity stays unverified. */
|
|
8
|
+
function queryClaudeAuthStatus(input) {
|
|
9
|
+
if (!input || typeof input !== 'object' || typeof input.command !== 'string' || input.command.length === 0 || input.command.length > 4096
|
|
10
|
+
|| !Array.isArray(input.args) || input.args.length > 16 || input.args.some(value => typeof value !== 'string' || value.length > 65536)
|
|
11
|
+
|| !Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30000
|
|
12
|
+
|| !Buffer.isBuffer(input.key) || input.key.length !== 32)
|
|
13
|
+
throw new Error('Claude auth query is invalid');
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = (0, child_process_1.spawn)(input.command, input.args, { stdio: ['ignore', 'pipe', 'pipe'], shell: false });
|
|
16
|
+
let settled = false;
|
|
17
|
+
let buffer = '';
|
|
18
|
+
const finish = (error, facts) => {
|
|
19
|
+
if (settled)
|
|
20
|
+
return;
|
|
21
|
+
settled = true;
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
if (error) {
|
|
24
|
+
child.kill();
|
|
25
|
+
reject(error);
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
resolve(facts);
|
|
29
|
+
};
|
|
30
|
+
const timer = setTimeout(() => finish(new Error('Claude auth status timed out')), input.timeoutMs);
|
|
31
|
+
child.on('error', () => finish(new Error('Claude CLI could not be started')));
|
|
32
|
+
child.stdout.setEncoding('utf8');
|
|
33
|
+
child.stdout.on('data', (chunk) => { buffer += chunk; if (buffer.length > 64 * 1024)
|
|
34
|
+
finish(new Error('Claude auth status exceeds 64 KiB')); });
|
|
35
|
+
child.on('exit', code => {
|
|
36
|
+
if (code !== 0)
|
|
37
|
+
return finish(new Error('Claude auth status failed or is logged out'));
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(buffer);
|
|
40
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
41
|
+
throw new Error('shape');
|
|
42
|
+
const value = parsed;
|
|
43
|
+
if (value.loggedIn !== true)
|
|
44
|
+
throw new Error('login');
|
|
45
|
+
const fields = ['authMethod', 'apiProvider', 'email', 'orgId'];
|
|
46
|
+
const identified = fields.every(field => typeof value[field] === 'string' && value[field].length > 0 && value[field].length <= 320);
|
|
47
|
+
const subscriptionType = value.subscriptionType;
|
|
48
|
+
if (subscriptionType !== null && subscriptionType !== undefined && (typeof subscriptionType !== 'string' || subscriptionType.length > 128))
|
|
49
|
+
throw new Error('subscription');
|
|
50
|
+
const accountScopeDigest = identified ? (0, crypto_1.createHmac)('sha256', input.key).update(JSON.stringify(fields.map(field => value[field]).concat([subscriptionType ?? null]))).digest('hex') : null;
|
|
51
|
+
finish(undefined, { provenance: 'claude-auth-status', accountState: accountScopeDigest ? 'identified' : 'unverified', accountScopeDigest, inferenceDispatched: false });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
finish(new Error('Claude auth status response is invalid'));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -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
|
+
}
|