@fitlab-ai/agent-infra 0.12.1 → 0.12.2

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 (303) hide show
  1. package/README.md +0 -2
  2. package/README.zh-CN.md +0 -2
  3. package/dist/bin/internal-cli.js +2 -17
  4. package/dist/lib/agent-clients/adapter.js +0 -13
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
  6. package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
  7. package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
  8. package/dist/lib/agent-clients/adapters/codex.js +0 -52
  9. package/dist/lib/agent-clients/adapters/opencode.js +0 -5
  10. package/dist/lib/agent-clients/adapters/traecli.js +0 -5
  11. package/dist/lib/agent-clients/config.js +19 -54
  12. package/dist/lib/agent-clients/registry.js +1 -12
  13. package/dist/lib/agent-clients/schema.js +0 -18
  14. package/dist/lib/agent-clients/tokens.js +1 -2
  15. package/dist/lib/agent-clients/types.js +0 -1
  16. package/dist/lib/decide.js +2 -0
  17. package/dist/lib/git/worktree-identity.js +1 -9
  18. package/dist/lib/internal/agent-client.js +2 -62
  19. package/dist/lib/internal/cli-route-inventory.js +1 -5
  20. package/dist/lib/internal/manual-validation.js +18 -10
  21. package/dist/lib/internal/task-create.js +1 -1
  22. package/dist/lib/internal/task-event.js +1 -8
  23. package/dist/lib/internal/task-finalization.js +0 -1
  24. package/dist/lib/internal/task-lifecycle.js +2 -2
  25. package/dist/lib/internal/task-operation-registry.js +4 -20
  26. package/dist/lib/internal/task-review.js +4 -4
  27. package/dist/lib/platform/comment-safety.js +1 -7
  28. package/dist/lib/platform/in-label-sync.js +1 -14
  29. package/dist/lib/platform/issue-comments.js +1 -16
  30. package/dist/lib/platform/issues.js +1 -1
  31. package/dist/lib/platform/operation-coordinator.js +4 -2
  32. package/dist/lib/platform/pr-change-report.js +1 -4
  33. package/dist/lib/platform/pr-checks.js +2 -8
  34. package/dist/lib/platform/provider-validation.js +0 -20
  35. package/dist/lib/platform/pull-requests.js +73 -84
  36. package/dist/lib/platform/releases.js +1 -43
  37. package/dist/lib/platform/verification-required.js +0 -2
  38. package/dist/lib/platform/verification-sync.js +1 -79
  39. package/dist/lib/process-data/normalize.js +1 -185
  40. package/dist/lib/process-data/privacy.js +1 -16
  41. package/dist/lib/process-data/sources.js +2 -279
  42. package/dist/lib/render.js +0 -12
  43. package/dist/lib/run/index.js +15 -10
  44. package/dist/lib/run/skills.js +0 -3
  45. package/dist/lib/sandbox/commands/create.js +40 -53
  46. package/dist/lib/sandbox/commands/list-running.js +0 -18
  47. package/dist/lib/sandbox/commands/start.js +7 -8
  48. package/dist/lib/sandbox/constants.js +0 -6
  49. package/dist/lib/sandbox/control/client.js +0 -1
  50. package/dist/lib/sandbox/control/container-identity.js +1 -16
  51. package/dist/lib/sandbox/control/executor.js +0 -4
  52. package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
  53. package/dist/lib/sandbox/control/lifecycle.js +21 -37
  54. package/dist/lib/sandbox/control/native-file-lock.js +0 -3
  55. package/dist/lib/sandbox/control/server.js +18 -38
  56. package/dist/lib/sandbox/control/state.js +1 -9
  57. package/dist/lib/sandbox/dockerfile.js +0 -6
  58. package/dist/lib/sandbox/engine.js +0 -14
  59. package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
  60. package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
  61. package/dist/lib/sandbox/recovery.js +24 -26
  62. package/dist/lib/sandbox/removal.js +11 -519
  63. package/dist/lib/sandbox/task-resolver.js +0 -41
  64. package/dist/lib/sandbox/tools.js +1 -4
  65. package/dist/lib/sandbox/workspace-view.js +1 -2
  66. package/dist/lib/server/display.js +0 -6
  67. package/dist/lib/task/activity-intent.js +45 -40
  68. package/dist/lib/task/activity-log.js +9 -16
  69. package/dist/lib/task/artifact-command.js +1 -1
  70. package/dist/lib/task/artifact-lifecycle.js +61 -63
  71. package/dist/lib/task/artifact-name.js +0 -6
  72. package/dist/lib/task/artifact-operations.js +3 -3
  73. package/dist/lib/task/capabilities.js +4 -5
  74. package/dist/lib/task/commands/log.js +0 -6
  75. package/dist/lib/task/commands/status.js +17 -60
  76. package/dist/lib/task/commit-operation.js +28 -39
  77. package/dist/lib/task/completion-facts.js +56 -43
  78. package/dist/lib/task/control-authority.js +23 -256
  79. package/dist/lib/task/control-recovery.js +3 -5
  80. package/dist/lib/task/create.js +2 -4
  81. package/dist/lib/task/decision-intents.js +8 -9
  82. package/dist/lib/task/delivery-target.js +1 -40
  83. package/dist/lib/task/events.js +84 -107
  84. package/dist/lib/task/finalization.js +44 -8
  85. package/dist/lib/task/ledger-intents.js +1 -1
  86. package/dist/lib/task/lifecycle-execution.js +38 -33
  87. package/dist/lib/task/lifecycle.js +49 -41
  88. package/dist/lib/task/local-artifact-finalization.js +1 -4
  89. package/dist/lib/task/manual-validation-evidence.js +1 -37
  90. package/dist/lib/task/platform-operation-recovery.js +67 -24
  91. package/dist/lib/task/pr-delivery-fact.js +2 -5
  92. package/dist/lib/task/qualification-confirmation.js +5 -7
  93. package/dist/lib/task/resolve-ref.js +1 -25
  94. package/dist/lib/task/review-command.js +2 -2
  95. package/dist/lib/task/review-finalization.js +1 -21
  96. package/dist/lib/task/review-fingerprint.js +0 -4
  97. package/dist/lib/task/secure-fs.js +0 -56
  98. package/dist/lib/task/short-id.js +1 -39
  99. package/dist/lib/task/skill-scope.js +0 -1
  100. package/dist/lib/task/verification-engine.js +58 -282
  101. package/dist/lib/task/verification.js +6 -3
  102. package/dist/lib/task/write.js +11 -1
  103. package/lib/agent-clients/adapter.ts +0 -76
  104. package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
  105. package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
  106. package/lib/agent-clients/adapters/claude-code.ts +0 -9
  107. package/lib/agent-clients/adapters/codex.ts +0 -56
  108. package/lib/agent-clients/adapters/opencode.ts +0 -5
  109. package/lib/agent-clients/adapters/traecli.ts +0 -5
  110. package/lib/agent-clients/config.ts +20 -61
  111. package/lib/agent-clients/registry.ts +1 -34
  112. package/lib/agent-clients/schema.ts +0 -18
  113. package/lib/agent-clients/tokens.ts +1 -2
  114. package/lib/agent-clients/types.ts +0 -15
  115. package/lib/decide.ts +1 -0
  116. package/lib/git/worktree-identity.ts +0 -10
  117. package/lib/internal/agent-client.ts +2 -70
  118. package/lib/internal/cli-route-inventory.ts +1 -4
  119. package/lib/internal/manual-validation.ts +16 -9
  120. package/lib/internal/task-create.ts +0 -1
  121. package/lib/internal/task-event.ts +1 -5
  122. package/lib/internal/task-finalization.ts +0 -1
  123. package/lib/internal/task-lifecycle.ts +3 -5
  124. package/lib/internal/task-operation-registry.ts +3 -19
  125. package/lib/internal/task-review.ts +4 -4
  126. package/lib/platform/comment-safety.ts +0 -10
  127. package/lib/platform/github-provider.ts +0 -2
  128. package/lib/platform/in-label-sync.ts +0 -16
  129. package/lib/platform/issue-comments.ts +0 -21
  130. package/lib/platform/issues.ts +0 -2
  131. package/lib/platform/operation-coordinator.ts +4 -2
  132. package/lib/platform/pr-change-report.ts +0 -5
  133. package/lib/platform/pr-checks.ts +1 -10
  134. package/lib/platform/pr-review.ts +1 -1
  135. package/lib/platform/provider-bridge.ts +0 -1
  136. package/lib/platform/provider-loader.ts +1 -5
  137. package/lib/platform/provider-validation.ts +1 -49
  138. package/lib/platform/pull-requests.ts +64 -99
  139. package/lib/platform/releases.ts +1 -40
  140. package/lib/platform/verification-required.ts +0 -2
  141. package/lib/platform/verification-sync.ts +0 -94
  142. package/lib/process-data/normalize.ts +1 -186
  143. package/lib/process-data/privacy.ts +2 -19
  144. package/lib/process-data/sources.ts +3 -283
  145. package/lib/render.ts +0 -15
  146. package/lib/run/index.ts +13 -7
  147. package/lib/run/skills.ts +0 -4
  148. package/lib/sandbox/commands/create.ts +37 -74
  149. package/lib/sandbox/commands/list-running.ts +0 -19
  150. package/lib/sandbox/commands/start.ts +7 -10
  151. package/lib/sandbox/constants.ts +0 -8
  152. package/lib/sandbox/control/client.ts +0 -1
  153. package/lib/sandbox/control/container-identity.ts +1 -16
  154. package/lib/sandbox/control/executor.ts +1 -5
  155. package/lib/sandbox/control/identity-sentinel.ts +0 -8
  156. package/lib/sandbox/control/lifecycle.ts +18 -51
  157. package/lib/sandbox/control/native-file-lock.ts +0 -8
  158. package/lib/sandbox/control/protocol.ts +0 -2
  159. package/lib/sandbox/control/server.ts +13 -39
  160. package/lib/sandbox/control/state.ts +1 -10
  161. package/lib/sandbox/dockerfile.ts +0 -11
  162. package/lib/sandbox/engine.ts +0 -17
  163. package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
  164. package/lib/sandbox/engines/wsl2-paths.ts +0 -4
  165. package/lib/sandbox/recovery.ts +24 -26
  166. package/lib/sandbox/removal.ts +11 -696
  167. package/lib/sandbox/shell.ts +1 -1
  168. package/lib/sandbox/task-resolver.ts +0 -48
  169. package/lib/sandbox/tools.ts +1 -5
  170. package/lib/sandbox/workspace-view.ts +2 -4
  171. package/lib/server/display.ts +0 -14
  172. package/lib/server/process-control.ts +1 -1
  173. package/lib/task/activity-intent.ts +36 -44
  174. package/lib/task/activity-log.ts +9 -22
  175. package/lib/task/artifact-command.ts +1 -1
  176. package/lib/task/artifact-lifecycle.ts +58 -67
  177. package/lib/task/artifact-name.ts +0 -7
  178. package/lib/task/artifact-operations.ts +3 -3
  179. package/lib/task/capabilities.ts +5 -6
  180. package/lib/task/commands/log.ts +0 -7
  181. package/lib/task/commands/status.ts +14 -79
  182. package/lib/task/commit-operation.ts +29 -51
  183. package/lib/task/completion-facts.ts +45 -42
  184. package/lib/task/control-authority.ts +27 -317
  185. package/lib/task/control-recovery.ts +2 -5
  186. package/lib/task/create.ts +2 -5
  187. package/lib/task/decision-intents.ts +5 -5
  188. package/lib/task/delivery-target.ts +0 -40
  189. package/lib/task/events.ts +89 -123
  190. package/lib/task/finalization.ts +41 -9
  191. package/lib/task/ledger-intents.ts +1 -1
  192. package/lib/task/lifecycle-execution.ts +37 -89
  193. package/lib/task/lifecycle.ts +48 -44
  194. package/lib/task/local-artifact-finalization.ts +0 -7
  195. package/lib/task/manual-validation-evidence.ts +1 -47
  196. package/lib/task/platform-operation-recovery.ts +50 -26
  197. package/lib/task/pr-delivery-fact.ts +2 -11
  198. package/lib/task/qualification-confirmation.ts +6 -6
  199. package/lib/task/resolve-ref.ts +0 -23
  200. package/lib/task/review-command.ts +2 -2
  201. package/lib/task/review-finalization.ts +1 -27
  202. package/lib/task/review-fingerprint.ts +0 -8
  203. package/lib/task/secure-fs.ts +0 -42
  204. package/lib/task/short-id.ts +0 -43
  205. package/lib/task/skill-scope.ts +0 -1
  206. package/lib/task/verification-engine.ts +56 -287
  207. package/lib/task/verification.ts +5 -5
  208. package/lib/task/write.ts +12 -1
  209. package/package.json +2 -2
  210. package/templates/.agents/README.en.md +1 -3
  211. package/templates/.agents/README.zh-CN.md +0 -2
  212. package/templates/.agents/rules/README.en.md +0 -1
  213. package/templates/.agents/rules/README.zh-CN.md +0 -1
  214. package/templates/.agents/rules/commit-and-pr.en.md +1 -1
  215. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
  216. package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
  217. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
  218. package/templates/.agents/rules/pr-sync.en.md +1 -1
  219. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
  220. package/templates/.agents/rules/task-management.en.md +3 -3
  221. package/templates/.agents/rules/task-management.zh-CN.md +3 -3
  222. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  223. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  224. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  225. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  226. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  227. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  228. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  229. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  230. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
  231. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
  232. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  233. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  234. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  235. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  236. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  237. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  238. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  239. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  240. package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
  241. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
  242. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  243. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  244. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
  245. package/templates/.agents/templates/task.en.md +1 -0
  246. package/templates/.agents/templates/task.zh-CN.md +1 -0
  247. package/templates/.claude/settings.json +0 -24
  248. package/templates/.codex/hooks.json +0 -50
  249. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
  250. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
  251. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
  252. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
  253. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
  254. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
  255. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
  256. package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
  257. package/dist/lib/internal/task-orchestration.js +0 -71
  258. package/dist/lib/platform/verification.js +0 -6
  259. package/dist/lib/run/prompt.js +0 -2
  260. package/dist/lib/sandbox/engines/authority.js +0 -289
  261. package/dist/lib/task/delegation-receipts.js +0 -410
  262. package/dist/lib/task/orchestration.js +0 -1233
  263. package/dist/lib/task/verification-checks.js +0 -9
  264. package/dist/lib/task/workspace-snapshot.js +0 -121
  265. package/dist/lifecycle-build-manifest.json +0 -6
  266. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
  267. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
  268. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
  269. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
  270. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
  271. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
  272. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
  273. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
  274. package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
  275. package/lib/internal/task-orchestration.ts +0 -74
  276. package/lib/platform/verification.ts +0 -11
  277. package/lib/run/prompt.ts +0 -1
  278. package/lib/sandbox/engines/authority.ts +0 -365
  279. package/lib/task/delegation-receipts.ts +0 -547
  280. package/lib/task/orchestration.ts +0 -1651
  281. package/lib/task/verification-checks.ts +0 -10
  282. package/lib/task/workspace-snapshot.ts +0 -144
  283. package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
  284. package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
  285. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
  286. package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
  287. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
  288. package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
  289. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
  290. package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
  291. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
  292. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
  293. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
  294. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
  295. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
  296. package/templates/.claude/commands/run-task.en.md +0 -8
  297. package/templates/.claude/commands/run-task.zh-CN.md +0 -8
  298. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
  299. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
  300. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
  301. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
  302. package/templates/.opencode/commands/run-task.en.md +0 -11
  303. package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
