@fitlab-ai/agent-infra 0.11.11 → 0.11.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
|
@@ -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
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
26
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
113
|
-
|
|
114
|
-
|
|
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,167 @@ 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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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:
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 =
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
.
|
|
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
|
|
214
|
+
if (matches.length !== 1)
|
|
206
215
|
throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
207
|
-
|
|
208
|
-
throw new Error(`Codex lifecycle child '${childThreadId}' is ambiguous`);
|
|
209
|
-
return readRecord(matches[0]);
|
|
216
|
+
return matches[0];
|
|
210
217
|
}
|
|
211
|
-
function
|
|
218
|
+
function consumeWithLockMode(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding, taskLockHeld) {
|
|
212
219
|
if (!consumer.trim())
|
|
213
220
|
throw new Error('Codex lifecycle consumer is required');
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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(taskLockHeld
|
|
228
|
+
? 'Codex lifecycle task receipt binding is missing'
|
|
229
|
+
: 'Codex lifecycle spawn evidence is missing');
|
|
230
|
+
}
|
|
231
|
+
const key = recordKey(spawn.sessionId, spawn.turnId, spawn.toolUseId);
|
|
232
|
+
return update(binding, key, (latest) => {
|
|
233
|
+
if (!latest || expectedTaskBinding && JSON.stringify(latest.taskBinding) !== JSON.stringify(expectedTaskBinding)) {
|
|
234
|
+
throw new Error('Codex lifecycle task binding does not match the expected receipt');
|
|
228
235
|
}
|
|
229
|
-
if (
|
|
236
|
+
if (latest.consumer && latest.consumer !== consumer) {
|
|
237
|
+
throw new Error(`Codex lifecycle evidence was already consumed by '${latest.consumer}'`);
|
|
238
|
+
}
|
|
239
|
+
if (latest.state.status !== 'stop-ready')
|
|
230
240
|
throw new Error('Codex lifecycle evidence is not stop-ready');
|
|
231
|
-
if (expectedHookDefinitionHash
|
|
232
|
-
&& current.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash)
|
|
241
|
+
if (expectedHookDefinitionHash && latest.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash) {
|
|
233
242
|
throw new Error('Codex lifecycle hook definition hash is stale');
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
updatedAt: now()
|
|
240
|
-
});
|
|
241
|
-
writeRecord(file, next, current.revision);
|
|
242
|
-
return next;
|
|
243
|
-
});
|
|
243
|
+
}
|
|
244
|
+
if (latest.consumer)
|
|
245
|
+
return latest;
|
|
246
|
+
return Object.freeze({ ...latest, revision: latest.revision + 1, consumer, consumedAt: now(), updatedAt: now() });
|
|
247
|
+
}, taskLockHeld);
|
|
244
248
|
}
|
|
245
|
-
function consume(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
function consume(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding) {
|
|
250
|
+
return consumeWithLockMode(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding, false);
|
|
251
|
+
}
|
|
252
|
+
function consumeWithinTaskLock(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding) {
|
|
253
|
+
return consumeWithLockMode(childThreadId, consumer, expectedHookDefinitionHash, expectedTaskBinding, true);
|
|
249
254
|
}
|
|
250
255
|
function expireBefore(cutoff) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
256
|
+
let changed = 0;
|
|
257
|
+
for (const record of allRecords(taskDir)) {
|
|
258
|
+
if (record.updatedAt >= cutoff || !record.taskBinding?.receiptId || !record.state.spawn)
|
|
259
|
+
continue;
|
|
260
|
+
const key = recordKey(record.state.spawn.sessionId, record.state.spawn.turnId, record.state.spawn.toolUseId);
|
|
261
|
+
update(record.taskBinding, key, (latest) => {
|
|
262
|
+
if (!latest || latest.updatedAt >= cutoff || latest.consumer
|
|
263
|
+
|| ['invalid', 'expired', 'stop-ready'].includes(latest.state.status))
|
|
264
|
+
return latest ?? record;
|
|
265
|
+
const state = expireCodexLifecycleState(latest.state);
|
|
266
|
+
if (state === latest.state)
|
|
267
|
+
return latest;
|
|
271
268
|
changed += 1;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
269
|
+
return Object.freeze({ ...latest, revision: latest.revision + 1, state, updatedAt: now() });
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return changed;
|
|
275
273
|
}
|
|
276
|
-
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
274
|
+
return Object.freeze({ taskId: options.taskId, root: path.join(taskDir, '.runtime'), apply, applyToSpawn, consume, consumeWithinTaskLock, expireBefore, findByParent, findByTaskBinding, read });
|
|
277
275
|
}
|
|
278
|
-
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
|
276
|
+
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence, readCodexLifecycleActivationEvidence };
|
|
279
277
|
//# sourceMappingURL=store.js.map
|