@fitlab-ai/agent-infra 0.9.8 → 0.9.10
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +25 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +127 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +63 -92
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
- package/dist/lib/agent-clients/adapters/index.js +3 -1
- package/dist/lib/agent-clients/adapters/traecli.js +66 -0
- package/dist/lib/agent-clients/tokens.js +2 -2
- package/dist/lib/agent-clients/types.js +2 -1
- package/dist/lib/defaults.json +5 -0
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +20 -6
- package/dist/lib/internal/codex-sandbox-controller.js +9 -15
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +275 -0
- package/dist/lib/sandbox/control/executor.js +140 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +161 -39
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +248 -6
- package/dist/lib/task/finalization.js +593 -0
- package/dist/lib/task/index.js +0 -9
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +110 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +37 -10
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +163 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +91 -118
- package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
- package/lib/agent-clients/adapters/index.ts +3 -1
- package/lib/agent-clients/adapters/traecli.ts +67 -0
- package/lib/agent-clients/tokens.ts +2 -2
- package/lib/agent-clients/types.ts +2 -1
- package/lib/defaults.json +5 -0
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +19 -6
- package/lib/internal/codex-sandbox-controller.ts +9 -14
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +352 -0
- package/lib/sandbox/control/executor.ts +169 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +168 -40
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +263 -4
- package/lib/task/finalization.ts +761 -0
- package/lib/task/index.ts +0 -8
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +218 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +115 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +3 -4
- package/templates/.agents/QUICKSTART.zh-CN.md +3 -4
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/compatibility-policy.en.md +39 -0
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +2 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
- package/templates/.agents/rules/review-method.en.md +1 -0
- package/templates/.agents/rules/review-method.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +17 -9
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -9
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/templates/.trae/skills/update-agent-infra.en.md +8 -0
- package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { runProbe } from './shell.ts';
|
|
6
|
+
import { resolveSandboxTarget } from './workspace-identity.ts';
|
|
5
7
|
|
|
6
8
|
type WorktreeChange = {
|
|
7
9
|
indexStatus: string;
|
|
@@ -16,6 +18,8 @@ type WorktreeSnapshot = {
|
|
|
16
18
|
head: string;
|
|
17
19
|
changes: readonly WorktreeChange[];
|
|
18
20
|
identity: string;
|
|
21
|
+
source?: 'registered' | 'recovered';
|
|
22
|
+
recovery?: WorktreeRecoveryContext;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
type WorktreeInspection =
|
|
@@ -28,6 +32,14 @@ type WorktreeRemovalPermit = {
|
|
|
28
32
|
snapshot: WorktreeSnapshot;
|
|
29
33
|
};
|
|
30
34
|
|
|
35
|
+
type WorktreeRecoveryContext = Readonly<{
|
|
36
|
+
repoRoot: string;
|
|
37
|
+
worktreeBase: string;
|
|
38
|
+
branch: string;
|
|
39
|
+
identitySource: 'branch-only' | 'task-bound';
|
|
40
|
+
taskId: string | null;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
31
43
|
type Probe = typeof runProbe;
|
|
32
44
|
|
|
33
45
|
function probeText(probe: Probe, cwd: string, args: string[]): string {
|
|
@@ -164,6 +176,203 @@ function captureOnce(worktree: string, probe: Probe): WorktreeSnapshot {
|
|
|
164
176
|
return { worktree: resolvedWorktree, branch, head, changes, identity: hash.digest('hex') };
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
function probeTextWithEnv(
|
|
180
|
+
probe: Probe,
|
|
181
|
+
repoRoot: string,
|
|
182
|
+
args: string[],
|
|
183
|
+
env: NodeJS.ProcessEnv
|
|
184
|
+
): string {
|
|
185
|
+
const result = probe('git', args, { cwd: repoRoot, encoding: 'utf8', env });
|
|
186
|
+
if (result.error) throw result.error;
|
|
187
|
+
if (result.status !== 0) {
|
|
188
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : result.stderr.toString('utf8').trim();
|
|
189
|
+
throw new Error(stderr || `git ${args[0] ?? 'command'} failed with exit code ${result.status ?? 'unknown'}`);
|
|
190
|
+
}
|
|
191
|
+
return typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf8');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function probeMetadataCommand(
|
|
195
|
+
probe: Probe,
|
|
196
|
+
repoRoot: string,
|
|
197
|
+
args: string[],
|
|
198
|
+
env: NodeJS.ProcessEnv
|
|
199
|
+
): boolean {
|
|
200
|
+
const result = probe('git', args, { cwd: repoRoot, encoding: 'utf8', env });
|
|
201
|
+
if (result.error) throw result.error;
|
|
202
|
+
return result.status === 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function hasValidHeadContent(content: string): boolean {
|
|
206
|
+
return /^(?:ref:\s+refs\/\S+|[0-9a-f]{40}|[0-9a-f]{64})$/i.test(content.trim());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function assertRecoverableMetadata(worktree: string, repoRoot: string, probe: Probe): void {
|
|
210
|
+
const dotGit = path.join(worktree, '.git');
|
|
211
|
+
let dotGitStat: fs.Stats;
|
|
212
|
+
try {
|
|
213
|
+
dotGitStat = fs.lstatSync(dotGit);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
if (dotGitStat.isSymbolicLink() || dotGitStat.isDirectory()) {
|
|
219
|
+
throw new Error('WORKTREE_RECOVERY_METADATA_NOT_ELIGIBLE');
|
|
220
|
+
}
|
|
221
|
+
if (!dotGitStat.isFile()) throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
222
|
+
|
|
223
|
+
const content = fs.readFileSync(dotGit, 'utf8').trim();
|
|
224
|
+
const match = /^gitdir:\s*(.+)$/i.exec(content);
|
|
225
|
+
if (!match) throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
226
|
+
|
|
227
|
+
const commonDir = fs.realpathSync.native(path.resolve(repoRoot, probeText(probe, repoRoot, [
|
|
228
|
+
'rev-parse', '--git-common-dir'
|
|
229
|
+
]).trim()));
|
|
230
|
+
const adminRoot = path.join(commonDir, 'worktrees');
|
|
231
|
+
const adminPath = path.resolve(path.dirname(dotGit), match[1]!);
|
|
232
|
+
const relative = path.relative(adminRoot, adminPath);
|
|
233
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative) || relative.includes(path.sep)) {
|
|
234
|
+
throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!fs.existsSync(adminPath)) return;
|
|
238
|
+
const adminStat = fs.lstatSync(adminPath);
|
|
239
|
+
if (adminStat.isSymbolicLink()) throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
240
|
+
if (!adminStat.isDirectory()) return;
|
|
241
|
+
|
|
242
|
+
const requiredFiles = ['HEAD', 'gitdir', 'commondir', 'index'];
|
|
243
|
+
if (requiredFiles.some((name) => !fs.existsSync(path.join(adminPath, name)))) return;
|
|
244
|
+
for (const name of requiredFiles) {
|
|
245
|
+
const filePath = path.join(adminPath, name);
|
|
246
|
+
const stat = fs.lstatSync(filePath);
|
|
247
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const adminGitdir = fs.readFileSync(path.join(adminPath, 'gitdir'), 'utf8').trim();
|
|
251
|
+
const adminCommonDir = fs.readFileSync(path.join(adminPath, 'commondir'), 'utf8').trim();
|
|
252
|
+
const adminGitdirPath = adminGitdir
|
|
253
|
+
? fs.realpathSync.native(path.resolve(adminPath, adminGitdir))
|
|
254
|
+
: '';
|
|
255
|
+
const canonicalDotGit = fs.realpathSync.native(dotGit);
|
|
256
|
+
const adminCommonDirPath = adminCommonDir
|
|
257
|
+
? fs.realpathSync.native(path.resolve(adminPath, adminCommonDir))
|
|
258
|
+
: '';
|
|
259
|
+
if (!adminGitdir || adminGitdirPath !== canonicalDotGit
|
|
260
|
+
|| !adminCommonDir || adminCommonDirPath !== commonDir) {
|
|
261
|
+
throw new Error('WORKTREE_RECOVERY_METADATA_INVALID');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const headContent = fs.readFileSync(path.join(adminPath, 'HEAD'), 'utf8');
|
|
265
|
+
const indexContent = fs.readFileSync(path.join(adminPath, 'index'));
|
|
266
|
+
if (!hasValidHeadContent(headContent) || indexContent.subarray(0, 4).toString('ascii') !== 'DIRC') return;
|
|
267
|
+
|
|
268
|
+
const validationEnv: NodeJS.ProcessEnv = { ...process.env };
|
|
269
|
+
for (const key of ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_INDEX_FILE', 'GIT_WORK_TREE', 'GIT_PREFIX']) {
|
|
270
|
+
delete validationEnv[key];
|
|
271
|
+
}
|
|
272
|
+
validationEnv.GIT_DIR = adminPath;
|
|
273
|
+
validationEnv.GIT_WORK_TREE = path.resolve(worktree);
|
|
274
|
+
if (!probeMetadataCommand(probe, repoRoot, ['rev-parse', '--verify', 'HEAD^{commit}'], validationEnv)) return;
|
|
275
|
+
if (!probeMetadataCommand(probe, repoRoot, ['ls-files', '--stage', '-z'], validationEnv)) return;
|
|
276
|
+
throw new Error('WORKTREE_RECOVERY_METADATA_REGISTERED');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parseRecoveredStatus(raw: string): WorktreeChange[] {
|
|
280
|
+
const records = raw.split('\0');
|
|
281
|
+
const changes: WorktreeChange[] = [];
|
|
282
|
+
for (let index = 0; index < records.length;) {
|
|
283
|
+
const status = records[index++] ?? '';
|
|
284
|
+
if (!status) continue;
|
|
285
|
+
const code = status[0] ?? '';
|
|
286
|
+
if (code === 'R' || code === 'C') {
|
|
287
|
+
const originalPath = records[index++];
|
|
288
|
+
const nextPath = records[index++];
|
|
289
|
+
if (!originalPath || !nextPath) throw new Error('Unable to parse recovered Git rename record');
|
|
290
|
+
changes.push({ indexStatus: code, worktreeStatus: ' ', path: nextPath, originalPath });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const filePath = records[index++];
|
|
294
|
+
if (!filePath) throw new Error('Unable to parse recovered Git status record');
|
|
295
|
+
changes.push({
|
|
296
|
+
indexStatus: code === 'D' ? ' ' : code,
|
|
297
|
+
worktreeStatus: code === 'D' ? 'D' : ' ',
|
|
298
|
+
path: filePath
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return changes;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function captureRecoveredOnce(
|
|
305
|
+
worktree: string,
|
|
306
|
+
recovery: WorktreeRecoveryContext,
|
|
307
|
+
probe: Probe
|
|
308
|
+
): WorktreeSnapshot {
|
|
309
|
+
const resolvedWorktree = path.resolve(worktree);
|
|
310
|
+
const resolvedBase = path.resolve(recovery.worktreeBase);
|
|
311
|
+
const relative = path.relative(resolvedBase, resolvedWorktree);
|
|
312
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
313
|
+
throw new Error(`WORKTREE_RECOVERY_PATH_INVALID: ${resolvedWorktree}`);
|
|
314
|
+
}
|
|
315
|
+
const stat = fs.lstatSync(resolvedWorktree);
|
|
316
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('WORKTREE_RECOVERY_PATH_INVALID');
|
|
317
|
+
const canonicalBase = fs.realpathSync.native(resolvedBase);
|
|
318
|
+
const canonicalWorktree = fs.realpathSync.native(resolvedWorktree);
|
|
319
|
+
const canonicalRelative = path.relative(canonicalBase, canonicalWorktree);
|
|
320
|
+
if (!canonicalRelative || canonicalRelative.startsWith('..') || path.isAbsolute(canonicalRelative)) {
|
|
321
|
+
throw new Error('WORKTREE_RECOVERY_PATH_INVALID');
|
|
322
|
+
}
|
|
323
|
+
const resolvedWorkspace = resolveSandboxTarget(recovery.branch, recovery.repoRoot).workspace;
|
|
324
|
+
if (recovery.identitySource === 'branch-only' && resolvedWorkspace.mode !== 'branch-only') {
|
|
325
|
+
throw new Error('WORKTREE_RECOVERY_IDENTITY_CHANGED');
|
|
326
|
+
}
|
|
327
|
+
if (recovery.identitySource === 'task-bound'
|
|
328
|
+
&& (resolvedWorkspace.mode !== 'task-bound' || resolvedWorkspace.taskId !== recovery.taskId)) {
|
|
329
|
+
throw new Error('WORKTREE_RECOVERY_IDENTITY_CHANGED');
|
|
330
|
+
}
|
|
331
|
+
assertRecoverableMetadata(resolvedWorktree, recovery.repoRoot, probe);
|
|
332
|
+
|
|
333
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-recovered-index-'));
|
|
334
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
335
|
+
for (const key of ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_PREFIX']) delete env[key];
|
|
336
|
+
env.GIT_INDEX_FILE = path.join(tempDir, 'index');
|
|
337
|
+
env.GIT_WORK_TREE = resolvedWorktree;
|
|
338
|
+
try {
|
|
339
|
+
const baseline = probeTextWithEnv(probe, recovery.repoRoot, [
|
|
340
|
+
'rev-parse', '--verify', `refs/heads/${recovery.branch}^{commit}`
|
|
341
|
+
], env).trim();
|
|
342
|
+
if (!baseline) throw new Error(`WORKTREE_RECOVERY_BRANCH_INVALID: ${recovery.branch}`);
|
|
343
|
+
probeTextWithEnv(probe, recovery.repoRoot, ['read-tree', baseline], env);
|
|
344
|
+
probeTextWithEnv(probe, recovery.repoRoot, ['add', '-A', '--', '.'], env);
|
|
345
|
+
const status = probeTextWithEnv(probe, recovery.repoRoot, [
|
|
346
|
+
'diff', '--cached', '--name-status', '--find-renames', '-z', '--', '.'
|
|
347
|
+
], env);
|
|
348
|
+
const changes = parseRecoveredStatus(status);
|
|
349
|
+
const hash = createHash('sha256');
|
|
350
|
+
updatePart(hash, 'source', 'recovered');
|
|
351
|
+
updatePart(hash, 'worktree', resolvedWorktree);
|
|
352
|
+
updatePart(hash, 'repo-root', path.resolve(recovery.repoRoot));
|
|
353
|
+
updatePart(hash, 'branch', recovery.branch);
|
|
354
|
+
updatePart(hash, 'baseline', baseline);
|
|
355
|
+
updatePart(hash, 'identity-source', recovery.identitySource);
|
|
356
|
+
updatePart(hash, 'task-id', recovery.taskId ?? '');
|
|
357
|
+
updatePart(hash, 'status', status);
|
|
358
|
+
for (const change of changes) {
|
|
359
|
+
hashPath(hash, resolvedWorktree, change.path, probe);
|
|
360
|
+
if (change.originalPath) hashPath(hash, resolvedWorktree, change.originalPath, probe);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
worktree: resolvedWorktree,
|
|
364
|
+
branch: recovery.branch,
|
|
365
|
+
head: baseline,
|
|
366
|
+
changes,
|
|
367
|
+
identity: hash.digest('hex'),
|
|
368
|
+
source: 'recovered',
|
|
369
|
+
recovery: { ...recovery }
|
|
370
|
+
};
|
|
371
|
+
} finally {
|
|
372
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
167
376
|
function inspectWorktree(worktree: string, { probe = runProbe }: { probe?: Probe } = {}): WorktreeInspection {
|
|
168
377
|
const resolvedWorktree = path.resolve(worktree);
|
|
169
378
|
try {
|
|
@@ -182,6 +391,28 @@ function inspectWorktree(worktree: string, { probe = runProbe }: { probe?: Probe
|
|
|
182
391
|
}
|
|
183
392
|
}
|
|
184
393
|
|
|
394
|
+
function inspectRecoveredWorktree(
|
|
395
|
+
worktree: string,
|
|
396
|
+
recovery: WorktreeRecoveryContext,
|
|
397
|
+
{ probe = runProbe }: { probe?: Probe } = {}
|
|
398
|
+
): WorktreeInspection {
|
|
399
|
+
const resolvedWorktree = path.resolve(worktree);
|
|
400
|
+
try {
|
|
401
|
+
const first = captureRecoveredOnce(resolvedWorktree, recovery, probe);
|
|
402
|
+
const second = captureRecoveredOnce(resolvedWorktree, recovery, probe);
|
|
403
|
+
if (first.identity !== second.identity) {
|
|
404
|
+
return { status: 'failed', worktree: resolvedWorktree, message: 'Snapshot changed while recovering worktree' };
|
|
405
|
+
}
|
|
406
|
+
return { status: second.changes.length === 0 ? 'clean' : 'dirty', snapshot: second };
|
|
407
|
+
} catch (error) {
|
|
408
|
+
return {
|
|
409
|
+
status: 'failed',
|
|
410
|
+
worktree: resolvedWorktree,
|
|
411
|
+
message: error instanceof Error ? error.message : String(error)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
185
416
|
function inspectWorktrees(worktrees: readonly string[]): WorktreeInspection[] {
|
|
186
417
|
return [...new Set(worktrees.map((worktree) => path.resolve(worktree)))].sort()
|
|
187
418
|
.map((worktree) => inspectWorktree(worktree));
|
|
@@ -201,7 +432,9 @@ function verifyWorktreePermit(
|
|
|
201
432
|
permit: WorktreeRemovalPermit,
|
|
202
433
|
options: { probe?: Probe } = {}
|
|
203
434
|
): WorktreeSnapshot {
|
|
204
|
-
const current =
|
|
435
|
+
const current = permit.snapshot.source === 'recovered' && permit.snapshot.recovery
|
|
436
|
+
? inspectRecoveredWorktree(permit.snapshot.worktree, permit.snapshot.recovery, options)
|
|
437
|
+
: inspectWorktree(permit.snapshot.worktree, options);
|
|
205
438
|
if (current.status === 'failed') throw new Error(`Unable to verify worktree permit: ${current.message}`);
|
|
206
439
|
if (current.snapshot.identity !== permit.snapshot.identity || (permit.mode === 'clean' && current.status !== 'clean')) {
|
|
207
440
|
throw new Error(`Worktree changed after authorization: ${permit.snapshot.worktree}`);
|
|
@@ -227,8 +460,9 @@ export {
|
|
|
227
460
|
createCleanPermit,
|
|
228
461
|
createDiscardPermit,
|
|
229
462
|
formatWorktreeSnapshot,
|
|
463
|
+
inspectRecoveredWorktree,
|
|
230
464
|
inspectWorktree,
|
|
231
465
|
inspectWorktrees,
|
|
232
466
|
verifyWorktreePermit
|
|
233
467
|
};
|
|
234
|
-
export type { WorktreeChange, WorktreeInspection, WorktreeRemovalPermit, WorktreeSnapshot };
|
|
468
|
+
export type { WorktreeChange, WorktreeInspection, WorktreeRecoveryContext, WorktreeRemovalPermit, WorktreeSnapshot };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
preflightCodexLifecycleEvidence,
|
|
5
3
|
resolveCodexSpawnedChild,
|
|
@@ -7,16 +5,28 @@ import {
|
|
|
7
5
|
resolveCodexThread
|
|
8
6
|
} from '../agent-clients/adapters/codex-lifecycle/app-server.ts';
|
|
9
7
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import {
|
|
9
|
+
createCodexCapabilityStore,
|
|
10
|
+
isCodexCapabilityProvenanceDetail
|
|
11
|
+
} from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
12
|
+
import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
13
|
+
import {
|
|
14
|
+
computeLifecycleBuildIdentity,
|
|
15
|
+
verifyLifecycleBuildIdentity,
|
|
16
|
+
type LifecycleBuildIdentity,
|
|
17
|
+
type LifecycleIdentityWarning
|
|
18
|
+
} from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
19
|
+
import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
20
|
+
import {
|
|
21
|
+
verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings
|
|
22
|
+
} from '../agent-clients/adapters/codex-lifecycle/controller-context.ts';
|
|
14
23
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
15
24
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
16
25
|
import {
|
|
17
26
|
activateMatchingOrchestrationDelegation,
|
|
18
27
|
hasActivatableOrchestrationDelegation,
|
|
19
28
|
hasSealableOrchestrationDelegation,
|
|
29
|
+
OrchestrationStateError,
|
|
20
30
|
pauseMatchingOrchestrationDelegation,
|
|
21
31
|
prepareOrchestrationDelegation,
|
|
22
32
|
reconcileMatchingOrchestrationDelegation,
|
|
@@ -52,10 +62,27 @@ function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
|
|
|
52
62
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
function bridgeFailure(code: string, message: string): OrchestrationResult {
|
|
65
|
+
function bridgeFailure(code: string, message: string, detail?: CodexCapabilityProvenanceDetail): OrchestrationResult {
|
|
56
66
|
return {
|
|
57
67
|
status: 'failed', changed: false, taskId: null, run: null, next: null,
|
|
58
|
-
error: { code, message }
|
|
68
|
+
error: { code, message, ...(detail ? { detail } : {}) }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function capabilityFailure(error: unknown): NonNullable<OrchestrationResult['error']> {
|
|
73
|
+
const detailValue = error instanceof Error
|
|
74
|
+
? (error as Error & { detail?: unknown }).detail
|
|
75
|
+
: undefined;
|
|
76
|
+
return {
|
|
77
|
+
code: error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
78
|
+
? error.name
|
|
79
|
+
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
80
|
+
message: error instanceof Error ? error.message : String(error),
|
|
81
|
+
...(error instanceof Error
|
|
82
|
+
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
83
|
+
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
84
|
+
? { detail: detailValue }
|
|
85
|
+
: {})
|
|
59
86
|
};
|
|
60
87
|
}
|
|
61
88
|
|
|
@@ -71,11 +98,25 @@ function requiredStore(options: CodexBridgeOptions): LifecycleStore {
|
|
|
71
98
|
return options.store;
|
|
72
99
|
}
|
|
73
100
|
|
|
74
|
-
function
|
|
75
|
-
const
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
function brokerControllerBinding(): Readonly<{ instanceDigest: string; controlGeneration: string }> | null {
|
|
102
|
+
const raw = process.env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING;
|
|
103
|
+
if (!raw) return null;
|
|
104
|
+
let value: unknown;
|
|
105
|
+
try {
|
|
106
|
+
value = JSON.parse(raw);
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
109
|
+
}
|
|
110
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
111
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
112
|
+
}
|
|
113
|
+
const binding = value as Record<string, unknown>;
|
|
114
|
+
if (Object.keys(binding).sort().join(',') !== 'controlGeneration,instanceDigest'
|
|
115
|
+
|| typeof binding.instanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.instanceDigest)
|
|
116
|
+
|| typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0) {
|
|
117
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
|
|
118
|
+
}
|
|
119
|
+
return binding as { instanceDigest: string; controlGeneration: string };
|
|
79
120
|
}
|
|
80
121
|
|
|
81
122
|
async function prepareCodexOrchestrationDelegation(
|
|
@@ -101,36 +142,113 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
101
142
|
);
|
|
102
143
|
}
|
|
103
144
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
104
|
-
const
|
|
145
|
+
const brokerController = brokerControllerBinding();
|
|
146
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
147
|
+
const contextVerification = contextPath
|
|
148
|
+
? (brokerController
|
|
149
|
+
? verifyControllerContextFileWithWarnings(contextPath, {
|
|
150
|
+
repoRoot,
|
|
151
|
+
generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
|
|
152
|
+
})
|
|
153
|
+
: verifySandboxControllerWithWarnings(contextPath, { repoRoot }))
|
|
154
|
+
: null;
|
|
155
|
+
const identityWarnings: LifecycleIdentityWarning[] = [...(contextVerification?.warnings ?? [])];
|
|
156
|
+
const localController = contextVerification?.context ?? null;
|
|
157
|
+
if (localController && brokerController
|
|
158
|
+
&& (localController.controllerInstanceDigest !== brokerController.instanceDigest
|
|
159
|
+
|| localController.controlGeneration !== brokerController.controlGeneration)) {
|
|
160
|
+
return bridgeFailure(
|
|
161
|
+
'CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH',
|
|
162
|
+
'broker and local controller bindings do not match'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const controller = brokerController ?? (localController ? {
|
|
166
|
+
instanceDigest: localController.controllerInstanceDigest,
|
|
167
|
+
controlGeneration: localController.controlGeneration
|
|
168
|
+
} : null);
|
|
105
169
|
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
106
|
-
const
|
|
170
|
+
const attested = capabilityStore.inspect(input.capabilityToken);
|
|
171
|
+
const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
|
|
172
|
+
if (!capabilityIdentity.ok) {
|
|
173
|
+
return bridgeFailure(capabilityIdentity.code!, capabilityIdentity.message!);
|
|
174
|
+
}
|
|
175
|
+
identityWarnings.push(...capabilityIdentity.warnings);
|
|
176
|
+
const lifecycleProvenance = {
|
|
177
|
+
...attested.buildIdentity,
|
|
178
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
179
|
+
...preflight.hookProvenance,
|
|
180
|
+
capabilitySessionId: attested.sessionId!,
|
|
181
|
+
capabilityTurnId: attested.turnId!,
|
|
182
|
+
capabilityToolUseId: attested.toolUseId!,
|
|
183
|
+
controllerInstanceDigest: controller?.instanceDigest ?? null,
|
|
184
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
185
|
+
};
|
|
186
|
+
const capabilityExpected = {
|
|
107
187
|
taskId: resolved.taskId,
|
|
108
188
|
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
109
189
|
buildIdentity,
|
|
110
190
|
...(controller ? { controller: {
|
|
111
|
-
instanceDigest: controller.
|
|
191
|
+
instanceDigest: controller.instanceDigest,
|
|
112
192
|
controlGeneration: controller.controlGeneration
|
|
113
193
|
} } : {})
|
|
114
|
-
}
|
|
115
|
-
|
|
194
|
+
};
|
|
195
|
+
const prepared = prepareOrchestrationDelegation(taskRef, {
|
|
116
196
|
...input,
|
|
117
|
-
lifecycleProvenance
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
197
|
+
lifecycleProvenance
|
|
198
|
+
}, {
|
|
199
|
+
...coreOptions(options),
|
|
200
|
+
validateLifecycleCapability: () => {
|
|
201
|
+
try {
|
|
202
|
+
const validated = capabilityStore.validate(input.capabilityToken!, capabilityExpected);
|
|
203
|
+
if (validated.sessionId !== attested.sessionId
|
|
204
|
+
|| validated.turnId !== attested.turnId
|
|
205
|
+
|| validated.toolUseId !== attested.toolUseId) {
|
|
206
|
+
return {
|
|
207
|
+
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
208
|
+
message: 'capability identity changed during validation'
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return capabilityFailure(error);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
consumeLifecycleCapability: () => {
|
|
217
|
+
try {
|
|
218
|
+
const consumed = capabilityStore.consume(input.capabilityToken!, capabilityExpected);
|
|
219
|
+
if (consumed.sessionId !== attested.sessionId
|
|
220
|
+
|| consumed.turnId !== attested.turnId
|
|
221
|
+
|| consumed.toolUseId !== attested.toolUseId) {
|
|
222
|
+
return {
|
|
223
|
+
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
224
|
+
message: 'capability identity changed during consumption'
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
return capabilityFailure(error);
|
|
230
|
+
}
|
|
126
231
|
}
|
|
127
|
-
}
|
|
232
|
+
});
|
|
233
|
+
return identityWarnings.length > 0 && prepared.status !== 'failed'
|
|
234
|
+
? { ...prepared, warnings: Object.freeze(identityWarnings) }
|
|
235
|
+
: prepared;
|
|
128
236
|
} catch (error) {
|
|
237
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
238
|
+
const detailValue = error instanceof Error
|
|
239
|
+
? (error as Error & { detail?: unknown }).detail
|
|
240
|
+
: undefined;
|
|
241
|
+
const detail = error instanceof Error
|
|
242
|
+
&& error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
|
|
243
|
+
&& isCodexCapabilityProvenanceDetail(detailValue)
|
|
244
|
+
? detailValue
|
|
245
|
+
: undefined;
|
|
129
246
|
return bridgeFailure(
|
|
130
247
|
error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
131
248
|
? error.name
|
|
132
249
|
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
133
|
-
error instanceof Error ? error.message : String(error)
|
|
250
|
+
error instanceof Error ? error.message : String(error),
|
|
251
|
+
detail
|
|
134
252
|
);
|
|
135
253
|
}
|
|
136
254
|
}
|
|
@@ -161,16 +279,11 @@ async function activateCodexOrchestrationDelegation(
|
|
|
161
279
|
});
|
|
162
280
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
163
281
|
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
164
|
-
const
|
|
165
|
-
? (
|
|
282
|
+
const contextVerification = contextPath
|
|
283
|
+
? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
|
|
166
284
|
: null;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
const controller = contextPath
|
|
171
|
-
? verifyCodexSandboxControllerContext(contextPath, contextTaskId as string, { repoRoot })
|
|
172
|
-
: null;
|
|
173
|
-
return activateMatchingOrchestrationDelegation('codex', {
|
|
285
|
+
const controller = contextVerification?.context ?? null;
|
|
286
|
+
const activated = activateMatchingOrchestrationDelegation('codex', {
|
|
174
287
|
nativeAgent: evidence.nativeAgent,
|
|
175
288
|
childId: evidence.childThreadId,
|
|
176
289
|
parentId: evidence.parentThreadId,
|
|
@@ -195,7 +308,12 @@ async function activateCodexOrchestrationDelegation(
|
|
|
195
308
|
controlGeneration: controller?.controlGeneration ?? null
|
|
196
309
|
}
|
|
197
310
|
}, coreOptions(options));
|
|
311
|
+
const warnings = contextVerification?.warnings ?? [];
|
|
312
|
+
return warnings.length > 0 && activated.status !== 'failed'
|
|
313
|
+
? { ...activated, warnings: Object.freeze(warnings) }
|
|
314
|
+
: activated;
|
|
198
315
|
} catch (error) {
|
|
316
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
199
317
|
return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
200
318
|
}
|
|
201
319
|
}
|
|
@@ -223,6 +341,7 @@ async function activateCodexSpawnDelegation(
|
|
|
223
341
|
resolveThread: async () => resolved
|
|
224
342
|
});
|
|
225
343
|
} catch (error) {
|
|
344
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
226
345
|
return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
227
346
|
}
|
|
228
347
|
}
|
|
@@ -238,7 +357,11 @@ async function sealCodexOrchestrationDelegation(
|
|
|
238
357
|
const store = requiredStore(options);
|
|
239
358
|
const existing = store.read(childThreadId);
|
|
240
359
|
if (!existing.consumer) {
|
|
241
|
-
|
|
360
|
+
const stopTurnId = existing.state.stop?.turnId;
|
|
361
|
+
if (!stopTurnId) {
|
|
362
|
+
return pauseBridge('ORCHESTRATION_CODEX_STOP_EVIDENCE_INVALID', 'Codex lifecycle stop hook is not available', options);
|
|
363
|
+
}
|
|
364
|
+
store.apply(await (options.resolveTerminal ?? resolveCodexTerminal)(childThreadId, stopTurnId));
|
|
242
365
|
}
|
|
243
366
|
const record = store.read(childThreadId);
|
|
244
367
|
const evidence = record.state.stopEvidence;
|
|
@@ -259,6 +382,7 @@ async function sealCodexOrchestrationDelegation(
|
|
|
259
382
|
coreOptions(options)
|
|
260
383
|
);
|
|
261
384
|
} catch (error) {
|
|
385
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
262
386
|
return pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', error instanceof Error ? error.message : String(error), options);
|
|
263
387
|
}
|
|
264
388
|
}
|
|
@@ -280,7 +404,10 @@ async function sealCodexParentDelegation(
|
|
|
280
404
|
const start = active.state.startEvidence!;
|
|
281
405
|
const child = active.state.child!;
|
|
282
406
|
if (!active.state.terminal) {
|
|
283
|
-
|
|
407
|
+
const resolveTerminal = options.resolveTerminal ?? resolveCodexTerminal;
|
|
408
|
+
store.apply(active.state.stop
|
|
409
|
+
? await resolveTerminal(start.childThreadId, active.state.stop.turnId)
|
|
410
|
+
: await resolveTerminal(start.childThreadId));
|
|
284
411
|
}
|
|
285
412
|
if (!store.read(start.childThreadId).state.stop) {
|
|
286
413
|
store.apply({
|
|
@@ -294,6 +421,7 @@ async function sealCodexParentDelegation(
|
|
|
294
421
|
}
|
|
295
422
|
return sealCodexOrchestrationDelegation(start.childThreadId, options);
|
|
296
423
|
} catch (error) {
|
|
424
|
+
if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
|
|
297
425
|
if (error instanceof Error && error.message === 'CODEX_TURN_NOT_TERMINAL') {
|
|
298
426
|
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'The matching Codex child has not completed');
|
|
299
427
|
}
|