@@ -1,688 +0,0 @@
1
- import { spawn, spawnSync } from 'node:child_process';
2
- import crypto from 'node:crypto';
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import semver from 'semver';
7
-
8
- import type { ChildProcessWithoutNullStreams } from 'node:child_process';
9
- import type { CodexLifecycleEvent } from './evidence.ts';
10
- import { parseCodexLifecycleBinding } from './binding.ts';
11
- import { hasActiveCodexLifecycleEvidence } from './store.ts';
12
- import { resolveTaskContext, resolveTaskRef } from '../../../task/resolve-ref.ts';
13
-
14
- type JsonObject = Record<string, unknown>;
15
- type ThreadResolution = Readonly<{
16
- thread: Extract<CodexLifecycleEvent, { type: 'app-thread' }>;
17
- settings: Extract<CodexLifecycleEvent, { type: 'app-settings' }>;
18
- }>;
19
-
20
- type DiscoveredHook = Readonly<{
21
- eventName: string;
22
- matcher: string | null;
23
- command: string;
24
- enabled: boolean;
25
- source: string;
26
- sourcePathDigest: string;
27
- trustStatus: string;
28
- currentHash: string;
29
- isManaged: boolean;
30
- pluginId: string | null;
31
- }>;
32
-
33
- type AppServerTransportOptions = Readonly<{
34
- command?: string;
35
- args?: readonly string[];
36
- cwd?: string;
37
- env?: NodeJS.ProcessEnv;
38
- timeoutMs?: number;
39
- rolloutReadAttempts?: number;
40
- rolloutRetryMs?: number;
41
- threadReadAttempts?: number;
42
- threadRetryMs?: number;
43
- terminalReadAttempts?: number;
44
- terminalRetryMs?: number;
45
- }>;
46
-
47
- type CodexRuntimeIdentity = Readonly<{
48
- sessionId: string;
49
- turnId: string;
50
- toolUseId: string;
51
- }>;
52
-
53
- const LIFECYCLE_HOOKS = Object.freeze([
54
- Object.freeze({ event: 'PreToolUse', matcher: '^collaborationspawn_agent$', phase: 'pre-tool' }),
55
- Object.freeze({ event: 'PostToolUse', matcher: '', phase: 'post-tool' }),
56
- Object.freeze({ event: 'SubagentStart', matcher: '', phase: 'subagent-start' }),
57
- Object.freeze({ event: 'SubagentStop', matcher: '', phase: 'subagent-stop' })
58
- ]);
59
- const DEFAULT_ROLLOUT_READ_ATTEMPTS = 81;
60
- const DEFAULT_ROLLOUT_RETRY_MS = 100;
61
- const DEFAULT_THREAD_READ_ATTEMPTS = 21;
62
- const DEFAULT_THREAD_RETRY_MS = 100;
63
- const DEFAULT_TERMINAL_READ_ATTEMPTS = 21;
64
- const DEFAULT_TERMINAL_RETRY_MS = 100;
65
-
66
- function object(value: unknown): JsonObject | null {
67
- return value && typeof value === 'object' && !Array.isArray(value)
68
- ? value as JsonObject
69
- : null;
70
- }
71
-
72
- function nonEmpty(value: unknown): string | null {
73
- return typeof value === 'string' && value.trim() ? value : null;
74
- }
75
-
76
- function readRolloutRecords(transcriptPath: string, sessionId: string): JsonObject[] {
77
- const name = path.basename(transcriptPath);
78
- if (!transcriptPath || (!name.endsWith(`-${sessionId}.jsonl`) && name !== `${sessionId}.jsonl`)) {
79
- throw new Error('Codex parent rollout path is invalid');
80
- }
81
- const stat = fs.lstatSync(transcriptPath);
82
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('Codex parent rollout is not a regular file');
83
- const offset = Math.max(0, stat.size - 8 * 1024 * 1024);
84
- const buffer = Buffer.alloc(stat.size - offset);
85
- const descriptor = fs.openSync(transcriptPath, 'r');
86
- try {
87
- fs.readSync(descriptor, buffer, 0, buffer.length, offset);
88
- } finally {
89
- fs.closeSync(descriptor);
90
- }
91
- let text = buffer.toString('utf8');
92
- if (offset) text = text.slice(text.indexOf('\n') + 1);
93
- return text.split(/\r?\n/u).filter(Boolean).map((line) => object(JSON.parse(line))).filter((record): record is JsonObject => Boolean(record));
94
- }
95
-
96
- function isExpectedSpawnCall(record: JsonObject, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
97
- const payload = object(record.payload);
98
- return record.type === 'response_item'
99
- && payload?.type === 'function_call'
100
- && payload.call_id === expected.toolUseId
101
- && payload.namespace === 'collaboration'
102
- && payload.name === 'spawn_agent';
103
- }
104
-
105
- function spawnArgsMatch(args: JsonObject | null, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
106
- return args?.agent_type === expected.nativeAgent
107
- && args.task_name === expected.taskName
108
- && (!expected.requestedModel || args.model === expected.requestedModel)
109
- && (!expected.requestedReasoningEffort || args.reasoning_effort === expected.requestedReasoningEffort);
110
- }
111
-
112
- function isExpectedChildActivity(record: JsonObject, expected: Parameters<typeof resolveCodexSpawnedChild>[1]): boolean {
113
- const payload = object(record.payload);
114
- const item = object(payload?.item);
115
- return record.type === 'event_msg'
116
- && payload?.type === 'item_completed'
117
- && payload.thread_id === expected.sessionId
118
- && payload.turn_id === expected.turnId
119
- && item?.type === 'SubAgentActivity'
120
- && item.id === expected.toolUseId
121
- && item.kind === 'started'
122
- && item.agent_path === `/root/${expected.taskName}`
123
- && Boolean(nonEmpty(item.agent_thread_id));
124
- }
125
-
126
- function resolveCodexSpawnedChild(
127
- transcriptPath: string,
128
- expected: Readonly<{
129
- sessionId: string;
130
- turnId: string;
131
- toolUseId: string;
132
- nativeAgent: string;
133
- taskName: string;
134
- requestedModel?: string;
135
- requestedReasoningEffort?: string;
136
- }>
137
- ): string {
138
- if (!parseCodexLifecycleBinding(expected.taskName)) {
139
- throw new Error('Codex spawn task_name does not carry a valid lifecycle binding');
140
- }
141
- const records = readRolloutRecords(transcriptPath, expected.sessionId);
142
- const calls = records.filter((record) => isExpectedSpawnCall(record, expected));
143
- if (calls.length !== 1) throw new Error('Codex parent rollout spawn call was not found uniquely');
144
- const call = object(calls[0]?.payload);
145
- const args = object(JSON.parse(nonEmpty(call?.arguments) ?? '{}'));
146
- if (!spawnArgsMatch(args, expected)) throw new Error('Codex parent rollout spawn identity does not match the hook event');
147
- const activities = records.filter((record) => isExpectedChildActivity(record, expected));
148
- if (activities.length !== 1) throw new Error('Codex parent rollout child activity was not found uniquely');
149
- return nonEmpty(object(object(activities[0]?.payload)?.item)?.agent_thread_id)!;
150
- }
151
-
152
- function validateCodexLifecycleHookConfig(value: unknown): void {
153
- const hooks = object(object(value)?.hooks);
154
- const valid = hooks && LIFECYCLE_HOOKS.every(({ event, matcher, phase }) => {
155
- const entries = hooks[event];
156
- if (!Array.isArray(entries)) return false;
157
- return entries.some((entryValue) => {
158
- const entry = object(entryValue);
159
- if (entry?.matcher !== matcher || !Array.isArray(entry.hooks)) return false;
160
- return entry.hooks.some((hookValue) => {
161
- const hook = object(hookValue);
162
- return hook?.type === 'command'
163
- && hook.timeout === 30
164
- && hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`;
165
- });
166
- });
167
- });
168
- if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_INVALID: lifecycle hooks are invalid');
169
- }
170
-
171
- function parseCodexHooksList(
172
- value: unknown,
173
- repoRoot: string
174
- ): readonly DiscoveredHook[] {
175
- const root = object(value);
176
- const entries = Array.isArray(root?.data) ? root.data : [];
177
- const entry = entries.map(object).find((candidate) => candidate?.cwd === repoRoot);
178
- const errors = Array.isArray(entry?.errors) ? entry.errors : [];
179
- if (errors.length) throw new Error(`CODEX_PREFLIGHT_HOOK_DISCOVERY_FAILED: ${errors.map(String).join('; ')}`);
180
- const hooks = (Array.isArray(entry?.hooks) ? entry.hooks : [])
181
- .map(object)
182
- .filter((hook): hook is JsonObject => hook !== null)
183
- .map((hook) => Object.freeze({
184
- eventName: String(hook.eventName ?? ''),
185
- matcher: typeof hook.matcher === 'string' ? hook.matcher : null,
186
- command: String(hook.command ?? ''),
187
- enabled: hook.enabled === true,
188
- source: String(hook.source ?? ''),
189
- sourcePathDigest: typeof hook.sourcePath === 'string' && path.isAbsolute(hook.sourcePath)
190
- ? crypto.createHash('sha256').update(hook.sourcePath).digest('hex')
191
- : '',
192
- trustStatus: String(hook.trustStatus ?? ''),
193
- currentHash: String(hook.currentHash ?? ''),
194
- isManaged: hook.isManaged === true,
195
- pluginId: typeof hook.pluginId === 'string' ? hook.pluginId : null
196
- }));
197
- const valid = LIFECYCLE_HOOKS.every(({ event, matcher, phase }) => hooks.some((hook) =>
198
- hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
199
- && hook.matcher === matcher
200
- && hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`
201
- && hook.enabled
202
- && hook.currentHash.length > 0
203
- && hook.sourcePathDigest.length === 64
204
- && hook.pluginId === null
205
- && (hook.source === 'project' && hook.trustStatus === 'trusted'
206
- || hook.isManaged && hook.trustStatus === 'managed')
207
- ));
208
- if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
209
- return Object.freeze(hooks);
210
- }
211
-
212
- function parseCodexThreadResolution(readValue: unknown, rolloutRecords: readonly unknown[]): ThreadResolution {
213
- const readRoot = object(readValue);
214
- const thread = object(readRoot?.thread);
215
- const source = object(thread?.source);
216
- const subAgent = object(source?.subAgent);
217
- const threadSpawn = object(subAgent?.thread_spawn);
218
- const sessionMeta = rolloutRecords
219
- .map(object)
220
- .find((record) => record?.type === 'session_meta');
221
- const sessionPayload = object(sessionMeta?.payload);
222
- const turnContext = rolloutRecords
223
- .map(object)
224
- .filter((record) => record?.type === 'turn_context')
225
- .at(-1);
226
- const turnPayload = object(turnContext?.payload);
227
- const childThreadId = nonEmpty(thread?.id);
228
- const parentThreadId = nonEmpty(thread?.parentThreadId);
229
- const sourceParentThreadId = nonEmpty(threadSpawn?.parent_thread_id);
230
- const rolloutChildThreadId = nonEmpty(sessionPayload?.id);
231
- const rolloutParentThreadId = nonEmpty(sessionPayload?.parent_thread_id);
232
- const agentRole = nonEmpty(sessionPayload?.agent_role);
233
- const model = nonEmpty(turnPayload?.model);
234
- const reasoningEffort = nonEmpty(turnPayload?.effort);
235
- const forkedFromId = thread?.forkedFromId;
236
- if (
237
- !childThreadId
238
- || rolloutChildThreadId !== childThreadId
239
- || !parentThreadId
240
- || !sourceParentThreadId
241
- || rolloutParentThreadId !== parentThreadId
242
- || rolloutParentThreadId !== sourceParentThreadId
243
- || !/^agent-infra-lifecycle-(executor|reviewer)$/.test(agentRole ?? '')
244
- || !model
245
- || !reasoningEffort
246
- || (forkedFromId !== null && typeof forkedFromId !== 'string')
247
- ) {
248
- throw new Error('Codex App Server thread resolution is invalid');
249
- }
250
- return Object.freeze({
251
- thread: Object.freeze({
252
- type: 'app-thread',
253
- childThreadId,
254
- parentThreadId,
255
- forkedFromId,
256
- sourceParentThreadId,
257
- nativeAgent: agentRole!
258
- }),
259
- settings: Object.freeze({
260
- type: 'app-settings',
261
- childThreadId,
262
- model,
263
- reasoningEffort
264
- })
265
- });
266
- }
267
-
268
- function resolvePreflightTaskDir(
269
- repoRoot: string,
270
- taskId?: string
271
- ): string | undefined {
272
- try {
273
- const context = taskId
274
- ? resolveTaskRef(taskId, { repoRoot })
275
- : resolveTaskContext(undefined, { repoRoot });
276
- return context.ok ? context.taskDir : undefined;
277
- } catch {
278
- return undefined;
279
- }
280
- }
281
-
282
- function readCodexRolloutRecords(readValue: unknown, childThreadId: string): readonly unknown[] {
283
- const thread = object(object(readValue)?.thread);
284
- const rolloutPath = nonEmpty(thread?.path);
285
- const rolloutName = rolloutPath ? path.basename(rolloutPath) : '';
286
- if (!rolloutPath || (rolloutName !== `${childThreadId}.jsonl` && !rolloutName.endsWith(`-${childThreadId}.jsonl`))) {
287
- throw new Error('Codex App Server rollout path is invalid');
288
- }
289
- const stat = fs.statSync(rolloutPath);
290
- if (!stat.isFile()) throw new Error('Codex App Server rollout path is not a regular file');
291
- const maxBytes = 1024 * 1024;
292
- const bytes = Math.min(stat.size, maxBytes);
293
- const descriptor = fs.openSync(rolloutPath, 'r');
294
- try {
295
- const buffer = Buffer.alloc(bytes);
296
- fs.readSync(descriptor, buffer, 0, bytes, 0);
297
- const text = buffer.toString('utf8');
298
- const completeText = stat.size <= maxBytes ? text : text.slice(0, text.lastIndexOf('\n'));
299
- return Object.freeze(completeText.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)));
300
- } finally {
301
- fs.closeSync(descriptor);
302
- }
303
- }
304
-
305
- async function resolveCodexRolloutRecords(
306
- readValue: unknown,
307
- childThreadId: string,
308
- attempts: number,
309
- retryMs: number
310
- ): Promise<readonly unknown[]> {
311
- let lastError: unknown;
312
- for (let attempt = 0; attempt < attempts; attempt += 1) {
313
- try {
314
- const records = readCodexRolloutRecords(readValue, childThreadId);
315
- parseCodexThreadResolution(readValue, records);
316
- return records;
317
- } catch (error) {
318
- lastError = error;
319
- if (attempt + 1 < attempts) await new Promise((resolve) => setTimeout(resolve, retryMs));
320
- }
321
- }
322
- throw lastError;
323
- }
324
-
325
- function parseCodexTurnCompleted(value: unknown): Extract<CodexLifecycleEvent, { type: 'app-terminal' }> {
326
- const root = object(value);
327
- const turn = object(root?.turn);
328
- const childThreadId = nonEmpty(root?.threadId);
329
- const turnId = nonEmpty(turn?.id);
330
- const status = turn?.status;
331
- if (!childThreadId || !turnId || !['completed', 'interrupted', 'failed'].includes(String(status))) {
332
- throw new Error('Codex App Server turn completion is invalid');
333
- }
334
- return Object.freeze({
335
- type: 'app-terminal',
336
- childThreadId,
337
- turnId,
338
- status: status as 'completed' | 'interrupted' | 'failed'
339
- });
340
- }
341
-
342
- function parseCodexModelReroute(value: unknown): Extract<CodexLifecycleEvent, { type: 'app-reroute' }> {
343
- const root = object(value);
344
- const childThreadId = nonEmpty(root?.threadId);
345
- const turnId = nonEmpty(root?.turnId);
346
- const fromModel = nonEmpty(root?.fromModel);
347
- const toModel = nonEmpty(root?.toModel);
348
- const reasonValue = root?.reason;
349
- const reason = nonEmpty(reasonValue) ?? (object(reasonValue) ? JSON.stringify(reasonValue) : null);
350
- if (!childThreadId || !turnId || !fromModel || !toModel || !reason) {
351
- throw new Error('Codex App Server model reroute is invalid');
352
- }
353
- return Object.freeze({ type: 'app-reroute', childThreadId, turnId, fromModel, toModel, reason });
354
- }
355
-
356
- class CodexAppServerTransport {
357
- readonly #options: Required<Pick<AppServerTransportOptions, 'command' | 'args' | 'timeoutMs'>> & AppServerTransportOptions;
358
- #child: ChildProcessWithoutNullStreams | null = null;
359
- #buffer = '';
360
- #stderr = '';
361
- #nextId = 1;
362
- #pending = new Map<number, Readonly<{
363
- resolve: (value: unknown) => void;
364
- reject: (error: Error) => void;
365
- timer: NodeJS.Timeout;
366
- }>>();
367
- #notifications: Readonly<{ method: string; params: unknown }>[] = [];
368
-
369
- constructor(options: AppServerTransportOptions = {}) {
370
- this.#options = {
371
- ...options,
372
- command: options.command ?? 'codex',
373
- args: options.args ?? ['app-server', '--stdio'],
374
- timeoutMs: options.timeoutMs ?? 5_000
375
- };
376
- }
377
-
378
- get diagnostics(): readonly string[] {
379
- return Object.freeze(this.#stderr.split(/\r?\n/).filter(Boolean));
380
- }
381
-
382
- get notifications(): readonly Readonly<{ method: string; params: unknown }>[] {
383
- return Object.freeze([...this.#notifications]);
384
- }
385
-
386
- start(): void {
387
- if (this.#child) throw new Error('Codex App Server transport is already started');
388
- const child = spawn(this.#options.command, [...this.#options.args], {
389
- cwd: this.#options.cwd,
390
- env: this.#options.env,
391
- stdio: ['pipe', 'pipe', 'pipe']
392
- });
393
- this.#child = child;
394
- child.stdout.setEncoding('utf8');
395
- child.stderr.setEncoding('utf8');
396
- child.stdout.on('data', (chunk: string) => this.#onData(chunk));
397
- child.stderr.on('data', (chunk: string) => {
398
- this.#stderr = `${this.#stderr}${chunk}`.slice(-32 * 1024);
399
- });
400
- child.on('error', (error) => this.#rejectAll(error));
401
- child.on('exit', (code, signal) => {
402
- if (this.#pending.size) {
403
- this.#rejectAll(new Error(`Codex App Server exited before responding (code=${code}, signal=${signal})`));
404
- }
405
- });
406
- }
407
-
408
- #onData(chunk: string): void {
409
- this.#buffer += chunk;
410
- for (;;) {
411
- const newline = this.#buffer.indexOf('\n');
412
- if (newline < 0) return;
413
- const line = this.#buffer.slice(0, newline).trim();
414
- this.#buffer = this.#buffer.slice(newline + 1);
415
- if (!line) continue;
416
- let message: JsonObject;
417
- try {
418
- const parsed = object(JSON.parse(line));
419
- if (!parsed) throw new Error('message is not an object');
420
- message = parsed;
421
- } catch (error) {
422
- this.#rejectAll(new Error(`Codex App Server returned invalid JSON: ${String(error)}`));
423
- continue;
424
- }
425
- if (typeof message.id === 'number') {
426
- const pending = this.#pending.get(message.id);
427
- if (!pending) continue;
428
- clearTimeout(pending.timer);
429
- this.#pending.delete(message.id);
430
- if (message.error) pending.reject(new Error(`Codex App Server request failed: ${JSON.stringify(message.error)}`));
431
- else pending.resolve(message.result);
432
- } else if (typeof message.method === 'string') {
433
- this.#notifications.push(Object.freeze({ method: message.method, params: message.params }));
434
- }
435
- }
436
- }
437
-
438
- #rejectAll(error: Error): void {
439
- for (const [id, pending] of this.#pending) {
440
- clearTimeout(pending.timer);
441
- this.#pending.delete(id);
442
- pending.reject(error);
443
- }
444
- }
445
-
446
- request(method: string, params: JsonObject): Promise<unknown> {
447
- if (!this.#child || !this.#child.stdin.writable) {
448
- return Promise.reject(new Error('Codex App Server transport is not running'));
449
- }
450
- const id = this.#nextId++;
451
- return new Promise((resolve, reject) => {
452
- const timer = setTimeout(() => {
453
- this.#pending.delete(id);
454
- reject(new Error(`Codex App Server request '${method}' timed out`));
455
- }, this.#options.timeoutMs);
456
- this.#pending.set(id, { resolve, reject, timer });
457
- this.#child!.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
458
- });
459
- }
460
-
461
- notify(method: string, params: JsonObject = {}): void {
462
- if (!this.#child || !this.#child.stdin.writable) throw new Error('Codex App Server transport is not running');
463
- this.#child.stdin.write(`${JSON.stringify({ method, params })}\n`);
464
- }
465
-
466
- async initialize(): Promise<unknown> {
467
- const result = await this.request('initialize', {
468
- clientInfo: { name: 'agent-infra', title: 'agent-infra', version: '0.9.5' },
469
- capabilities: null
470
- });
471
- this.notify('initialized');
472
- return result;
473
- }
474
-
475
- close(): void {
476
- if (!this.#child) return;
477
- this.#child.stdin.end();
478
- this.#child.kill();
479
- this.#child = null;
480
- }
481
- }
482
-
483
- async function resolveCodexThread(
484
- childThreadId: string,
485
- options: AppServerTransportOptions = {}
486
- ): Promise<Readonly<{ resolution: ThreadResolution; reroutes: readonly Extract<CodexLifecycleEvent, { type: 'app-reroute' }>[]; diagnostics: readonly string[] }>> {
487
- if (!nonEmpty(childThreadId)) throw new Error('Codex child thread id is required');
488
- const transport = new CodexAppServerTransport(options);
489
- transport.start();
490
- try {
491
- await transport.initialize();
492
- const readResult = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: false }));
493
- const readThread = object(readResult?.thread);
494
- if (nonEmpty(readThread?.id) !== childThreadId) throw new Error('Codex App Server returned the wrong child thread');
495
- let rolloutRecords: readonly unknown[];
496
- try {
497
- rolloutRecords = await resolveCodexRolloutRecords(
498
- readResult,
499
- childThreadId,
500
- options.rolloutReadAttempts ?? DEFAULT_ROLLOUT_READ_ATTEMPTS,
501
- options.rolloutRetryMs ?? DEFAULT_ROLLOUT_RETRY_MS
502
- );
503
- } catch {
504
- throw new Error('Codex App Server rollout metadata is unavailable');
505
- }
506
- let resolution = parseCodexThreadResolution(readResult, rolloutRecords);
507
- const threadReadAttempts = options.threadReadAttempts ?? DEFAULT_THREAD_READ_ATTEMPTS;
508
- for (let attempt = 1; resolution.thread.forkedFromId !== null && attempt < threadReadAttempts; attempt += 1) {
509
- await new Promise((resolve) => setTimeout(resolve, options.threadRetryMs ?? DEFAULT_THREAD_RETRY_MS));
510
- const refreshed = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: false }));
511
- if (nonEmpty(object(refreshed?.thread)?.id) !== childThreadId) {
512
- throw new Error('Codex App Server returned the wrong child thread');
513
- }
514
- resolution = parseCodexThreadResolution(refreshed, rolloutRecords);
515
- }
516
- if (resolution.thread.forkedFromId !== null) {
517
- throw new Error('Codex App Server fresh thread metadata is unavailable');
518
- }
519
- const reroutes = transport.notifications
520
- .filter((entry) => entry.method === 'model/rerouted')
521
- .map((entry) => parseCodexModelReroute(entry.params))
522
- .filter((event) => event.childThreadId === childThreadId);
523
- return Object.freeze({ resolution, reroutes: Object.freeze(reroutes), diagnostics: transport.diagnostics });
524
- } finally {
525
- try {
526
- await transport.request('thread/unsubscribe', { threadId: childThreadId });
527
- } catch {
528
- // Closing the short-lived transport still removes the subscription.
529
- }
530
- transport.close();
531
- }
532
- }
533
-
534
- async function resolveCodexTerminal(
535
- childThreadId: string,
536
- expectedTurnIdOrOptions: string | AppServerTransportOptions = {},
537
- terminalOptions: AppServerTransportOptions = {}
538
- ): Promise<Extract<CodexLifecycleEvent, { type: 'app-terminal' }>> {
539
- const expectedTurnId = typeof expectedTurnIdOrOptions === 'string' ? expectedTurnIdOrOptions : null;
540
- const options = typeof expectedTurnIdOrOptions === 'string' ? terminalOptions : expectedTurnIdOrOptions;
541
- const transport = new CodexAppServerTransport(options);
542
- transport.start();
543
- try {
544
- await transport.initialize();
545
- const attempts = expectedTurnId ? options.terminalReadAttempts ?? DEFAULT_TERMINAL_READ_ATTEMPTS : 1;
546
- let abnormal: Extract<CodexLifecycleEvent, { type: 'app-terminal' }> | null = null;
547
- for (let attempt = 0; attempt < attempts; attempt += 1) {
548
- const result = object(await transport.request('thread/read', { threadId: childThreadId, includeTurns: true }));
549
- const thread = object(result?.thread);
550
- if (nonEmpty(thread?.id) !== childThreadId) throw new Error('Codex App Server returned the wrong child thread');
551
- const turns = Array.isArray(thread?.turns) ? thread.turns : [];
552
- const turn = expectedTurnId
553
- ? turns.find((candidate) => object(candidate)?.id === expectedTurnId)
554
- : turns.at(-1);
555
- const turnValue = object(turn);
556
- if (turnValue && turnValue.status !== 'inProgress') {
557
- const terminal = parseCodexTurnCompleted({ threadId: childThreadId, turn });
558
- if (terminal.status === 'completed') return terminal;
559
- abnormal = terminal;
560
- }
561
- if (attempt + 1 < attempts) {
562
- await new Promise((resolve) => setTimeout(resolve, options.terminalRetryMs ?? DEFAULT_TERMINAL_RETRY_MS));
563
- }
564
- }
565
- if (abnormal) return abnormal;
566
- throw new Error('CODEX_TURN_NOT_TERMINAL');
567
- } finally {
568
- transport.close();
569
- }
570
- }
571
-
572
- async function preflightCodexLifecycleEvidence(
573
- repoRoot: string = process.cwd(),
574
- runtimeIdentity?: CodexRuntimeIdentity,
575
- taskId?: string
576
- ) {
577
- const { cliVersion, hooksRaw } = validateStaticCodexPreflight(repoRoot);
578
- const hookDefinitionHash = crypto.createHash('sha256').update(hooksRaw).digest('hex');
579
- const taskDir = resolvePreflightTaskDir(repoRoot, taskId);
580
- const runtimeLiveness = Boolean(taskDir && runtimeIdentity
581
- && hasActiveCodexLifecycleEvidence(taskDir, { hookDefinitionHash, identity: runtimeIdentity }));
582
- const host = await discoverLifecycleHooks(repoRoot);
583
- return Object.freeze({
584
- cliVersion,
585
- hookDefinitionHash,
586
- staticReady: true,
587
- discoveredHooks: host.discoveredHooks,
588
- hookProvenance: host.hookProvenance,
589
- runtimeLiveness,
590
- diagnostics: host.diagnostics
591
- });
592
- }
593
-
594
- function validateStaticCodexPreflight(repoRoot: string): Readonly<{ cliVersion: string; hooksRaw: string }> {
595
- const versionRun = spawnSync('codex', ['--version'], { encoding: 'utf8' });
596
- const match = /codex-cli\s+(\d+\.\d+\.\d+)/.exec(versionRun.stdout ?? '');
597
- if (versionRun.status !== 0 || !match?.[1] || !semver.gte(match[1], '0.147.0')) {
598
- throw new Error('CODEX_PREFLIGHT_VERSION_UNSUPPORTED');
599
- }
600
- const featuresRun = spawnSync('codex', ['features', 'list'], { encoding: 'utf8' });
601
- if (
602
- featuresRun.status !== 0
603
- || !/^hooks\s+\S+\s+true$/m.test(featuresRun.stdout)
604
- || !/^multi_agent\s+\S+\s+true$/m.test(featuresRun.stdout)
605
- ) throw new Error('CODEX_PREFLIGHT_FEATURE_DISABLED');
606
-
607
- const hooksPath = path.join(repoRoot, '.codex', 'hooks.json');
608
- const hooksRaw = fs.readFileSync(hooksPath, 'utf8');
609
- validateCodexLifecycleHookConfig(JSON.parse(hooksRaw));
610
-
611
- const schemaDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-codex-schema-'));
612
- try {
613
- const schemaRun = spawnSync('codex', ['app-server', 'generate-json-schema', '--out', schemaDir], { encoding: 'utf8' });
614
- if (schemaRun.status !== 0) throw new Error('CODEX_PREFLIGHT_SCHEMA_UNAVAILABLE');
615
- const schema = fs.readFileSync(path.join(schemaDir, 'codex_app_server_protocol.v2.schemas.json'), 'utf8');
616
- for (const field of ['parentThreadId', 'forkedFromId', 'ThreadSettings', 'turn/completed', 'model/rerouted']) {
617
- if (!schema.includes(field)) throw new Error(`CODEX_PREFLIGHT_SCHEMA_MISSING:${field}`);
618
- }
619
- } finally {
620
- fs.rmSync(schemaDir, { recursive: true, force: true });
621
- }
622
- return { cliVersion: match[1]!, hooksRaw };
623
- }
624
-
625
- async function discoverLifecycleHooks(repoRoot: string): Promise<Readonly<{
626
- discoveredHooks: readonly DiscoveredHook[];
627
- hookProvenance: Readonly<{
628
- hookSource: 'managed' | 'project';
629
- hookSourcePathDigest: string;
630
- hookSourceHash: string;
631
- }>;
632
- diagnostics: readonly unknown[];
633
- }>> {
634
- const transport = new CodexAppServerTransport();
635
- transport.start();
636
- let discoveredHooks: readonly DiscoveredHook[];
637
- try {
638
- await transport.initialize();
639
- discoveredHooks = parseCodexHooksList(
640
- await transport.request('hooks/list', { cwds: [repoRoot] }),
641
- repoRoot
642
- );
643
- } finally { transport.close(); }
644
- const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) =>
645
- hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
646
- && hook.matcher === matcher
647
- && hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`
648
- ));
649
- if (lifecycleHooks.some((matches) => matches.length !== 1)) {
650
- throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hook source is not unique');
651
- }
652
- const matchedHooks = lifecycleHooks.flat();
653
- const sources = new Set(matchedHooks.map((hook) => hook.isManaged ? 'managed' : hook.source));
654
- const sourcePathDigests = new Set(matchedHooks.map((hook) => hook.sourcePathDigest));
655
- if (sources.size !== 1 || sourcePathDigests.size !== 1) {
656
- throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
657
- }
658
- const source = [...sources][0];
659
- const hookSource = source === 'managed' ? 'managed' : 'project';
660
- return {
661
- discoveredHooks,
662
- hookProvenance: Object.freeze({
663
- hookSource,
664
- hookSourcePathDigest: [...sourcePathDigests][0]!,
665
- hookSourceHash: crypto.createHash('sha256').update(JSON.stringify(matchedHooks.map((hook) => ({
666
- eventName: hook.eventName,
667
- matcher: hook.matcher,
668
- command: hook.command,
669
- currentHash: hook.currentHash
670
- })))).digest('hex')
671
- }),
672
- diagnostics: transport.diagnostics
673
- };
674
- }
675
-
676
- export {
677
- CodexAppServerTransport,
678
- parseCodexModelReroute,
679
- parseCodexHooksList,
680
- parseCodexThreadResolution,
681
- parseCodexTurnCompleted,
682
- resolveCodexSpawnedChild,
683
- preflightCodexLifecycleEvidence,
684
- resolveCodexTerminal,
685
- resolveCodexThread,
686
- validateCodexLifecycleHookConfig
687
- };
688
- export type { AppServerTransportOptions, CodexRuntimeIdentity, ThreadResolution };