@fitlab-ai/agent-infra 0.11.10 → 0.11.12

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 (219) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +27 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +222 -210
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
  17. package/dist/lib/agent-clients/adapters/codex.js +44 -1
  18. package/dist/lib/agent-clients/adapters/index.js +2 -10
  19. package/dist/lib/agent-clients/tokens.js +3 -2
  20. package/dist/lib/internal/agent-client.js +132 -72
  21. package/dist/lib/internal/cli-route-inventory.js +2 -2
  22. package/dist/lib/internal/manual-validation.js +2 -2
  23. package/dist/lib/internal/platform-pr.js +4 -5
  24. package/dist/lib/internal/task-artifact.js +1 -1
  25. package/dist/lib/internal/task-create.js +15 -1
  26. package/dist/lib/internal/task-finalization.js +1 -3
  27. package/dist/lib/internal/task-operation-registry.js +5 -139
  28. package/dist/lib/internal/task-validate.js +1 -1
  29. package/dist/lib/platform/issues.js +18 -6
  30. package/dist/lib/platform/pr-summary.js +45 -8
  31. package/dist/lib/platform/pull-requests.js +79 -14
  32. package/dist/lib/platform/verification-sync.js +17 -14
  33. package/dist/lib/runtime/agent-runtime.js +6 -29
  34. package/dist/lib/sandbox/commands/control-status.js +1 -0
  35. package/dist/lib/sandbox/commands/create.js +19 -2
  36. package/dist/lib/sandbox/commands/enter.js +47 -33
  37. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  38. package/dist/lib/sandbox/control/client.js +68 -161
  39. package/dist/lib/sandbox/control/executor.js +13 -87
  40. package/dist/lib/sandbox/control/host-recovery.js +218 -0
  41. package/dist/lib/sandbox/control/protocol.js +15 -44
  42. package/dist/lib/sandbox/control/server.js +57 -51
  43. package/dist/lib/sandbox/environment.js +150 -0
  44. package/dist/lib/sandbox/mounts.js +2 -1
  45. package/dist/lib/sandbox/recovery.js +11 -1
  46. package/dist/lib/sandbox/removal.js +9 -5
  47. package/dist/lib/sandbox/workspace-identity.js +0 -14
  48. package/dist/lib/sandbox/workspace-view.js +41 -23
  49. package/dist/lib/task/artifact-command.js +7 -2
  50. package/dist/lib/task/artifact-lifecycle.js +2 -1
  51. package/dist/lib/task/capabilities.js +4 -1
  52. package/dist/lib/task/control-authority.js +54 -266
  53. package/dist/lib/task/control-recovery.js +1 -1
  54. package/dist/lib/task/create.js +2 -2
  55. package/dist/lib/task/delegation-receipts.js +74 -161
  56. package/dist/lib/task/events.js +89 -45
  57. package/dist/lib/task/finalization.js +58 -60
  58. package/dist/lib/task/frontmatter.js +24 -1
  59. package/dist/lib/task/ledger-intents.js +2 -1
  60. package/dist/lib/task/lifecycle.js +84 -97
  61. package/dist/lib/task/orchestration.js +182 -129
  62. package/dist/lib/task/platform-operation-journal.js +12 -0
  63. package/dist/lib/task/platform-operation-recovery.js +14 -7
  64. package/dist/lib/task/resolve-ref.js +8 -3
  65. package/dist/lib/task/runtime-paths.js +10 -0
  66. package/dist/lib/task/verification-engine.js +28 -15
  67. package/dist/lib/task/workspace-snapshot.js +1 -1
  68. package/dist/lifecycle-build-manifest.json +3 -3
  69. package/lib/agent-clients/adapter.ts +61 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  71. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  72. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  73. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  74. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  75. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  76. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  77. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  78. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  80. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  81. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  82. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  83. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
  84. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  85. package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
  86. package/lib/agent-clients/adapters/codex.ts +48 -1
  87. package/lib/agent-clients/adapters/index.ts +7 -20
  88. package/lib/agent-clients/tokens.ts +4 -3
  89. package/lib/internal/agent-client.ts +142 -73
  90. package/lib/internal/cli-route-inventory.ts +2 -2
  91. package/lib/internal/manual-validation.ts +2 -2
  92. package/lib/internal/platform-pr.ts +3 -4
  93. package/lib/internal/task-artifact.ts +1 -1
  94. package/lib/internal/task-create.ts +15 -2
  95. package/lib/internal/task-finalization.ts +1 -3
  96. package/lib/internal/task-operation-registry.ts +5 -151
  97. package/lib/internal/task-validate.ts +1 -1
  98. package/lib/platform/issues.ts +18 -6
  99. package/lib/platform/pr-summary.ts +43 -7
  100. package/lib/platform/pull-requests.ts +74 -13
  101. package/lib/platform/verification-sync.ts +18 -14
  102. package/lib/runtime/agent-runtime.ts +6 -41
  103. package/lib/sandbox/commands/control-status.ts +1 -0
  104. package/lib/sandbox/commands/create.ts +25 -2
  105. package/lib/sandbox/commands/enter.ts +42 -39
  106. package/lib/sandbox/control/agent-operation.ts +49 -0
  107. package/lib/sandbox/control/client.ts +86 -210
  108. package/lib/sandbox/control/executor.ts +17 -96
  109. package/lib/sandbox/control/host-recovery.ts +195 -0
  110. package/lib/sandbox/control/protocol.ts +24 -55
  111. package/lib/sandbox/control/server.ts +54 -52
  112. package/lib/sandbox/environment.ts +162 -0
  113. package/lib/sandbox/mounts.ts +2 -1
  114. package/lib/sandbox/recovery.ts +13 -1
  115. package/lib/sandbox/removal.ts +12 -5
  116. package/lib/sandbox/tool-types.ts +1 -0
  117. package/lib/sandbox/workspace-identity.ts +0 -26
  118. package/lib/sandbox/workspace-view.ts +42 -24
  119. package/lib/task/artifact-command.ts +9 -2
  120. package/lib/task/artifact-lifecycle.ts +2 -1
  121. package/lib/task/capabilities.ts +5 -2
  122. package/lib/task/control-authority.ts +65 -382
  123. package/lib/task/control-recovery.ts +2 -2
  124. package/lib/task/create.ts +3 -3
  125. package/lib/task/delegation-receipts.ts +87 -231
  126. package/lib/task/events.ts +101 -43
  127. package/lib/task/finalization.ts +57 -58
  128. package/lib/task/frontmatter.ts +25 -1
  129. package/lib/task/ledger-intents.ts +2 -1
  130. package/lib/task/lifecycle.ts +82 -87
  131. package/lib/task/orchestration.ts +237 -176
  132. package/lib/task/platform-operation-journal.ts +29 -2
  133. package/lib/task/platform-operation-recovery.ts +12 -6
  134. package/lib/task/resolve-ref.ts +8 -3
  135. package/lib/task/runtime-paths.ts +12 -0
  136. package/lib/task/verification-engine.ts +27 -16
  137. package/lib/task/workspace-snapshot.ts +1 -1
  138. package/package.json +1 -1
  139. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  140. package/templates/.agents/rules/README.en.md +2 -2
  141. package/templates/.agents/rules/README.zh-CN.md +2 -2
  142. package/templates/.agents/rules/issue-sync.en.md +1 -1
  143. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  144. package/templates/.agents/rules/next-step-output.en.md +6 -19
  145. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  146. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  147. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  148. package/templates/.agents/rules/review-handshake.en.md +3 -0
  149. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
  150. package/templates/.agents/rules/task-management.en.md +1 -1
  151. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  152. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  153. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  154. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  158. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  159. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  160. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
  161. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
  162. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
  163. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  166. package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
  167. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
  168. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
  169. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
  170. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
  171. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
  172. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -2
  173. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -2
  174. package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
  175. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
  176. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  177. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  178. package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
  179. package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
  180. package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
  181. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
  182. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  183. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  184. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  189. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  190. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  191. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  192. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  193. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  194. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  195. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  196. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
  197. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
  198. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  199. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  200. package/templates/.agents/skills/test/SKILL.en.md +4 -1
  201. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
  202. package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
  203. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
  204. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +4 -1
  205. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -1
  206. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
  207. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
  208. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  211. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  212. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  213. package/dist/lib/internal/codex-lifecycle.js +0 -385
  214. package/dist/lib/task/codex-orchestration.js +0 -206
  215. package/dist/lib/task/finalization-handoff.js +0 -93
  216. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  217. package/lib/internal/codex-lifecycle.ts +0 -390
  218. package/lib/task/codex-orchestration.ts +0 -276
  219. package/lib/task/finalization-handoff.ts +0 -105
