@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
package/dist/bin/internal-cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const command = process.argv[2] || '';
|
|
|
8
8
|
if (process.env.AGENT_INFRA_CONTROL_TOKEN
|
|
9
9
|
&& (command === 'task-lifecycle' || command === 'task-orchestration')) {
|
|
10
10
|
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
11
|
-
sandboxControl(['client', command, ...process.argv.slice(3)]);
|
|
11
|
+
await sandboxControl(['client', command, ...process.argv.slice(3)]);
|
|
12
12
|
}
|
|
13
13
|
else
|
|
14
14
|
switch (command) {
|
|
@@ -19,7 +19,7 @@ else
|
|
|
19
19
|
}
|
|
20
20
|
case 'sandbox-control': {
|
|
21
21
|
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
22
|
-
sandboxControl(process.argv.slice(3));
|
|
22
|
+
await sandboxControl(process.argv.slice(3));
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
25
25
|
case 'agent-client': {
|
|
@@ -32,6 +32,11 @@ else
|
|
|
32
32
|
await codexLifecycle(process.argv.slice(3));
|
|
33
33
|
break;
|
|
34
34
|
}
|
|
35
|
+
case 'codex-sandbox-controller': {
|
|
36
|
+
const { codexSandboxController } = await import('../lib/internal/codex-sandbox-controller.js');
|
|
37
|
+
await codexSandboxController(process.argv.slice(3));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
35
40
|
case 'git-workflow': {
|
|
36
41
|
const { gitWorkflow } = await import('../lib/internal/git-workflow.js');
|
|
37
42
|
gitWorkflow(process.argv.slice(3));
|
|
@@ -109,7 +114,7 @@ else
|
|
|
109
114
|
}
|
|
110
115
|
case 'task-orchestration': {
|
|
111
116
|
const { taskOrchestration } = await import('../lib/internal/task-orchestration.js');
|
|
112
|
-
taskOrchestration(process.argv.slice(3));
|
|
117
|
+
await taskOrchestration(process.argv.slice(3));
|
|
113
118
|
break;
|
|
114
119
|
}
|
|
115
120
|
case 'task-review': {
|
|
@@ -6,10 +6,12 @@ import path from 'node:path';
|
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
const LIFECYCLE_HOOKS = Object.freeze([
|
|
8
8
|
Object.freeze({ event: 'PreToolUse', matcher: '^collaborationspawn_agent$', phase: 'pre-tool' }),
|
|
9
|
-
Object.freeze({ event: 'PostToolUse', matcher: '
|
|
10
|
-
Object.freeze({ event: 'SubagentStart', matcher: '
|
|
11
|
-
Object.freeze({ event: 'SubagentStop', matcher: '
|
|
9
|
+
Object.freeze({ event: 'PostToolUse', matcher: '', phase: 'post-tool' }),
|
|
10
|
+
Object.freeze({ event: 'SubagentStart', matcher: '', phase: 'subagent-start' }),
|
|
11
|
+
Object.freeze({ event: 'SubagentStop', matcher: '', phase: 'subagent-stop' })
|
|
12
12
|
]);
|
|
13
|
+
const DEFAULT_ROLLOUT_READ_ATTEMPTS = 81;
|
|
14
|
+
const DEFAULT_ROLLOUT_RETRY_MS = 100;
|
|
13
15
|
function object(value) {
|
|
14
16
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
15
17
|
? value
|
|
@@ -18,6 +20,58 @@ function object(value) {
|
|
|
18
20
|
function nonEmpty(value) {
|
|
19
21
|
return typeof value === 'string' && value.trim() ? value : null;
|
|
20
22
|
}
|
|
23
|
+
function resolveCodexSpawnedChild(transcriptPath, expected) {
|
|
24
|
+
const name = path.basename(transcriptPath);
|
|
25
|
+
if (!transcriptPath || (!name.endsWith(`-${expected.sessionId}.jsonl`) && name !== `${expected.sessionId}.jsonl`)) {
|
|
26
|
+
throw new Error('Codex parent rollout path is invalid');
|
|
27
|
+
}
|
|
28
|
+
const stat = fs.lstatSync(transcriptPath);
|
|
29
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
30
|
+
throw new Error('Codex parent rollout is not a regular file');
|
|
31
|
+
const maxBytes = 8 * 1024 * 1024;
|
|
32
|
+
const offset = Math.max(0, stat.size - maxBytes);
|
|
33
|
+
const buffer = Buffer.alloc(stat.size - offset);
|
|
34
|
+
const descriptor = fs.openSync(transcriptPath, 'r');
|
|
35
|
+
try {
|
|
36
|
+
fs.readSync(descriptor, buffer, 0, buffer.length, offset);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
fs.closeSync(descriptor);
|
|
40
|
+
}
|
|
41
|
+
let text = buffer.toString('utf8');
|
|
42
|
+
if (offset)
|
|
43
|
+
text = text.slice(text.indexOf('\n') + 1);
|
|
44
|
+
const records = text.split(/\r?\n/).filter(Boolean).map((line) => object(JSON.parse(line))).filter(Boolean);
|
|
45
|
+
const calls = records.filter((record) => {
|
|
46
|
+
const payload = object(record?.payload);
|
|
47
|
+
return record?.type === 'response_item'
|
|
48
|
+
&& payload?.type === 'function_call'
|
|
49
|
+
&& payload.call_id === expected.toolUseId
|
|
50
|
+
&& payload.namespace === 'collaboration'
|
|
51
|
+
&& payload.name === 'spawn_agent';
|
|
52
|
+
});
|
|
53
|
+
if (calls.length !== 1)
|
|
54
|
+
throw new Error('Codex parent rollout spawn call was not found uniquely');
|
|
55
|
+
const call = object(calls[0]?.payload);
|
|
56
|
+
const args = object(JSON.parse(nonEmpty(call?.arguments) ?? '{}'));
|
|
57
|
+
if (args?.agent_type !== expected.nativeAgent
|
|
58
|
+
|| args.task_name !== expected.taskName
|
|
59
|
+
|| (expected.requestedModel && args.model !== expected.requestedModel)
|
|
60
|
+
|| (expected.requestedReasoningEffort && args.reasoning_effort !== expected.requestedReasoningEffort))
|
|
61
|
+
throw new Error('Codex parent rollout spawn identity does not match the hook event');
|
|
62
|
+
const activities = records.filter((record) => {
|
|
63
|
+
const payload = object(record?.payload);
|
|
64
|
+
return record?.type === 'event_msg'
|
|
65
|
+
&& payload?.type === 'sub_agent_activity'
|
|
66
|
+
&& payload.event_id === expected.toolUseId
|
|
67
|
+
&& payload.kind === 'started'
|
|
68
|
+
&& payload.agent_path === `/root/${expected.taskName}`
|
|
69
|
+
&& nonEmpty(payload.agent_thread_id);
|
|
70
|
+
});
|
|
71
|
+
if (activities.length !== 1)
|
|
72
|
+
throw new Error('Codex parent rollout child activity was not found uniquely');
|
|
73
|
+
return nonEmpty(object(activities[0]?.payload)?.agent_thread_id);
|
|
74
|
+
}
|
|
21
75
|
function validateCodexLifecycleHookConfig(value) {
|
|
22
76
|
const hooks = object(object(value)?.hooks);
|
|
23
77
|
const valid = hooks && LIFECYCLE_HOOKS.every(({ event, matcher, phase }) => {
|
|
@@ -31,7 +85,7 @@ function validateCodexLifecycleHookConfig(value) {
|
|
|
31
85
|
return entry.hooks.some((hookValue) => {
|
|
32
86
|
const hook = object(hookValue);
|
|
33
87
|
return hook?.type === 'command'
|
|
34
|
-
&& hook.timeout ===
|
|
88
|
+
&& hook.timeout === 15
|
|
35
89
|
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`;
|
|
36
90
|
});
|
|
37
91
|
});
|
|
@@ -39,7 +93,7 @@ function validateCodexLifecycleHookConfig(value) {
|
|
|
39
93
|
if (!valid)
|
|
40
94
|
throw new Error('CODEX_PREFLIGHT_HOOKS_INVALID: lifecycle hooks are invalid');
|
|
41
95
|
}
|
|
42
|
-
function parseCodexHooksList(value, repoRoot) {
|
|
96
|
+
function parseCodexHooksList(value, repoRoot, expectedSource = 'direct-host') {
|
|
43
97
|
const root = object(value);
|
|
44
98
|
const entries = Array.isArray(root?.data) ? root.data : [];
|
|
45
99
|
const entry = entries.map(object).find((candidate) => candidate?.cwd === repoRoot);
|
|
@@ -53,12 +107,27 @@ function parseCodexHooksList(value, repoRoot) {
|
|
|
53
107
|
eventName: String(hook.eventName ?? ''),
|
|
54
108
|
matcher: typeof hook.matcher === 'string' ? hook.matcher : null,
|
|
55
109
|
command: String(hook.command ?? ''),
|
|
56
|
-
enabled: hook.enabled === true
|
|
110
|
+
enabled: hook.enabled === true,
|
|
111
|
+
source: String(hook.source ?? ''),
|
|
112
|
+
sourcePathDigest: typeof hook.sourcePath === 'string' && path.isAbsolute(hook.sourcePath)
|
|
113
|
+
? crypto.createHash('sha256').update(hook.sourcePath).digest('hex')
|
|
114
|
+
: '',
|
|
115
|
+
trustStatus: String(hook.trustStatus ?? ''),
|
|
116
|
+
currentHash: String(hook.currentHash ?? ''),
|
|
117
|
+
isManaged: hook.isManaged === true,
|
|
118
|
+
pluginId: typeof hook.pluginId === 'string' ? hook.pluginId : null
|
|
57
119
|
}));
|
|
58
120
|
const valid = LIFECYCLE_HOOKS.every(({ event, matcher, phase }) => hooks.some((hook) => hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
|
|
59
121
|
&& hook.matcher === matcher
|
|
60
122
|
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`
|
|
61
|
-
&& hook.enabled
|
|
123
|
+
&& hook.enabled
|
|
124
|
+
&& hook.currentHash.length > 0
|
|
125
|
+
&& hook.sourcePathDigest.length === 64
|
|
126
|
+
&& hook.pluginId === null
|
|
127
|
+
&& (expectedSource === 'isolated-user'
|
|
128
|
+
? hook.source === 'user' && hook.trustStatus !== 'modified'
|
|
129
|
+
: (hook.source === 'project' && hook.trustStatus === 'trusted')
|
|
130
|
+
|| hook.isManaged && hook.trustStatus === 'managed')));
|
|
62
131
|
if (!valid)
|
|
63
132
|
throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
|
|
64
133
|
return Object.freeze(hooks);
|
|
@@ -84,25 +153,37 @@ function hasCodexRuntimeLiveness(runtimeRoot, hookDefinitionHash, identity) {
|
|
|
84
153
|
}
|
|
85
154
|
});
|
|
86
155
|
}
|
|
87
|
-
function parseCodexThreadResolution(readValue,
|
|
156
|
+
function parseCodexThreadResolution(readValue, rolloutRecords) {
|
|
88
157
|
const readRoot = object(readValue);
|
|
89
|
-
const resumeRoot = object(resumeValue);
|
|
90
158
|
const thread = object(readRoot?.thread);
|
|
91
|
-
const resumedThread = object(resumeRoot?.thread);
|
|
92
159
|
const source = object(thread?.source);
|
|
93
160
|
const subAgent = object(source?.subAgent);
|
|
94
161
|
const threadSpawn = object(subAgent?.thread_spawn);
|
|
162
|
+
const sessionMeta = rolloutRecords
|
|
163
|
+
.map(object)
|
|
164
|
+
.find((record) => record?.type === 'session_meta');
|
|
165
|
+
const sessionPayload = object(sessionMeta?.payload);
|
|
166
|
+
const turnContext = rolloutRecords
|
|
167
|
+
.map(object)
|
|
168
|
+
.filter((record) => record?.type === 'turn_context')
|
|
169
|
+
.at(-1);
|
|
170
|
+
const turnPayload = object(turnContext?.payload);
|
|
95
171
|
const childThreadId = nonEmpty(thread?.id);
|
|
96
172
|
const parentThreadId = nonEmpty(thread?.parentThreadId);
|
|
97
173
|
const sourceParentThreadId = nonEmpty(threadSpawn?.parent_thread_id);
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
174
|
+
const rolloutChildThreadId = nonEmpty(sessionPayload?.id);
|
|
175
|
+
const rolloutParentThreadId = nonEmpty(sessionPayload?.parent_thread_id);
|
|
176
|
+
const agentRole = nonEmpty(sessionPayload?.agent_role);
|
|
177
|
+
const model = nonEmpty(turnPayload?.model);
|
|
178
|
+
const reasoningEffort = nonEmpty(turnPayload?.effort);
|
|
101
179
|
const forkedFromId = thread?.forkedFromId;
|
|
102
180
|
if (!childThreadId
|
|
103
|
-
||
|
|
181
|
+
|| rolloutChildThreadId !== childThreadId
|
|
104
182
|
|| !parentThreadId
|
|
105
183
|
|| !sourceParentThreadId
|
|
184
|
+
|| rolloutParentThreadId !== parentThreadId
|
|
185
|
+
|| rolloutParentThreadId !== sourceParentThreadId
|
|
186
|
+
|| !/^agent-infra-lifecycle-(executor|reviewer)$/.test(agentRole ?? '')
|
|
106
187
|
|| !model
|
|
107
188
|
|| !reasoningEffort
|
|
108
189
|
|| (forkedFromId !== null && typeof forkedFromId !== 'string')) {
|
|
@@ -114,7 +195,8 @@ function parseCodexThreadResolution(readValue, resumeValue) {
|
|
|
114
195
|
childThreadId,
|
|
115
196
|
parentThreadId,
|
|
116
197
|
forkedFromId,
|
|
117
|
-
sourceParentThreadId
|
|
198
|
+
sourceParentThreadId,
|
|
199
|
+
nativeAgent: agentRole
|
|
118
200
|
}),
|
|
119
201
|
settings: Object.freeze({
|
|
120
202
|
type: 'app-settings',
|
|
@@ -124,6 +206,46 @@ function parseCodexThreadResolution(readValue, resumeValue) {
|
|
|
124
206
|
})
|
|
125
207
|
});
|
|
126
208
|
}
|
|
209
|
+
function readCodexRolloutRecords(readValue, childThreadId) {
|
|
210
|
+
const thread = object(object(readValue)?.thread);
|
|
211
|
+
const rolloutPath = nonEmpty(thread?.path);
|
|
212
|
+
const rolloutName = rolloutPath ? path.basename(rolloutPath) : '';
|
|
213
|
+
if (!rolloutPath || (rolloutName !== `${childThreadId}.jsonl` && !rolloutName.endsWith(`-${childThreadId}.jsonl`))) {
|
|
214
|
+
throw new Error('Codex App Server rollout path is invalid');
|
|
215
|
+
}
|
|
216
|
+
const stat = fs.statSync(rolloutPath);
|
|
217
|
+
if (!stat.isFile())
|
|
218
|
+
throw new Error('Codex App Server rollout path is not a regular file');
|
|
219
|
+
const maxBytes = 1024 * 1024;
|
|
220
|
+
const bytes = Math.min(stat.size, maxBytes);
|
|
221
|
+
const descriptor = fs.openSync(rolloutPath, 'r');
|
|
222
|
+
try {
|
|
223
|
+
const buffer = Buffer.alloc(bytes);
|
|
224
|
+
fs.readSync(descriptor, buffer, 0, bytes, 0);
|
|
225
|
+
const text = buffer.toString('utf8');
|
|
226
|
+
const completeText = stat.size <= maxBytes ? text : text.slice(0, text.lastIndexOf('\n'));
|
|
227
|
+
return Object.freeze(completeText.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)));
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
fs.closeSync(descriptor);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function resolveCodexRolloutRecords(readValue, childThreadId, attempts, retryMs) {
|
|
234
|
+
let lastError;
|
|
235
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
236
|
+
try {
|
|
237
|
+
const records = readCodexRolloutRecords(readValue, childThreadId);
|
|
238
|
+
parseCodexThreadResolution(readValue, records);
|
|
239
|
+
return records;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
lastError = error;
|
|
243
|
+
if (attempt + 1 < attempts)
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, retryMs));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
throw lastError;
|
|
248
|
+
}
|
|
127
249
|
function parseCodexTurnCompleted(value) {
|
|
128
250
|
const root = object(value);
|
|
129
251
|
const turn = object(root?.turn);
|
|
@@ -287,13 +409,13 @@ async function resolveCodexThread(childThreadId, options = {}) {
|
|
|
287
409
|
const readThread = object(readResult?.thread);
|
|
288
410
|
if (nonEmpty(readThread?.id) !== childThreadId)
|
|
289
411
|
throw new Error('Codex App Server returned the wrong child thread');
|
|
290
|
-
const resumeResult = await transport.request('thread/resume', { threadId: childThreadId });
|
|
291
412
|
let resolution;
|
|
292
413
|
try {
|
|
293
|
-
|
|
414
|
+
const rolloutRecords = await resolveCodexRolloutRecords(readResult, childThreadId, options.rolloutReadAttempts ?? DEFAULT_ROLLOUT_READ_ATTEMPTS, options.rolloutRetryMs ?? DEFAULT_ROLLOUT_RETRY_MS);
|
|
415
|
+
resolution = parseCodexThreadResolution(readResult, rolloutRecords);
|
|
294
416
|
}
|
|
295
417
|
catch {
|
|
296
|
-
throw new Error('Codex App Server
|
|
418
|
+
throw new Error('Codex App Server rollout metadata is unavailable');
|
|
297
419
|
}
|
|
298
420
|
const reroutes = transport.notifications
|
|
299
421
|
.filter((entry) => entry.method === 'model/rerouted')
|
|
@@ -322,6 +444,9 @@ async function resolveCodexTerminal(childThreadId, options = {}) {
|
|
|
322
444
|
throw new Error('Codex App Server returned the wrong child thread');
|
|
323
445
|
const turns = Array.isArray(thread?.turns) ? thread.turns : [];
|
|
324
446
|
const turn = turns.at(-1);
|
|
447
|
+
const turnValue = object(turn);
|
|
448
|
+
if (!turnValue || turnValue.status === 'inProgress')
|
|
449
|
+
throw new Error('CODEX_TURN_NOT_TERMINAL');
|
|
325
450
|
return parseCodexTurnCompleted({ threadId: childThreadId, turn });
|
|
326
451
|
}
|
|
327
452
|
finally {
|
|
@@ -364,19 +489,45 @@ async function preflightCodexLifecycleEvidence(repoRoot = process.cwd(), runtime
|
|
|
364
489
|
let discoveredHooks;
|
|
365
490
|
try {
|
|
366
491
|
await transport.initialize();
|
|
367
|
-
discoveredHooks = parseCodexHooksList(await transport.request('hooks/list', { cwds: [repoRoot] }), repoRoot);
|
|
492
|
+
discoveredHooks = parseCodexHooksList(await transport.request('hooks/list', { cwds: [repoRoot] }), repoRoot, process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host');
|
|
368
493
|
}
|
|
369
494
|
finally {
|
|
370
495
|
transport.close();
|
|
371
496
|
}
|
|
497
|
+
const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) => hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
|
|
498
|
+
&& hook.matcher === matcher
|
|
499
|
+
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`));
|
|
500
|
+
if (lifecycleHooks.some((matches) => matches.length !== 1)) {
|
|
501
|
+
throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hook source is not unique');
|
|
502
|
+
}
|
|
503
|
+
const matchedHooks = lifecycleHooks.flat();
|
|
504
|
+
const sources = new Set(matchedHooks.map((hook) => hook.isManaged ? 'managed' : hook.source));
|
|
505
|
+
const sourcePathDigests = new Set(matchedHooks.map((hook) => hook.sourcePathDigest));
|
|
506
|
+
if (sources.size !== 1 || sourcePathDigests.size !== 1) {
|
|
507
|
+
throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
|
|
508
|
+
}
|
|
509
|
+
const source = [...sources][0];
|
|
510
|
+
const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
|
|
511
|
+
? 'isolated-user'
|
|
512
|
+
: source === 'managed' ? 'managed' : 'project';
|
|
372
513
|
return Object.freeze({
|
|
373
514
|
cliVersion: match[1],
|
|
374
515
|
hookDefinitionHash,
|
|
375
516
|
staticReady: true,
|
|
376
517
|
discoveredHooks,
|
|
518
|
+
hookProvenance: Object.freeze({
|
|
519
|
+
hookSource,
|
|
520
|
+
hookSourcePathDigest: [...sourcePathDigests][0],
|
|
521
|
+
hookSourceHash: crypto.createHash('sha256').update(JSON.stringify(matchedHooks.map((hook) => ({
|
|
522
|
+
eventName: hook.eventName,
|
|
523
|
+
matcher: hook.matcher,
|
|
524
|
+
command: hook.command,
|
|
525
|
+
currentHash: hook.currentHash
|
|
526
|
+
})))).digest('hex')
|
|
527
|
+
}),
|
|
377
528
|
runtimeLiveness,
|
|
378
529
|
diagnostics: transport.diagnostics
|
|
379
530
|
});
|
|
380
531
|
}
|
|
381
|
-
export { CodexAppServerTransport, hasCodexRuntimeLiveness, parseCodexModelReroute, parseCodexHooksList, parseCodexThreadResolution, parseCodexTurnCompleted, preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread, validateCodexLifecycleHookConfig };
|
|
532
|
+
export { CodexAppServerTransport, hasCodexRuntimeLiveness, parseCodexModelReroute, parseCodexHooksList, parseCodexThreadResolution, parseCodexTurnCompleted, resolveCodexSpawnedChild, preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread, validateCodexLifecycleHookConfig };
|
|
382
533
|
//# sourceMappingURL=app-server.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const LIFECYCLE_PROTOCOL_VERSION = 3;
|
|
6
|
+
function exactText(value) {
|
|
7
|
+
return typeof value === 'string' && value.trim() === value && value.length > 0;
|
|
8
|
+
}
|
|
9
|
+
function normalizeRelativePath(value) {
|
|
10
|
+
return value.replaceAll('\\', '/');
|
|
11
|
+
}
|
|
12
|
+
function readLifecycleManifestFiles(packageRoot) {
|
|
13
|
+
const file = path.join(packageRoot, 'lib', 'agent-clients', 'adapters', 'codex-lifecycle', 'manifest-files.json');
|
|
14
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
15
|
+
if (!Array.isArray(value.executableFiles)
|
|
16
|
+
|| !Array.isArray(value.contractFiles)
|
|
17
|
+
|| !value.executableFiles.every(exactText)
|
|
18
|
+
|| !value.contractFiles.every(exactText)
|
|
19
|
+
|| new Set(value.executableFiles).size !== value.executableFiles.length
|
|
20
|
+
|| new Set(value.contractFiles).size !== value.contractFiles.length) {
|
|
21
|
+
throw new Error('Lifecycle manifest file list is invalid');
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
executableFiles: Object.freeze([...value.executableFiles]),
|
|
25
|
+
contractFiles: Object.freeze([...value.contractFiles])
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function executablePackageRoot() {
|
|
29
|
+
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
while (true) {
|
|
31
|
+
const candidate = path.join(current, 'package.json');
|
|
32
|
+
if (fs.existsSync(candidate)) {
|
|
33
|
+
const value = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
34
|
+
if (value.name === '@fitlab-ai/agent-infra')
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
const parent = path.dirname(current);
|
|
38
|
+
if (parent === current)
|
|
39
|
+
throw new Error('Lifecycle executable package root is unavailable');
|
|
40
|
+
current = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function hashFiles(root, files) {
|
|
44
|
+
const hash = crypto.createHash('sha256');
|
|
45
|
+
for (const relative of [...new Set(files.map(normalizeRelativePath))].sort()) {
|
|
46
|
+
if (path.isAbsolute(relative) || relative.split(/[\\/]/u).includes('..')) {
|
|
47
|
+
throw new Error(`Lifecycle manifest path '${relative}' is unsafe`);
|
|
48
|
+
}
|
|
49
|
+
const file = path.join(root, relative);
|
|
50
|
+
const stat = fs.lstatSync(file);
|
|
51
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
52
|
+
throw new Error(`Lifecycle manifest entry '${relative}' must be a regular file`);
|
|
53
|
+
}
|
|
54
|
+
hash.update(relative);
|
|
55
|
+
hash.update('\0');
|
|
56
|
+
hash.update(fs.readFileSync(file));
|
|
57
|
+
hash.update('\0');
|
|
58
|
+
}
|
|
59
|
+
return hash.digest('hex');
|
|
60
|
+
}
|
|
61
|
+
function resolveLifecycleExecutableFiles(root, entries) {
|
|
62
|
+
const pending = [...entries];
|
|
63
|
+
const resolved = new Set();
|
|
64
|
+
while (pending.length > 0) {
|
|
65
|
+
const relative = normalizeRelativePath(pending.pop());
|
|
66
|
+
if (resolved.has(relative))
|
|
67
|
+
continue;
|
|
68
|
+
resolved.add(relative);
|
|
69
|
+
if (!/\.[cm]?[jt]s$/u.test(relative))
|
|
70
|
+
continue;
|
|
71
|
+
const file = path.join(root, relative);
|
|
72
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
73
|
+
const imports = source.matchAll(/(?:\bfrom\s*|\bimport\s*(?:\(\s*)?)["'](\.[^"']+)["']/gu);
|
|
74
|
+
for (const match of imports) {
|
|
75
|
+
const requested = match[1];
|
|
76
|
+
const base = path.resolve(path.dirname(file), requested);
|
|
77
|
+
const candidates = [
|
|
78
|
+
base,
|
|
79
|
+
`${base}.ts`, `${base}.js`,
|
|
80
|
+
path.join(base, 'index.ts'), path.join(base, 'index.js')
|
|
81
|
+
];
|
|
82
|
+
const dependency = candidates.find((candidate) => fs.existsSync(candidate) && fs.lstatSync(candidate).isFile());
|
|
83
|
+
if (!dependency)
|
|
84
|
+
throw new Error(`Lifecycle executable dependency '${requested}' from '${relative}' is unavailable`);
|
|
85
|
+
const dependencyRelative = path.relative(root, dependency);
|
|
86
|
+
if (dependencyRelative.startsWith('..') || path.isAbsolute(dependencyRelative)) {
|
|
87
|
+
throw new Error(`Lifecycle executable dependency '${requested}' escapes the package root`);
|
|
88
|
+
}
|
|
89
|
+
pending.push(normalizeRelativePath(dependencyRelative));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...resolved].sort();
|
|
93
|
+
}
|
|
94
|
+
function computeLifecycleBuildIdentity(repoRoot, options = {}) {
|
|
95
|
+
const packageRoot = executablePackageRoot();
|
|
96
|
+
const executableRoot = options.executableFiles ? repoRoot : packageRoot;
|
|
97
|
+
const defaults = readLifecycleManifestFiles(packageRoot);
|
|
98
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(executableRoot, 'package.json'), 'utf8'));
|
|
99
|
+
if (!exactText(packageJson.version))
|
|
100
|
+
throw new Error('Lifecycle package version is invalid');
|
|
101
|
+
let internalExecutableBuildHash;
|
|
102
|
+
if (options.executableFiles) {
|
|
103
|
+
internalExecutableBuildHash = hashFiles(repoRoot, options.executableFiles);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const manifestFile = path.join(executableRoot, 'dist', 'lifecycle-build-manifest.json');
|
|
107
|
+
if (fs.existsSync(manifestFile)) {
|
|
108
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
|
109
|
+
if (manifest.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION
|
|
110
|
+
|| manifest.packageVersion !== packageJson.version
|
|
111
|
+
|| !exactText(manifest.sourceInputHash)
|
|
112
|
+
|| !exactText(manifest.internalExecutableBuildHash)) {
|
|
113
|
+
throw new Error('Lifecycle executable build manifest is stale or invalid');
|
|
114
|
+
}
|
|
115
|
+
if (fileURLToPath(import.meta.url).split(path.sep).includes('dist')) {
|
|
116
|
+
const compiledFiles = defaults.executableFiles.map((file) => file.endsWith('.ts') ? path.join('dist', file.replace(/\.ts$/u, '.js')) : file);
|
|
117
|
+
if (hashFiles(executableRoot, resolveLifecycleExecutableFiles(executableRoot, compiledFiles)) !== manifest.internalExecutableBuildHash) {
|
|
118
|
+
throw new Error('Lifecycle compiled executable build does not match its manifest');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (hashFiles(executableRoot, resolveLifecycleExecutableFiles(executableRoot, defaults.executableFiles)) !== manifest.sourceInputHash) {
|
|
122
|
+
throw new Error('Lifecycle executable build manifest is stale or invalid');
|
|
123
|
+
}
|
|
124
|
+
internalExecutableBuildHash = manifest.internalExecutableBuildHash;
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
internalExecutableBuildHash = hashFiles(executableRoot, resolveLifecycleExecutableFiles(executableRoot, defaults.executableFiles));
|
|
128
|
+
}
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
protocolVersion: LIFECYCLE_PROTOCOL_VERSION,
|
|
131
|
+
packageVersion: packageJson.version,
|
|
132
|
+
internalExecutableBuildHash,
|
|
133
|
+
lifecycleContractHash: hashFiles(repoRoot, options.contractFiles ?? defaults.contractFiles)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function verifyLifecycleBuildIdentity(expected, actual) {
|
|
137
|
+
if (actual.protocolVersion !== expected.protocolVersion) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
code: 'CODEX_LIFECYCLE_PROTOCOL_MISMATCH',
|
|
141
|
+
message: 'Codex lifecycle protocol version does not match'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (actual.packageVersion !== expected.packageVersion
|
|
145
|
+
|| actual.internalExecutableBuildHash !== expected.internalExecutableBuildHash) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
code: 'CODEX_LIFECYCLE_BUILD_MISMATCH',
|
|
149
|
+
message: 'Codex lifecycle executable build identity does not match'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (actual.lifecycleContractHash !== expected.lifecycleContractHash) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
code: 'CODEX_LIFECYCLE_CONTRACT_MISMATCH',
|
|
156
|
+
message: 'Codex lifecycle contract identity does not match'
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, code: null, message: null };
|
|
160
|
+
}
|
|
161
|
+
export { LIFECYCLE_PROTOCOL_VERSION, computeLifecycleBuildIdentity, readLifecycleManifestFiles, resolveLifecycleExecutableFiles, verifyLifecycleBuildIdentity };
|
|
162
|
+
//# sourceMappingURL=build-identity.js.map
|