@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.
Files changed (172) hide show
  1. package/dist/bin/internal-cli.js +8 -3
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
  8. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -4
  11. package/dist/lib/internal/codex-lifecycle.js +169 -3
  12. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  13. package/dist/lib/internal/release-workflow.js +108 -11
  14. package/dist/lib/internal/sandbox-control.js +43 -6
  15. package/dist/lib/internal/task-create.js +12 -4
  16. package/dist/lib/internal/task-orchestration.js +40 -7
  17. package/dist/lib/platform/context.js +6 -4
  18. package/dist/lib/platform/github-client.js +55 -2
  19. package/dist/lib/platform/issue-comments.js +2 -1
  20. package/dist/lib/process-data/checkpoint.js +319 -0
  21. package/dist/lib/process-data/index.js +128 -60
  22. package/dist/lib/process-data/materialize.js +84 -0
  23. package/dist/lib/process-data/normalize.js +34 -1
  24. package/dist/lib/process-data/reconcile.js +55 -1
  25. package/dist/lib/process-data/sources.js +422 -1
  26. package/dist/lib/process-data/store.js +341 -11
  27. package/dist/lib/release/workflow.js +29 -21
  28. package/dist/lib/sandbox/commands/create.js +111 -60
  29. package/dist/lib/sandbox/commands/prune.js +40 -16
  30. package/dist/lib/sandbox/commands/rm.js +372 -94
  31. package/dist/lib/sandbox/commands/start.js +4 -3
  32. package/dist/lib/sandbox/control/client.js +138 -22
  33. package/dist/lib/sandbox/control/container-identity.js +64 -0
  34. package/dist/lib/sandbox/control/executor.js +142 -0
  35. package/dist/lib/sandbox/control/lifecycle.js +557 -0
  36. package/dist/lib/sandbox/control/protocol.js +69 -26
  37. package/dist/lib/sandbox/control/server.js +395 -99
  38. package/dist/lib/sandbox/control/state.js +191 -0
  39. package/dist/lib/sandbox/control/timing.js +8 -0
  40. package/dist/lib/sandbox/index.js +2 -2
  41. package/dist/lib/sandbox/managed-fs.js +14 -2
  42. package/dist/lib/sandbox/mounts.js +22 -19
  43. package/dist/lib/sandbox/recovery.js +284 -50
  44. package/dist/lib/sandbox/workspace-view.js +90 -10
  45. package/dist/lib/sandbox/worktree-safety.js +196 -0
  46. package/dist/lib/server/daemon.js +1 -1
  47. package/dist/lib/server/process-control.js +17 -1
  48. package/dist/lib/server/process-state.js +57 -20
  49. package/dist/lib/task/artifact-lifecycle.js +87 -15
  50. package/dist/lib/task/artifact-receipts.js +128 -0
  51. package/dist/lib/task/codex-orchestration.js +236 -0
  52. package/dist/lib/task/commands/status.js +3 -0
  53. package/dist/lib/task/commands/validate.js +289 -0
  54. package/dist/lib/task/commit-finalization.js +36 -9
  55. package/dist/lib/task/create.js +10 -2
  56. package/dist/lib/task/delegation-receipts.js +109 -2
  57. package/dist/lib/task/events.js +149 -6
  58. package/dist/lib/task/finalization-artifacts.js +2 -1
  59. package/dist/lib/task/implementation-inputs.js +1 -7
  60. package/dist/lib/task/index.js +9 -0
  61. package/dist/lib/task/ledger.js +13 -1
  62. package/dist/lib/task/orchestration.js +365 -15
  63. package/dist/lib/task/sections.js +5 -1
  64. package/dist/lib/task/task-execution-lock.js +5 -4
  65. package/dist/lib/task/verification-engine.js +78 -14
  66. package/dist/lib/task/verification.js +1 -0
  67. package/dist/lifecycle-build-manifest.json +6 -0
  68. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
  69. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  70. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
  71. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
  72. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  73. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
  74. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
  75. package/lib/agent-clients/adapters/codex.ts +1 -1
  76. package/lib/defaults.json +10 -2
  77. package/lib/git/workflow.ts +25 -3
  78. package/lib/internal/codex-lifecycle.ts +175 -4
  79. package/lib/internal/codex-sandbox-controller.ts +109 -0
  80. package/lib/internal/release-workflow.ts +107 -11
  81. package/lib/internal/sandbox-control.ts +38 -6
  82. package/lib/internal/task-create.ts +16 -4
  83. package/lib/internal/task-orchestration.ts +34 -7
  84. package/lib/platform/context.ts +8 -5
  85. package/lib/platform/github-client.ts +61 -3
  86. package/lib/platform/issue-comments.ts +2 -1
  87. package/lib/process-data/checkpoint.ts +335 -0
  88. package/lib/process-data/index.ts +112 -60
  89. package/lib/process-data/materialize.ts +83 -0
  90. package/lib/process-data/normalize.ts +29 -1
  91. package/lib/process-data/reconcile.ts +64 -2
  92. package/lib/process-data/sources.ts +449 -2
  93. package/lib/process-data/store.ts +304 -11
  94. package/lib/process-data/types.ts +124 -2
  95. package/lib/release/workflow.ts +69 -20
  96. package/lib/sandbox/commands/create.ts +70 -25
  97. package/lib/sandbox/commands/prune.ts +53 -15
  98. package/lib/sandbox/commands/rm.ts +423 -108
  99. package/lib/sandbox/commands/start.ts +4 -3
  100. package/lib/sandbox/control/client.ts +152 -28
  101. package/lib/sandbox/control/container-identity.ts +93 -0
  102. package/lib/sandbox/control/executor.ts +167 -0
  103. package/lib/sandbox/control/lifecycle.ts +573 -0
  104. package/lib/sandbox/control/protocol.ts +121 -68
  105. package/lib/sandbox/control/server.ts +398 -109
  106. package/lib/sandbox/control/state.ts +213 -0
  107. package/lib/sandbox/control/timing.ts +12 -0
  108. package/lib/sandbox/index.ts +2 -2
  109. package/lib/sandbox/managed-fs.ts +17 -2
  110. package/lib/sandbox/mounts.ts +16 -8
  111. package/lib/sandbox/recovery.ts +335 -65
  112. package/lib/sandbox/workspace-view.ts +102 -11
  113. package/lib/sandbox/worktree-safety.ts +234 -0
  114. package/lib/server/daemon.ts +1 -1
  115. package/lib/server/process-control.ts +24 -3
  116. package/lib/server/process-state.ts +82 -27
  117. package/lib/task/artifact-lifecycle.ts +78 -14
  118. package/lib/task/artifact-receipts.ts +161 -0
  119. package/lib/task/codex-orchestration.ts +319 -0
  120. package/lib/task/commands/status.ts +2 -0
  121. package/lib/task/commands/validate.ts +282 -0
  122. package/lib/task/commit-finalization.ts +52 -9
  123. package/lib/task/create.ts +11 -2
  124. package/lib/task/delegation-receipts.ts +208 -7
  125. package/lib/task/events.ts +131 -5
  126. package/lib/task/finalization-artifacts.ts +2 -1
  127. package/lib/task/implementation-inputs.ts +1 -5
  128. package/lib/task/index.ts +8 -0
  129. package/lib/task/ledger.ts +16 -1
  130. package/lib/task/orchestration.ts +552 -18
  131. package/lib/task/sections.ts +6 -2
  132. package/lib/task/task-execution-lock.ts +11 -9
  133. package/lib/task/verification-engine.ts +93 -24
  134. package/lib/task/verification.ts +2 -1
  135. package/package.json +1 -1
  136. package/templates/.agents/README.en.md +7 -0
  137. package/templates/.agents/README.zh-CN.md +5 -0
  138. package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
  139. package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
  140. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
  141. package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
  142. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
  143. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  144. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  145. package/templates/.agents/rules/review-handshake.en.md +3 -1
  146. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  147. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  148. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  149. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  150. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  151. package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
  152. package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
  153. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
  154. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
  155. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
  156. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
  157. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  158. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  159. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
  160. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
  161. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  162. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  163. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
  164. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
  165. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  166. package/templates/.claude/commands/run-manual-validation.en.md +9 -0
  167. package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
  168. package/templates/.codex/hooks.json +8 -8
  169. package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
  170. package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
  171. package/templates/.agents/workspace/README.en.md +0 -26
  172. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -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: '^collaborationspawn_agent$', phase: 'post-tool' }),
10
- Object.freeze({ event: 'SubagentStart', matcher: '^agent-infra-lifecycle-(executor|reviewer)$', phase: 'subagent-start' }),
11
- Object.freeze({ event: 'SubagentStop', matcher: '^agent-infra-lifecycle-(executor|reviewer)$', phase: 'subagent-stop' })
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 === 5
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, resumeValue) {
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 resumedChildThreadId = nonEmpty(resumedThread?.id);
99
- const model = nonEmpty(resumeRoot?.model);
100
- const reasoningEffort = nonEmpty(resumeRoot?.reasoningEffort ?? object(resumeRoot?.settings)?.effort);
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
- || resumedChildThreadId !== childThreadId
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
- resolution = parseCodexThreadResolution(readResult, resumeResult);
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 resumed settings are unavailable');
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