@@ -1,120 +1,101 @@
1
1
  import crypto from 'node:crypto';
2
- import fs from 'node:fs';
3
2
  import path from 'node:path';
3
+ import { mutateDelegationAdapterEvidence, mutateDelegationAdapterEvidenceWithinTaskLock, readRun } from '../../../task/orchestration.js';
4
+ import { resolveTaskRef } from '../../../task/resolve-ref.js';
4
5
  import { createCodexLifecycleState, expireCodexLifecycleState, reduceCodexLifecycleEvent } from './evidence.js';
5
- import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
6
- const LOCK_RETRY_MS = 10;
7
- const LOCK_TIMEOUT_MS = 1_000;
8
- const LOCK_STALE_MS = 30_000;
9
- function digest(value) {
10
- return crypto.createHash('sha256').update(value).digest('hex');
6
+ function readCodexLifecycleActivationEvidence(receipt) {
7
+ const state = storedEvidence(receipt.adapterEvidence?.codex);
8
+ return state.activationEvidence && typeof state.activationEvidence === 'object'
9
+ ? state.activationEvidence
10
+ : null;
11
11
  }
12
- function readRecord(file) {
13
- const value = JSON.parse(fs.readFileSync(file, 'utf8'));
14
- if (value.schemaVersion !== 1
15
- || !Number.isSafeInteger(value.revision)
16
- || value.revision < 1
17
- || !value.state
18
- || value.state.schemaVersion !== 1
19
- || (value.spawnObservedAt != null && (typeof value.spawnObservedAt !== 'string'
20
- || !Number.isFinite(Date.parse(value.spawnObservedAt))))) {
21
- throw new Error(`Codex lifecycle record '${path.basename(file)}' is invalid`);
22
- }
23
- return Object.freeze({ ...value, spawnObservedAt: value.spawnObservedAt ?? null });
12
+ const NON_PERSISTENT_FAILURES = new Set([
13
+ 'CODEX_EVIDENCE_IDENTITY_MISMATCH',
14
+ 'CODEX_EVIDENCE_PARENT_MISMATCH',
15
+ 'CODEX_EVIDENCE_REPLAY_CONFLICT'
16
+ ]);
17
+ function recordKey(sessionId, turnId, toolUseId) {
18
+ return crypto.createHash('sha256').update(`${sessionId}\0${turnId}\0${toolUseId}`).digest('hex');
24
19
  }
25
- function recordFiles(root) {
26
- if (!fs.existsSync(root))
27
- return [];
28
- return fs.readdirSync(root)
29
- .filter((name) => /^[a-f0-9]{64}\.json$/.test(name))
30
- .map((name) => path.join(root, name));
20
+ function isRecord(value) {
21
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
31
22
  }
32
- function hasActiveCodexLifecycleEvidence(root, query) {
33
- return recordFiles(root).map(readRecord).some((record) => {
34
- const evidence = record.state.startEvidence;
35
- return record.consumer === null
36
- && ['start-ready', 'observed-terminal', 'stop-ready'].includes(record.state.status)
37
- && evidence?.nativeAgent === query.nativeAgent
38
- && evidence.hookDefinitionHash === query.hookDefinitionHash;
39
- });
23
+ function storedEvidence(value) {
24
+ if (!isRecord(value) || !isRecord(value.records))
25
+ return Object.freeze({ records: Object.freeze({}) });
26
+ return value;
40
27
  }
41
- function writeRecord(file, record, expectedRevision) {
42
- const temp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
43
- fs.writeFileSync(temp, `${JSON.stringify(record, null, 2)}\n`, {
44
- encoding: 'utf8',
45
- flag: 'wx',
46
- mode: 0o600
47
- });
48
- try {
49
- const actualRevision = fs.existsSync(file) ? readRecord(file).revision : 0;
50
- if (actualRevision !== expectedRevision) {
51
- throw new Error(`Codex lifecycle revision changed from ${expectedRevision} to ${actualRevision}`);
52
- }
53
- fs.renameSync(temp, file);
54
- fs.chmodSync(file, 0o600);
55
- }
56
- finally {
57
- if (fs.existsSync(temp))
58
- fs.unlinkSync(temp);
28
+ function receipts(taskDir) {
29
+ const run = readRun(taskDir);
30
+ return run ? Object.freeze([...run.receipts, ...(run.pendingDelegation ? [run.pendingDelegation] : [])]) : [];
31
+ }
32
+ function allRecords(taskDir) {
33
+ return Object.freeze(receipts(taskDir).flatMap((receipt) => {
34
+ const state = storedEvidence(receipt.adapterEvidence?.codex);
35
+ return Object.values(state.records);
36
+ }));
37
+ }
38
+ function lifecyclePath(taskDir) {
39
+ return path.join(taskDir, '.runtime', 'orchestration.json');
40
+ }
41
+ function assertCurrentCanApply(current, binding) {
42
+ if (current.revision > 0 && binding && JSON.stringify(current.taskBinding) !== JSON.stringify(binding)) {
43
+ throw new Error('Codex lifecycle task binding does not match the stored spawn');
59
44
  }
45
+ if (current.consumer)
46
+ throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
47
+ }
48
+ function hasActiveCodexLifecycleEvidence(taskDir, query) {
49
+ return allRecords(taskDir).some((record) => {
50
+ return record.consumer === null
51
+ && !['invalid', 'expired'].includes(record.state.status)
52
+ && record.state.spawn?.hookDefinitionHash === query.hookDefinitionHash
53
+ && (!query.identity || record.state.spawn?.sessionId === query.identity.sessionId
54
+ && record.state.spawn.turnId === query.identity.turnId
55
+ && record.state.spawn.toolUseId === query.identity.toolUseId);
56
+ });
60
57
  }
61
58
  function createCodexLifecycleStore(options) {
62
- const root = options.root ?? resolveAgentRuntimeStoreRoot({ store: 'lifecycle' });
59
+ if (!/^TASK-[0-9]{8}-[0-9]{6}$/u.test(options.taskId)) {
60
+ throw new Error('Codex lifecycle store task id is invalid');
61
+ }
62
+ const repoRoot = options.repoRoot ?? process.cwd();
63
+ const resolved = resolveTaskRef(options.taskId, { repoRoot });
64
+ if (!resolved.ok)
65
+ throw new Error(`${resolved.code}: ${resolved.message}`);
66
+ const taskDir = resolved.taskDir;
63
67
  const now = options.now ?? (() => new Date().toISOString());
64
- fs.mkdirSync(root, { recursive: true, mode: 0o700 });
65
- fs.chmodSync(root, 0o700);
66
- function withWriteLock(operation) {
67
- const lock = path.join(root, '.write.lock');
68
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
69
- let descriptor = null;
70
- while (descriptor === null) {
71
- try {
72
- descriptor = fs.openSync(lock, 'wx', 0o600);
73
- }
74
- catch (error) {
75
- if (error.code !== 'EEXIST')
76
- throw error;
77
- try {
78
- if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) {
79
- fs.unlinkSync(lock);
80
- continue;
81
- }
82
- }
83
- catch (lockError) {
84
- if (lockError.code === 'ENOENT')
85
- continue;
86
- throw lockError;
87
- }
88
- if (Date.now() >= deadline)
89
- throw new Error('Codex lifecycle store is busy');
90
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MS);
91
- }
92
- }
93
- try {
94
- return operation();
95
- }
96
- finally {
97
- fs.closeSync(descriptor);
98
- fs.unlinkSync(lock);
68
+ function currentBinding() {
69
+ const run = readRun(taskDir);
70
+ const receipt = run?.pendingDelegation;
71
+ if (!run || !receipt || receipt.client !== 'codex') {
72
+ throw new Error('Codex lifecycle evidence has no current task delegation');
99
73
  }
74
+ return Object.freeze({ taskId: receipt.taskId, runId: receipt.runId, receiptId: receipt.id });
100
75
  }
101
76
  function findByChild(childThreadId) {
102
- return recordFiles(root).filter((file) => {
103
- const child = readRecord(file).state.child;
104
- return child?.childThreadId === childThreadId;
105
- });
77
+ return allRecords(taskDir).filter((record) => record.state.child?.childThreadId === childThreadId);
106
78
  }
107
79
  function locate(event) {
108
80
  if (event.type === 'hook-spawn') {
109
- return path.join(root, `${digest(`${event.sessionId}\0${event.turnId}\0${event.toolUseId}`)}.json`);
81
+ const key = recordKey(event.sessionId, event.turnId, event.toolUseId);
82
+ const existing = allRecords(taskDir).find((record) => record.spawnObservedAt && record.state.spawn
83
+ && recordKey(record.state.spawn.sessionId, record.state.spawn.turnId, record.state.spawn.toolUseId) === key);
84
+ return existing ?? Object.freeze({
85
+ schemaVersion: 2,
86
+ revision: 0,
87
+ taskBinding: event.taskBinding ?? null,
88
+ state: createCodexLifecycleState(options.cliVersion),
89
+ consumer: null,
90
+ consumedAt: null,
91
+ spawnObservedAt: null,
92
+ updatedAt: now()
93
+ });
110
94
  }
111
95
  if (event.type === 'hook-child') {
112
- const matches = recordFiles(root).filter((file) => {
113
- const state = readRecord(file).state;
114
- return state.spawn?.sessionId === event.parentThreadId
115
- && state.spawn.nativeAgent === event.nativeAgent
116
- && (!state.child || state.child.childThreadId === event.childThreadId);
117
- });
96
+ const matches = allRecords(taskDir).filter((record) => record.state.spawn?.sessionId === event.parentThreadId
97
+ && record.state.spawn.nativeAgent === event.nativeAgent
98
+ && (!record.state.child || record.state.child.childThreadId === event.childThreadId));
118
99
  if (matches.length !== 1) {
119
100
  throw new Error(matches.length === 0
120
101
  ? 'Codex lifecycle parent session and agent correlation was not found'
@@ -130,150 +111,181 @@ function createCodexLifecycleStore(options) {
130
111
  }
131
112
  return matches[0];
132
113
  }
114
+ function update(binding, key, mutate, taskLockHeld = false) {
115
+ let result = null;
116
+ const updateState = taskLockHeld ? mutateDelegationAdapterEvidenceWithinTaskLock : mutateDelegationAdapterEvidence;
117
+ updateState(options.taskId, binding.receiptId, 'codex', (value) => {
118
+ const currentState = storedEvidence(value);
119
+ const current = currentState.records[key] ?? null;
120
+ result = mutate(current);
121
+ return Object.freeze({
122
+ ...currentState,
123
+ records: Object.freeze({ ...currentState.records, [key]: result })
124
+ });
125
+ }, { repoRoot, now });
126
+ if (!result)
127
+ throw new Error('Codex lifecycle record was not updated');
128
+ return result;
129
+ }
133
130
  function apply(event) {
134
- return withWriteLock(() => {
135
- const file = locate(event);
136
- const observedAt = now();
137
- const current = fs.existsSync(file)
138
- ? readRecord(file)
139
- : Object.freeze({
140
- schemaVersion: 1,
141
- revision: 0,
142
- state: createCodexLifecycleState(options.cliVersion),
143
- consumer: null,
144
- consumedAt: null,
145
- spawnObservedAt: null,
146
- updatedAt: observedAt
147
- });
148
- if (current.consumer)
149
- throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
150
- const nextState = reduceCodexLifecycleEvent(current.state, event);
151
- const next = Object.freeze({
152
- schemaVersion: 1,
153
- revision: current.revision + 1,
154
- state: nextState,
131
+ const existing = event.type === 'hook-spawn' ? null : locate(event);
132
+ const binding = event.type === 'hook-spawn' ? event.taskBinding ?? currentBinding() : existing?.taskBinding;
133
+ if (!binding)
134
+ throw new Error('Codex lifecycle event has no task receipt binding');
135
+ const key = event.type === 'hook-spawn'
136
+ ? recordKey(event.sessionId, event.turnId, event.toolUseId)
137
+ : existing?.state.spawn
138
+ ? recordKey(existing.state.spawn.sessionId, existing.state.spawn.turnId, existing.state.spawn.toolUseId)
139
+ : '';
140
+ const observedAt = now();
141
+ const next = update(binding, key, (current) => {
142
+ const base = current ?? Object.freeze({
143
+ schemaVersion: 2,
144
+ revision: 0,
145
+ taskBinding: binding,
146
+ state: createCodexLifecycleState(options.cliVersion),
155
147
  consumer: null,
156
148
  consumedAt: null,
157
- spawnObservedAt: event.type === 'hook-spawn' && current.revision === 0
158
- ? observedAt
159
- : current.spawnObservedAt,
149
+ spawnObservedAt: null,
150
+ updatedAt: observedAt
151
+ });
152
+ assertCurrentCanApply(base, binding);
153
+ const state = reduceCodexLifecycleEvent(base.state, event);
154
+ if (state === base.state)
155
+ return base;
156
+ if (state.status === 'invalid' && state.error && NON_PERSISTENT_FAILURES.has(state.error.code)) {
157
+ throw new Error(`${state.error.code}: ${state.error.message}`);
158
+ }
159
+ return Object.freeze({
160
+ ...base,
161
+ revision: base.revision + 1,
162
+ state,
163
+ spawnObservedAt: event.type === 'hook-spawn' && base.revision === 0 ? observedAt : base.spawnObservedAt,
160
164
  updatedAt: observedAt
161
165
  });
162
- writeRecord(file, next, current.revision);
163
- return Object.freeze({ path: file, revision: next.revision, state: next.state });
164
166
  });
167
+ return Object.freeze({ path: lifecyclePath(taskDir), revision: next.revision, state: next.state });
165
168
  }
166
- function applyToSpawn(identity, event) {
167
- return withWriteLock(() => {
168
- const file = path.join(root, `${digest(`${identity.sessionId}\0${identity.turnId}\0${identity.toolUseId}`)}.json`);
169
- if (!fs.existsSync(file))
170
- throw new Error('Codex lifecycle spawn identity was not found');
171
- const current = readRecord(file);
172
- if (current.consumer)
173
- throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
174
- if (current.state.spawn?.sessionId !== identity.sessionId
175
- || current.state.spawn.turnId !== identity.turnId
176
- || current.state.spawn.toolUseId !== identity.toolUseId)
169
+ function applyToSpawn(identity, event, expectedRevision) {
170
+ const key = recordKey(identity.sessionId, identity.turnId, identity.toolUseId);
171
+ const current = allRecords(taskDir).find((record) => record.state.spawn
172
+ && recordKey(record.state.spawn.sessionId, record.state.spawn.turnId, record.state.spawn.toolUseId) === key);
173
+ if (!current)
174
+ throw new Error('Codex lifecycle spawn identity was not found');
175
+ const next = update(identity.taskBinding, key, (latest) => {
176
+ if (!latest || JSON.stringify(latest.taskBinding) !== JSON.stringify(identity.taskBinding)) {
177
+ throw new Error('Codex lifecycle task binding does not match the stored spawn');
178
+ }
179
+ if (latest.revision !== expectedRevision) {
180
+ throw new Error(`Codex lifecycle revision changed from ${expectedRevision} to ${latest.revision}`);
181
+ }
182
+ if (latest.consumer)
183
+ throw new Error(`Codex lifecycle evidence was already consumed by '${latest.consumer}'`);
184
+ if (latest.state.spawn?.sessionId !== identity.sessionId
185
+ || latest.state.spawn.turnId !== identity.turnId
186
+ || latest.state.spawn.toolUseId !== identity.toolUseId
187
+ || latest.state.spawn.nativeAgent !== event.nativeAgent
188
+ || event.sessionId !== identity.sessionId
189
+ || event.parentThreadId !== identity.sessionId) {
177
190
  throw new Error('Codex lifecycle spawn identity does not match the stored event');
178
- const nextState = reduceCodexLifecycleEvent(current.state, event);
179
- const next = Object.freeze({
180
- ...current,
181
- revision: current.revision + 1,
182
- state: nextState,
183
- updatedAt: now()
184
- });
185
- writeRecord(file, next, current.revision);
186
- return Object.freeze({ path: file, revision: next.revision, state: next.state });
191
+ }
192
+ const state = reduceCodexLifecycleEvent(latest.state, event);
193
+ if (state.status === 'invalid' && state.error && NON_PERSISTENT_FAILURES.has(state.error.code)) {
194
+ throw new Error(`${state.error.code}: ${state.error.message}`);
195
+ }
196
+ return state === latest.state ? latest : Object.freeze({ ...latest, revision: latest.revision + 1, state, updatedAt: now() });
187
197
  });
198
+ return Object.freeze({ path: lifecyclePath(taskDir), revision: next.revision, state: next.state });
188
199
  }
189
200
  function findByParent(parentThreadId) {
190
- const matches = recordFiles(root)
191
- .map(readRecord)
192
- .filter((record) => record.state.startEvidence?.parentThreadId === parentThreadId
201
+ const matches = allRecords(taskDir).filter((record) => record.state.startEvidence?.parentThreadId === parentThreadId
193
202
  && ['start-ready', 'observed-terminal', 'stop-ready'].includes(record.state.status));
194
203
  const unconsumed = matches.filter((record) => !record.consumer);
195
204
  if (unconsumed.length > 1)
196
205
  throw new Error(`Codex lifecycle parent '${parentThreadId}' has ambiguous active children`);
197
- if (unconsumed.length === 1)
198
- return Object.freeze(unconsumed);
199
- return Object.freeze(matches
200
- .filter((record) => record.consumer && record.state.status === 'stop-ready')
201
- .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
206
+ return Object.freeze(unconsumed.length === 1 ? unconsumed : matches.filter((record) => record.consumer));
207
+ }
208
+ function findByTaskBinding(binding) {
209
+ return Object.freeze(allRecords(taskDir).filter((record) => record.taskBinding?.runId === binding.runId
210
+ && record.taskBinding.receiptId === binding.receiptId));
202
211
  }
203
212
  function read(childThreadId) {
204
213
  const matches = findByChild(childThreadId);
205
- if (matches.length === 0)
214
+ if (matches.length !== 1)
206
215
  throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
207
- if (matches.length > 1)
208
- throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
209
- return readRecord(matches[0]);
216
+ return matches[0];
210
217
  }
211
- function consumeInternal(childThreadId, consumer, expectedHookDefinitionHash) {
218
+ function consume(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding) {
212
219
  if (!consumer.trim())
213
220
  throw new Error('Codex lifecycle consumer is required');
214
- return withWriteLock(() => {
215
- const matches = findByChild(childThreadId);
216
- if (matches.length !== 1)
217
- throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
218
- const file = matches[0];
219
- const current = readRecord(file);
220
- if (current.consumer) {
221
- if (current.consumer !== consumer) {
222
- throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
223
- }
224
- if (expectedHookDefinitionHash
225
- && current.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash)
226
- throw new Error('Codex lifecycle hook definition hash is stale');
227
- return current;
221
+ const current = read(childThreadId);
222
+ const binding = expectedTaskBinding ?? current.taskBinding;
223
+ if (!binding)
224
+ throw new Error('Codex lifecycle task receipt binding is missing');
225
+ const spawn = current.state.spawn;
226
+ if (!spawn)
227
+ throw new Error('Codex lifecycle spawn evidence is missing');
228
+ const key = recordKey(spawn.sessionId, spawn.turnId, spawn.toolUseId);
229
+ return update(binding, key, (latest) => {
230
+ if (!latest || expectedTaskBinding && JSON.stringify(latest.taskBinding) !== JSON.stringify(expectedTaskBinding)) {
231
+ throw new Error('Codex lifecycle task binding does not match the expected receipt');
232
+ }
233
+ if (latest.consumer && latest.consumer !== consumer) {
234
+ throw new Error(`Codex lifecycle evidence was already consumed by '${latest.consumer}'`);
228
235
  }
229
- if (current.state.status !== 'stop-ready')
236
+ if (latest.state.status !== 'stop-ready')
230
237
  throw new Error('Codex lifecycle evidence is not stop-ready');
231
- if (expectedHookDefinitionHash
232
- && current.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash)
238
+ if (expectedHookDefinitionHash && latest.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash) {
233
239
  throw new Error('Codex lifecycle hook definition hash is stale');
234
- const next = Object.freeze({
235
- ...current,
236
- revision: current.revision + 1,
237
- consumer,
238
- consumedAt: now(),
239
- updatedAt: now()
240
- });
241
- writeRecord(file, next, current.revision);
242
- return next;
240
+ }
241
+ if (latest.consumer)
242
+ return latest;
243
+ return Object.freeze({ ...latest, revision: latest.revision + 1, consumer, consumedAt: now(), updatedAt: now() });
243
244
  });
244
245
  }
245
- function consume(childThreadId, consumer, expectedHookDefinitionHash) {
246
+ function consumeWithinTaskLock(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding) {
246
247
  if (!consumer.trim())
247
248
  throw new Error('Codex lifecycle consumer is required');
248
- return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
249
+ const current = read(childThreadId);
250
+ const binding = expectedTaskBinding ?? current.taskBinding;
251
+ if (!binding || !current.state.spawn)
252
+ throw new Error('Codex lifecycle task receipt binding is missing');
253
+ const key = recordKey(current.state.spawn.sessionId, current.state.spawn.turnId, current.state.spawn.toolUseId);
254
+ return update(binding, key, (latest) => {
255
+ if (!latest || expectedTaskBinding && JSON.stringify(latest.taskBinding) !== JSON.stringify(expectedTaskBinding)) {
256
+ throw new Error('Codex lifecycle task binding does not match the expected receipt');
257
+ }
258
+ if (latest.consumer && latest.consumer !== consumer) {
259
+ throw new Error(`Codex lifecycle evidence was already consumed by '${latest.consumer}'`);
260
+ }
261
+ if (latest.state.status !== 'stop-ready')
262
+ throw new Error('Codex lifecycle evidence is not stop-ready');
263
+ if (expectedHookDefinitionHash && latest.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash) {
264
+ throw new Error('Codex lifecycle hook definition hash is stale');
265
+ }
266
+ return latest.consumer ? latest : Object.freeze({ ...latest, revision: latest.revision + 1, consumer, consumedAt: now(), updatedAt: now() });
267
+ }, true);
249
268
  }
250
269
  function expireBefore(cutoff) {
251
- return withWriteLock(() => {
252
- let changed = 0;
253
- for (const file of recordFiles(root)) {
254
- const current = readRecord(file);
255
- if (current.updatedAt >= cutoff)
256
- continue;
257
- if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
258
- fs.unlinkSync(file);
259
- changed += 1;
260
- continue;
261
- }
262
- const expiredState = expireCodexLifecycleState(current.state);
263
- if (expiredState === current.state)
264
- continue;
265
- writeRecord(file, Object.freeze({
266
- ...current,
267
- revision: current.revision + 1,
268
- state: expiredState,
269
- updatedAt: now()
270
- }), current.revision);
270
+ let changed = 0;
271
+ for (const record of allRecords(taskDir)) {
272
+ if (record.updatedAt >= cutoff || !record.taskBinding?.receiptId || !record.state.spawn)
273
+ continue;
274
+ const key = recordKey(record.state.spawn.sessionId, record.state.spawn.turnId, record.state.spawn.toolUseId);
275
+ update(record.taskBinding, key, (latest) => {
276
+ if (!latest || latest.updatedAt >= cutoff || latest.consumer
277
+ || ['invalid', 'expired', 'stop-ready'].includes(latest.state.status))
278
+ return latest ?? record;
279
+ const state = expireCodexLifecycleState(latest.state);
280
+ if (state === latest.state)
281
+ return latest;
271
282
  changed += 1;
272
- }
273
- return changed;
274
- });
283
+ return Object.freeze({ ...latest, revision: latest.revision + 1, state, updatedAt: now() });
284
+ });
285
+ }
286
+ return changed;
275
287
  }
276
- return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
288
+ return Object.freeze({ taskId: options.taskId, root: path.join(taskDir, '.runtime'), apply, applyToSpawn, consume, consumeWithinTaskLock, expireBefore, findByParent, findByTaskBinding, read });
277
289
  }
278
- export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
290
+ export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence, readCodexLifecycleActivationEvidence };
279
291
  //# sourceMappingURL=store.js.map