@fitlab-ai/agent-infra 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -14,10 +14,12 @@ function readRecord(file) {
|
|
|
14
14
|
|| !Number.isSafeInteger(value.revision)
|
|
15
15
|
|| value.revision < 1
|
|
16
16
|
|| !value.state
|
|
17
|
-
|| value.state.schemaVersion !== 1
|
|
17
|
+
|| value.state.schemaVersion !== 1
|
|
18
|
+
|| (value.spawnObservedAt != null && (typeof value.spawnObservedAt !== 'string'
|
|
19
|
+
|| !Number.isFinite(Date.parse(value.spawnObservedAt))))) {
|
|
18
20
|
throw new Error(`Codex lifecycle record '${path.basename(file)}' is invalid`);
|
|
19
21
|
}
|
|
20
|
-
return value;
|
|
22
|
+
return Object.freeze({ ...value, spawnObservedAt: value.spawnObservedAt ?? null });
|
|
21
23
|
}
|
|
22
24
|
function recordFiles(root) {
|
|
23
25
|
if (!fs.existsSync(root))
|
|
@@ -26,6 +28,15 @@ function recordFiles(root) {
|
|
|
26
28
|
.filter((name) => /^[a-f0-9]{64}\.json$/.test(name))
|
|
27
29
|
.map((name) => path.join(root, name));
|
|
28
30
|
}
|
|
31
|
+
function hasActiveCodexLifecycleEvidence(root, query) {
|
|
32
|
+
return recordFiles(root).map(readRecord).some((record) => {
|
|
33
|
+
const evidence = record.state.startEvidence;
|
|
34
|
+
return record.consumer === null
|
|
35
|
+
&& ['start-ready', 'observed-terminal', 'stop-ready'].includes(record.state.status)
|
|
36
|
+
&& evidence?.nativeAgent === query.nativeAgent
|
|
37
|
+
&& evidence.hookDefinitionHash === query.hookDefinitionHash;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
29
40
|
function writeRecord(file, record, expectedRevision) {
|
|
30
41
|
const temp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
31
42
|
fs.writeFileSync(temp, `${JSON.stringify(record, null, 2)}\n`, {
|
|
@@ -99,7 +110,7 @@ function createCodexLifecycleStore(options) {
|
|
|
99
110
|
if (event.type === 'hook-child') {
|
|
100
111
|
const matches = recordFiles(root).filter((file) => {
|
|
101
112
|
const state = readRecord(file).state;
|
|
102
|
-
return state.spawn?.sessionId === event.
|
|
113
|
+
return state.spawn?.sessionId === event.parentThreadId
|
|
103
114
|
&& state.spawn.nativeAgent === event.nativeAgent
|
|
104
115
|
&& (!state.child || state.child.childThreadId === event.childThreadId);
|
|
105
116
|
});
|
|
@@ -121,6 +132,7 @@ function createCodexLifecycleStore(options) {
|
|
|
121
132
|
function apply(event) {
|
|
122
133
|
return withWriteLock(() => {
|
|
123
134
|
const file = locate(event);
|
|
135
|
+
const observedAt = now();
|
|
124
136
|
const current = fs.existsSync(file)
|
|
125
137
|
? readRecord(file)
|
|
126
138
|
: Object.freeze({
|
|
@@ -129,7 +141,8 @@ function createCodexLifecycleStore(options) {
|
|
|
129
141
|
state: createCodexLifecycleState(options.cliVersion),
|
|
130
142
|
consumer: null,
|
|
131
143
|
consumedAt: null,
|
|
132
|
-
|
|
144
|
+
spawnObservedAt: null,
|
|
145
|
+
updatedAt: observedAt
|
|
133
146
|
});
|
|
134
147
|
if (current.consumer)
|
|
135
148
|
throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
|
|
@@ -140,12 +153,52 @@ function createCodexLifecycleStore(options) {
|
|
|
140
153
|
state: nextState,
|
|
141
154
|
consumer: null,
|
|
142
155
|
consumedAt: null,
|
|
156
|
+
spawnObservedAt: event.type === 'hook-spawn' && current.revision === 0
|
|
157
|
+
? observedAt
|
|
158
|
+
: current.spawnObservedAt,
|
|
159
|
+
updatedAt: observedAt
|
|
160
|
+
});
|
|
161
|
+
writeRecord(file, next, current.revision);
|
|
162
|
+
return Object.freeze({ path: file, revision: next.revision, state: next.state });
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function applyToSpawn(identity, event) {
|
|
166
|
+
return withWriteLock(() => {
|
|
167
|
+
const file = path.join(root, `${digest(`${identity.sessionId}\0${identity.turnId}\0${identity.toolUseId}`)}.json`);
|
|
168
|
+
if (!fs.existsSync(file))
|
|
169
|
+
throw new Error('Codex lifecycle spawn identity was not found');
|
|
170
|
+
const current = readRecord(file);
|
|
171
|
+
if (current.consumer)
|
|
172
|
+
throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
|
|
173
|
+
if (current.state.spawn?.sessionId !== identity.sessionId
|
|
174
|
+
|| current.state.spawn.turnId !== identity.turnId
|
|
175
|
+
|| current.state.spawn.toolUseId !== identity.toolUseId)
|
|
176
|
+
throw new Error('Codex lifecycle spawn identity does not match the stored event');
|
|
177
|
+
const nextState = reduceCodexLifecycleEvent(current.state, event);
|
|
178
|
+
const next = Object.freeze({
|
|
179
|
+
...current,
|
|
180
|
+
revision: current.revision + 1,
|
|
181
|
+
state: nextState,
|
|
143
182
|
updatedAt: now()
|
|
144
183
|
});
|
|
145
184
|
writeRecord(file, next, current.revision);
|
|
146
185
|
return Object.freeze({ path: file, revision: next.revision, state: next.state });
|
|
147
186
|
});
|
|
148
187
|
}
|
|
188
|
+
function findByParent(parentThreadId) {
|
|
189
|
+
const matches = recordFiles(root)
|
|
190
|
+
.map(readRecord)
|
|
191
|
+
.filter((record) => record.state.startEvidence?.parentThreadId === parentThreadId
|
|
192
|
+
&& ['start-ready', 'observed-terminal', 'stop-ready'].includes(record.state.status));
|
|
193
|
+
const unconsumed = matches.filter((record) => !record.consumer);
|
|
194
|
+
if (unconsumed.length > 1)
|
|
195
|
+
throw new Error(`Codex lifecycle parent '${parentThreadId}' has ambiguous active children`);
|
|
196
|
+
if (unconsumed.length === 1)
|
|
197
|
+
return Object.freeze(unconsumed);
|
|
198
|
+
return Object.freeze(matches
|
|
199
|
+
.filter((record) => record.consumer && record.state.status === 'stop-ready')
|
|
200
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)));
|
|
201
|
+
}
|
|
149
202
|
function read(childThreadId) {
|
|
150
203
|
const matches = findByChild(childThreadId);
|
|
151
204
|
if (matches.length !== 1)
|
|
@@ -161,8 +214,15 @@ function createCodexLifecycleStore(options) {
|
|
|
161
214
|
throw new Error(`Codex lifecycle child '${childThreadId}' was not found uniquely`);
|
|
162
215
|
const file = matches[0];
|
|
163
216
|
const current = readRecord(file);
|
|
164
|
-
if (current.consumer)
|
|
165
|
-
|
|
217
|
+
if (current.consumer) {
|
|
218
|
+
if (current.consumer !== consumer) {
|
|
219
|
+
throw new Error(`Codex lifecycle evidence was already consumed by '${current.consumer}'`);
|
|
220
|
+
}
|
|
221
|
+
if (expectedHookDefinitionHash
|
|
222
|
+
&& current.state.startEvidence?.hookDefinitionHash !== expectedHookDefinitionHash)
|
|
223
|
+
throw new Error('Codex lifecycle hook definition hash is stale');
|
|
224
|
+
return current;
|
|
225
|
+
}
|
|
166
226
|
if (current.state.status !== 'stop-ready')
|
|
167
227
|
throw new Error('Codex lifecycle evidence is not stop-ready');
|
|
168
228
|
if (expectedHookDefinitionHash
|
|
@@ -205,7 +265,7 @@ function createCodexLifecycleStore(options) {
|
|
|
205
265
|
return changed;
|
|
206
266
|
});
|
|
207
267
|
}
|
|
208
|
-
return Object.freeze({ root, apply, consume, expireBefore, read });
|
|
268
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
209
269
|
}
|
|
210
|
-
export { createCodexLifecycleStore };
|
|
270
|
+
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
|
211
271
|
//# sourceMappingURL=store.js.map
|
|
@@ -11,7 +11,7 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
11
11
|
commands: { level: 'integrated' },
|
|
12
12
|
hooks: { level: 'integrated' },
|
|
13
13
|
subagents: { level: 'experimental' },
|
|
14
|
-
orchestration: { level: '
|
|
14
|
+
orchestration: { level: 'experimental' },
|
|
15
15
|
sandbox: { level: 'integrated' },
|
|
16
16
|
verification: { level: 'compatible' }
|
|
17
17
|
},
|
package/dist/lib/defaults.json
CHANGED
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
".agents/skills/",
|
|
57
57
|
".agents/templates/",
|
|
58
58
|
".agents/workflows/",
|
|
59
|
-
".agents/workspace/README.md",
|
|
60
59
|
".git-hooks/check-large-files.cjs",
|
|
61
60
|
".git-hooks/check-version-format.sh",
|
|
62
61
|
".github/scripts/",
|
|
@@ -84,6 +83,15 @@
|
|
|
84
83
|
".git-hooks/pre-commit",
|
|
85
84
|
".gitignore"
|
|
86
85
|
],
|
|
87
|
-
"ejected": []
|
|
86
|
+
"ejected": [],
|
|
87
|
+
"retiredManaged": [
|
|
88
|
+
{
|
|
89
|
+
"path": ".agents/workspace/README.md",
|
|
90
|
+
"templateHashes": [
|
|
91
|
+
"sha256:967b27ca4008154c5abf99a1df728242f80ed3ace9bd98eb2828bc1c76af826c",
|
|
92
|
+
"sha256:15aa21942cf2a1396d27834f1d3d723b61a46f8d14600b706f39f3bebc21aea3"
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
88
96
|
}
|
|
89
97
|
}
|
package/dist/lib/git/workflow.js
CHANGED
|
@@ -12,6 +12,9 @@ function value(runner, cwd, args, trim = true) {
|
|
|
12
12
|
const result = run(runner, cwd, args);
|
|
13
13
|
return result.status === 0 ? (trim ? result.stdout.trim() : result.stdout.replace(/\n$/, '')) : null;
|
|
14
14
|
}
|
|
15
|
+
function nulPaths(output) {
|
|
16
|
+
return output.split('\0').filter(Boolean);
|
|
17
|
+
}
|
|
15
18
|
function cleanMessage(message) {
|
|
16
19
|
return message.replace(/(https?:\/\/)[^\s/@:]+(?::[^\s/@]*)?@/gi, '$1***@').trim();
|
|
17
20
|
}
|
|
@@ -64,9 +67,33 @@ function commitExplicitPaths(input, runner = defaultRunner) {
|
|
|
64
67
|
const unrelatedStaged = before.snapshot.staged.filter((candidate) => !selected.has(candidate));
|
|
65
68
|
if (unrelatedStaged.length > 0)
|
|
66
69
|
return { status: 'failed', changed: false, snapshot: before.snapshot, operations: [], error: { code: 'GIT_STAGED_SCOPE_MISMATCH', message: `Unrelated staged paths: ${unrelatedStaged.join(', ')}` } };
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const stageFailure = (message) => ({ status: 'failed', changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'failed', message }], error: { code: 'GIT_STAGE_FAILED', message } });
|
|
71
|
+
const indexed = run(runner, input.cwd, ['ls-files', '-z', '--', ...input.paths]);
|
|
72
|
+
if (indexed.status !== 0)
|
|
73
|
+
return stageFailure(indexed.stderr.trim());
|
|
74
|
+
const headed = run(runner, input.cwd, ['ls-tree', '-r', '--name-only', '-z', 'HEAD', '--', ...input.paths]);
|
|
75
|
+
if (headed.status !== 0)
|
|
76
|
+
return stageFailure(headed.stderr.trim());
|
|
77
|
+
const indexedPaths = new Set(nulPaths(indexed.stdout));
|
|
78
|
+
const headPaths = new Set(nulPaths(headed.stdout));
|
|
79
|
+
const trackedPaths = [];
|
|
80
|
+
const addPaths = [];
|
|
81
|
+
for (const candidate of input.paths) {
|
|
82
|
+
if (indexedPaths.has(candidate))
|
|
83
|
+
trackedPaths.push(candidate);
|
|
84
|
+
else if (!headPaths.has(candidate) || fs.existsSync(path.join(input.cwd, candidate)))
|
|
85
|
+
addPaths.push(candidate);
|
|
86
|
+
}
|
|
87
|
+
if (trackedPaths.length > 0) {
|
|
88
|
+
const update = run(runner, input.cwd, ['add', '-u', '--', ...trackedPaths]);
|
|
89
|
+
if (update.status !== 0)
|
|
90
|
+
return stageFailure(update.stderr.trim());
|
|
91
|
+
}
|
|
92
|
+
if (addPaths.length > 0) {
|
|
93
|
+
const add = run(runner, input.cwd, ['add', '--', ...addPaths]);
|
|
94
|
+
if (add.status !== 0)
|
|
95
|
+
return stageFailure(add.stderr.trim());
|
|
96
|
+
}
|
|
70
97
|
const staged = run(runner, input.cwd, ['diff', '--cached', '--quiet', '--']);
|
|
71
98
|
if (staged.status === 0)
|
|
72
99
|
return { status: 'no-op', changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'no-op' }], error: null };
|
|
@@ -119,7 +146,6 @@ function pushRebasedBranch(input, runner = defaultRunner) {
|
|
|
119
146
|
.map((name) => value(runner, input.cwd, ['rev-parse', '--git-path', name]))
|
|
120
147
|
.filter((candidate) => Boolean(candidate));
|
|
121
148
|
if (snapshot.worktree.length > 0 || snapshot.staged.length > 0 ||
|
|
122
|
-
run(runner, input.cwd, ['rev-parse', '-q', '--verify', 'REBASE_HEAD']).status === 0 ||
|
|
123
149
|
rebasePaths.some((candidate) => fs.existsSync(path.isAbsolute(candidate) ? candidate : path.join(input.cwd, candidate)))) {
|
|
124
150
|
return { status: 'failed', changed: false, snapshot, operations: [], error: { code: 'GIT_LOCAL_STATE_UNSAFE', message: 'Working tree must be clean with no rebase in progress' } };
|
|
125
151
|
}
|
|
@@ -4,7 +4,12 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread } from '../agent-clients/adapters/codex-lifecycle/app-server.js';
|
|
6
6
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
7
|
-
|
|
7
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
8
|
+
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
9
|
+
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
10
|
+
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
11
|
+
import { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, reconcileCodexOrchestrationDelegation, sealCodexOrchestrationDelegation, sealCodexParentDelegation } from '../task/codex-orchestration.js';
|
|
12
|
+
const USAGE = 'Usage: agent-infra-internal codex-lifecycle <capability-arm|hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
|
|
8
13
|
const MANAGED_AGENT = /^agent-infra-lifecycle-(executor|reviewer)$/;
|
|
9
14
|
function output(value) {
|
|
10
15
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
@@ -13,6 +18,15 @@ function failure(code, message) {
|
|
|
13
18
|
output({ status: 'failed', changed: false, evidence: null, diagnostics: [], error: { code, message } });
|
|
14
19
|
process.exitCode = 1;
|
|
15
20
|
}
|
|
21
|
+
function outputBridgeResult(result) {
|
|
22
|
+
if (result.error?.code === 'ORCHESTRATION_DELEGATION_MISSING') {
|
|
23
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
output(result);
|
|
27
|
+
if (result.status !== 'running')
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
16
30
|
function parse(args) {
|
|
17
31
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
18
32
|
process.stdout.write(USAGE);
|
|
@@ -24,7 +38,8 @@ function parse(args) {
|
|
|
24
38
|
return null;
|
|
25
39
|
}
|
|
26
40
|
const allowed = {
|
|
27
|
-
'
|
|
41
|
+
'capability-arm': ['--task-id'],
|
|
42
|
+
'hook-event': ['--event', '--bridge'],
|
|
28
43
|
'resolve-start': ['--child-id'],
|
|
29
44
|
'resolve-stop': ['--child-id'],
|
|
30
45
|
preflight: ['--format', '--session-id', '--turn-id', '--tool-use-id'],
|
|
@@ -61,6 +76,16 @@ function hookDefinitionHash() {
|
|
|
61
76
|
const file = path.join(process.cwd(), '.codex', 'hooks.json');
|
|
62
77
|
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
63
78
|
}
|
|
79
|
+
function controllerBinding(taskId) {
|
|
80
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
81
|
+
if (!contextPath)
|
|
82
|
+
return undefined;
|
|
83
|
+
const context = verifyCodexSandboxControllerContext(contextPath, taskId, { repoRoot: process.cwd() });
|
|
84
|
+
return {
|
|
85
|
+
instanceDigest: context.controllerInstanceDigest,
|
|
86
|
+
controlGeneration: context.controlGeneration
|
|
87
|
+
};
|
|
88
|
+
}
|
|
64
89
|
async function readStdin() {
|
|
65
90
|
let input = '';
|
|
66
91
|
process.stdin.setEncoding('utf8');
|
|
@@ -110,6 +135,12 @@ function recordFromPayload(phase, payload) {
|
|
|
110
135
|
return null;
|
|
111
136
|
throw new Error(`unknown Codex lifecycle hook phase '${phase}'`);
|
|
112
137
|
}
|
|
138
|
+
function payloadText(payload, key) {
|
|
139
|
+
return payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
140
|
+
&& typeof payload[key] === 'string'
|
|
141
|
+
? payload[key]
|
|
142
|
+
: '';
|
|
143
|
+
}
|
|
113
144
|
async function codexLifecycle(args = []) {
|
|
114
145
|
const parsed = parse(args);
|
|
115
146
|
if (!parsed || process.exitCode)
|
|
@@ -140,18 +171,153 @@ async function codexLifecycle(args = []) {
|
|
|
140
171
|
output({ status: 'ready', changed: false, evidence: result, diagnostics: result.diagnostics, error: null });
|
|
141
172
|
return;
|
|
142
173
|
}
|
|
174
|
+
if (parsed.operation === 'capability-arm') {
|
|
175
|
+
const taskId = parsed.values['--task-id'];
|
|
176
|
+
if (!taskId)
|
|
177
|
+
throw new Error('capability-arm requires --task-id');
|
|
178
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: process.cwd() });
|
|
179
|
+
if (!resolved.ok)
|
|
180
|
+
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
181
|
+
const armed = createCodexCapabilityStore().arm({
|
|
182
|
+
taskId: resolved.taskId,
|
|
183
|
+
buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
|
|
184
|
+
controller: controllerBinding(resolved.taskId)
|
|
185
|
+
});
|
|
186
|
+
output({
|
|
187
|
+
status: 'armed',
|
|
188
|
+
changed: true,
|
|
189
|
+
capabilityToken: armed.token,
|
|
190
|
+
marker: armed.marker,
|
|
191
|
+
expiresAt: armed.expiresAt,
|
|
192
|
+
buildIdentity: armed.buildIdentity,
|
|
193
|
+
error: null
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
143
197
|
const store = createCodexLifecycleStore({ cliVersion: cliVersion() });
|
|
144
198
|
if (parsed.operation === 'hook-event') {
|
|
145
199
|
const phase = parsed.values['--event'];
|
|
146
200
|
if (!phase || !['pre-tool', 'subagent-start', 'post-tool', 'subagent-stop'].includes(phase)) {
|
|
147
201
|
throw new Error('hook-event requires a known --event');
|
|
148
202
|
}
|
|
149
|
-
|
|
203
|
+
if (parsed.values['--bridge'] !== undefined && parsed.values['--bridge'] !== 'true') {
|
|
204
|
+
throw new Error("hook-event --bridge must be 'true'");
|
|
205
|
+
}
|
|
206
|
+
const payload = await readStdin();
|
|
207
|
+
if (phase === 'post-tool') {
|
|
208
|
+
const capabilityToken = payloadText(payload, 'capabilityToken');
|
|
209
|
+
if (capabilityToken) {
|
|
210
|
+
const capabilityStore = createCodexCapabilityStore();
|
|
211
|
+
const armed = capabilityStore.inspect(capabilityToken);
|
|
212
|
+
const capability = createCodexCapabilityStore().attest({
|
|
213
|
+
token: capabilityToken,
|
|
214
|
+
sessionId: payloadText(payload, 'sessionId'),
|
|
215
|
+
turnId: payloadText(payload, 'turnId'),
|
|
216
|
+
toolUseId: payloadText(payload, 'toolUseId'),
|
|
217
|
+
hookDefinitionHash: payloadText(payload, 'hookDefinitionHash'),
|
|
218
|
+
buildIdentity: computeLifecycleBuildIdentity(process.cwd()),
|
|
219
|
+
controller: controllerBinding(armed.taskId)
|
|
220
|
+
});
|
|
221
|
+
output({
|
|
222
|
+
status: capability.status,
|
|
223
|
+
changed: true,
|
|
224
|
+
evidence: {
|
|
225
|
+
revision: capability.revision,
|
|
226
|
+
sessionId: capability.sessionId,
|
|
227
|
+
turnId: capability.turnId,
|
|
228
|
+
toolUseId: capability.toolUseId,
|
|
229
|
+
expiresAt: capability.expiresAt
|
|
230
|
+
},
|
|
231
|
+
diagnostics: [],
|
|
232
|
+
error: null
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (phase === 'post-tool' && parsed.values['--bridge'] === 'true') {
|
|
238
|
+
const toolName = payloadText(payload, 'toolName');
|
|
239
|
+
if (toolName === 'collaborationspawn_agent') {
|
|
240
|
+
const nativeAgent = payloadText(payload, 'nativeAgent');
|
|
241
|
+
if (!MANAGED_AGENT.test(nativeAgent)) {
|
|
242
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const requestedModel = payloadText(payload, 'requestedModel');
|
|
246
|
+
const requestedReasoningEffort = payloadText(payload, 'requestedReasoningEffort');
|
|
247
|
+
const bridged = await activateCodexSpawnDelegation({
|
|
248
|
+
sessionId: payloadText(payload, 'sessionId'),
|
|
249
|
+
turnId: payloadText(payload, 'turnId'),
|
|
250
|
+
toolUseId: payloadText(payload, 'toolUseId'),
|
|
251
|
+
transcriptPath: payloadText(payload, 'transcriptPath'),
|
|
252
|
+
nativeAgent,
|
|
253
|
+
taskName: payloadText(payload, 'taskName'),
|
|
254
|
+
...(requestedModel ? { requestedModel } : {}),
|
|
255
|
+
...(requestedReasoningEffort ? { requestedReasoningEffort } : {})
|
|
256
|
+
}, { store });
|
|
257
|
+
outputBridgeResult(bridged);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (toolName === 'collaborationwait_agent') {
|
|
261
|
+
outputBridgeResult(await sealCodexParentDelegation(payloadText(payload, 'sessionId'), { store }));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const event = recordFromPayload(phase, payload);
|
|
150
268
|
if (!event) {
|
|
269
|
+
if (phase === 'post-tool' && parsed.values['--bridge'] === 'true') {
|
|
270
|
+
const value = payload;
|
|
271
|
+
const childThreadId = String(value.childThreadId ?? '');
|
|
272
|
+
const reconciled = reconcileCodexOrchestrationDelegation(childThreadId);
|
|
273
|
+
outputBridgeResult(reconciled);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
151
276
|
output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
|
|
152
277
|
return;
|
|
153
278
|
}
|
|
279
|
+
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
|
|
280
|
+
try {
|
|
281
|
+
if (store.read(event.childThreadId).consumer) {
|
|
282
|
+
const bridged = await sealCodexOrchestrationDelegation(event.childThreadId, { store });
|
|
283
|
+
outputBridgeResult(bridged);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// The normal apply path below owns missing or invalid evidence errors.
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-child') {
|
|
292
|
+
const resolved = await resolveCodexThread(event.childThreadId);
|
|
293
|
+
store.apply({
|
|
294
|
+
...event,
|
|
295
|
+
parentThreadId: resolved.resolution.thread.parentThreadId
|
|
296
|
+
});
|
|
297
|
+
const bridged = await activateCodexOrchestrationDelegation(event.childThreadId, {
|
|
298
|
+
store,
|
|
299
|
+
resolveThread: async () => resolved
|
|
300
|
+
});
|
|
301
|
+
outputBridgeResult(bridged);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (event.type === 'hook-child') {
|
|
305
|
+
const resolved = await resolveCodexThread(event.childThreadId);
|
|
306
|
+
const result = store.apply({
|
|
307
|
+
...event,
|
|
308
|
+
parentThreadId: resolved.resolution.thread.parentThreadId
|
|
309
|
+
});
|
|
310
|
+
output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: resolved.diagnostics, error: result.state.error });
|
|
311
|
+
if (result.state.status === 'invalid')
|
|
312
|
+
process.exitCode = 1;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
154
315
|
const result = store.apply(event);
|
|
316
|
+
if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
|
|
317
|
+
const bridged = await sealCodexOrchestrationDelegation(event.childThreadId, { store });
|
|
318
|
+
outputBridgeResult(bridged);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
155
321
|
output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: [], error: result.state.error });
|
|
156
322
|
if (result.state.status === 'invalid')
|
|
157
323
|
process.exitCode = 1;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { runCodexSandboxController, verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
|
|
2
|
+
const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
|
|
3
|
+
function fail(code, message, exitCode = 1) {
|
|
4
|
+
process.stdout.write(`${JSON.stringify({
|
|
5
|
+
status: 'failed',
|
|
6
|
+
changed: false,
|
|
7
|
+
error: { code, message }
|
|
8
|
+
})}\n`);
|
|
9
|
+
process.exitCode = exitCode;
|
|
10
|
+
}
|
|
11
|
+
function parse(args) {
|
|
12
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
13
|
+
process.stdout.write(USAGE);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const operation = args[0];
|
|
17
|
+
if (!operation || !['run', 'verify-context'].includes(operation)) {
|
|
18
|
+
process.stderr.write(USAGE);
|
|
19
|
+
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', 'operation must be run or verify-context', 2);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const allowed = new Set([
|
|
23
|
+
'--task-id',
|
|
24
|
+
'--task-ref',
|
|
25
|
+
'--context',
|
|
26
|
+
'--executor-model',
|
|
27
|
+
'--executor-reasoning-effort',
|
|
28
|
+
'--reviewer-model',
|
|
29
|
+
'--reviewer-reasoning-effort'
|
|
30
|
+
]);
|
|
31
|
+
const values = {};
|
|
32
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
33
|
+
const flag = args[index];
|
|
34
|
+
const value = args[++index];
|
|
35
|
+
if (!allowed.has(flag) || Object.hasOwn(values, flag) || !value || value.startsWith('--')) {
|
|
36
|
+
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', `invalid option '${flag}'`, 2);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
values[flag] = value;
|
|
40
|
+
}
|
|
41
|
+
return { operation, values };
|
|
42
|
+
}
|
|
43
|
+
async function codexSandboxController(args = []) {
|
|
44
|
+
const parsed = parse(args);
|
|
45
|
+
if (!parsed || process.exitCode)
|
|
46
|
+
return;
|
|
47
|
+
const taskId = parsed.values['--task-id'];
|
|
48
|
+
if (!taskId) {
|
|
49
|
+
fail('CODEX_SANDBOX_CONTROLLER_PAYLOAD_INVALID', '--task-id is required', 2);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
if (parsed.operation === 'verify-context') {
|
|
54
|
+
const contextPath = parsed.values['--context']
|
|
55
|
+
?? process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
56
|
+
if (!contextPath)
|
|
57
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_MISSING');
|
|
58
|
+
const context = verifyCodexSandboxControllerContext(contextPath, taskId);
|
|
59
|
+
process.stdout.write(`${JSON.stringify({
|
|
60
|
+
status: 'ready',
|
|
61
|
+
changed: false,
|
|
62
|
+
context: {
|
|
63
|
+
taskId: context.taskId,
|
|
64
|
+
controlGeneration: context.controlGeneration,
|
|
65
|
+
controllerInstanceDigest: context.controllerInstanceDigest,
|
|
66
|
+
expiresAt: context.expiresAt,
|
|
67
|
+
buildIdentity: context.buildIdentity,
|
|
68
|
+
hookDefinitionHash: context.hookDefinitionHash,
|
|
69
|
+
lifecycleProfilesHash: context.lifecycleProfilesHash
|
|
70
|
+
},
|
|
71
|
+
error: null
|
|
72
|
+
})}\n`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const taskRef = parsed.values['--task-ref'];
|
|
76
|
+
if (!taskRef)
|
|
77
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_REF_REQUIRED');
|
|
78
|
+
const policy = [
|
|
79
|
+
'--executor-model',
|
|
80
|
+
'--executor-reasoning-effort',
|
|
81
|
+
'--reviewer-model',
|
|
82
|
+
'--reviewer-reasoning-effort'
|
|
83
|
+
];
|
|
84
|
+
if (policy.some((flag) => parsed.values[flag] !== undefined)
|
|
85
|
+
&& policy.some((flag) => parsed.values[flag] === undefined)) {
|
|
86
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_MODEL_POLICY_INVALID');
|
|
87
|
+
}
|
|
88
|
+
const exitCode = await runCodexSandboxController({
|
|
89
|
+
taskId,
|
|
90
|
+
taskRef,
|
|
91
|
+
executorModel: parsed.values['--executor-model'],
|
|
92
|
+
executorReasoningEffort: parsed.values['--executor-reasoning-effort'],
|
|
93
|
+
reviewerModel: parsed.values['--reviewer-model'],
|
|
94
|
+
reviewerReasoningEffort: parsed.values['--reviewer-reasoning-effort']
|
|
95
|
+
});
|
|
96
|
+
process.exitCode = exitCode;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1]
|
|
101
|
+
?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
|
|
102
|
+
fail(code, message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export { codexSandboxController };
|
|
106
|
+
//# sourceMappingURL=codex-sandbox-controller.js.map
|