@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
|
@@ -8,7 +8,7 @@ import { validateCurrentTaskContract } from './current-contract.ts';
|
|
|
8
8
|
import { parseReviewSummary } from './review-artifacts.ts';
|
|
9
9
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
10
10
|
import type { LedgerDocument } from './ledger.ts';
|
|
11
|
-
import { resolveTaskRef } from './resolve-ref.ts';
|
|
11
|
+
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
12
12
|
import {
|
|
13
13
|
activateDelegation,
|
|
14
14
|
abortActivatedDelegation,
|
|
@@ -23,14 +23,13 @@ import {
|
|
|
23
23
|
sealDelegation
|
|
24
24
|
} from './delegation-receipts.ts';
|
|
25
25
|
import type {
|
|
26
|
-
DelegationLifecycleProvenance,
|
|
27
26
|
DelegationReceipt,
|
|
28
27
|
DelegationRole,
|
|
29
28
|
DelegationStage
|
|
30
29
|
} from './delegation-receipts.ts';
|
|
31
30
|
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
32
|
-
import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
|
|
33
31
|
import {
|
|
32
|
+
getAgentClientAdapter,
|
|
34
33
|
getAgentClientCapability,
|
|
35
34
|
getAgentClientDelegationEvidence,
|
|
36
35
|
getAgentClientModelSelection
|
|
@@ -50,8 +49,6 @@ import type { RepositorySnapshot } from './workspace-snapshot.ts';
|
|
|
50
49
|
import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
|
|
51
50
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
52
51
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
53
|
-
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
54
|
-
import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
55
52
|
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
|
|
56
53
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
|
|
57
54
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
|
|
@@ -66,24 +63,23 @@ type ModelPolicySource = Readonly<{
|
|
|
66
63
|
client: AgentClientId;
|
|
67
64
|
resolvedAt: string;
|
|
68
65
|
}>;
|
|
69
|
-
type
|
|
66
|
+
type ClientCapabilityRecovery = Readonly<{
|
|
70
67
|
code: 'CLIENT_CAPABILITY_ENABLED';
|
|
71
68
|
recoveredAt: string;
|
|
72
69
|
previousStatus: 'paused';
|
|
73
70
|
previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
|
|
74
|
-
client:
|
|
71
|
+
client: AgentClientId;
|
|
75
72
|
guards: Readonly<{
|
|
76
73
|
stepCount: 0;
|
|
77
74
|
nextStage: null;
|
|
78
75
|
baselineEmpty: true;
|
|
79
76
|
receiptCount: 0;
|
|
80
77
|
pendingDelegation: false;
|
|
81
|
-
commitAuthorizationUnused: true;
|
|
82
78
|
completionEvidenceAbsent: true;
|
|
83
79
|
}>;
|
|
84
80
|
resultingStatus: 'running';
|
|
85
81
|
}>;
|
|
86
|
-
type OrchestrationRecovery =
|
|
82
|
+
type OrchestrationRecovery = ClientCapabilityRecovery;
|
|
87
83
|
type CleanCompletionEvidence = Readonly<{
|
|
88
84
|
kind: 'reviewed-head-clean';
|
|
89
85
|
observedAt: string;
|
|
@@ -108,7 +104,6 @@ type OrchestrationRun = Readonly<{
|
|
|
108
104
|
pendingDelegation: DelegationReceipt | null;
|
|
109
105
|
receipts: readonly DelegationReceipt[];
|
|
110
106
|
pause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
|
|
111
|
-
commitAuthorization: Readonly<{ issuedAt: string | null; consumedAt: string | null }>;
|
|
112
107
|
completionEvidence: CleanCompletionEvidence | null;
|
|
113
108
|
createdAt: string;
|
|
114
109
|
updatedAt: string;
|
|
@@ -128,6 +123,7 @@ type OrchestrationResult = Readonly<{
|
|
|
128
123
|
taskId: string | null;
|
|
129
124
|
run: OrchestrationRun | null;
|
|
130
125
|
next: OrchestrationNext | null;
|
|
126
|
+
adapterContext?: string;
|
|
131
127
|
warnings?: readonly Readonly<{ code: string; message: string; action: string }>[];
|
|
132
128
|
error: Readonly<{
|
|
133
129
|
code: string;
|
|
@@ -135,7 +131,7 @@ type OrchestrationResult = Readonly<{
|
|
|
135
131
|
client?: AgentClientId;
|
|
136
132
|
missingFields?: readonly string[];
|
|
137
133
|
modelSelectionContext?: ReturnType<typeof getAgentClientModelSelection>;
|
|
138
|
-
detail?:
|
|
134
|
+
detail?: unknown;
|
|
139
135
|
}> | null;
|
|
140
136
|
}>;
|
|
141
137
|
type OrchestrationStageIdentity = Readonly<{
|
|
@@ -155,6 +151,7 @@ type OrchestrationCompletionPlanResult = Readonly<{
|
|
|
155
151
|
}>;
|
|
156
152
|
type OrchestrationOptions = {
|
|
157
153
|
repoRoot?: string;
|
|
154
|
+
taskId?: string;
|
|
158
155
|
gitWorktreeRoot?: string;
|
|
159
156
|
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
160
157
|
id?: () => string;
|
|
@@ -191,7 +188,7 @@ function supportsLifecycleDelegation(client: AgentClientId): boolean {
|
|
|
191
188
|
}
|
|
192
189
|
|
|
193
190
|
function orchestrationPath(taskDir: string): string {
|
|
194
|
-
return path.join(taskDir, 'orchestration.json');
|
|
191
|
+
return path.join(taskDir, '.runtime', 'orchestration.json');
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; the file was left unchanged; rebuild the sandbox or manually repair the state before retrying';
|
|
@@ -250,7 +247,7 @@ function isModelPolicy(value: unknown): value is OrchestrationModelPolicy {
|
|
|
250
247
|
|
|
251
248
|
const RECOVERY_GUARD_KEYS = [
|
|
252
249
|
'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
|
|
253
|
-
'
|
|
250
|
+
'completionEvidenceAbsent'
|
|
254
251
|
] as const;
|
|
255
252
|
|
|
256
253
|
function isRecovery(value: unknown): value is OrchestrationRecovery {
|
|
@@ -263,14 +260,13 @@ function isRecovery(value: unknown): value is OrchestrationRecovery {
|
|
|
263
260
|
&& value.previousStatus === 'paused'
|
|
264
261
|
&& isPause(value.previousPause)
|
|
265
262
|
&& value.previousPause.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
266
|
-
&& value.client
|
|
263
|
+
&& isAgentClientId(value.client)
|
|
267
264
|
&& hasExactKeys(guards, RECOVERY_GUARD_KEYS)
|
|
268
265
|
&& guards.stepCount === 0
|
|
269
266
|
&& guards.nextStage === null
|
|
270
267
|
&& guards.baselineEmpty === true
|
|
271
268
|
&& guards.receiptCount === 0
|
|
272
269
|
&& guards.pendingDelegation === false
|
|
273
|
-
&& guards.commitAuthorizationUnused === true
|
|
274
270
|
&& guards.completionEvidenceAbsent === true
|
|
275
271
|
&& value.resultingStatus === 'running';
|
|
276
272
|
}
|
|
@@ -292,15 +288,14 @@ export function isCompletionEvidence(value: unknown): value is CleanCompletionEv
|
|
|
292
288
|
const ORCHESTRATION_RUN_KEYS = [
|
|
293
289
|
'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
|
|
294
290
|
'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
|
|
295
|
-
'pause', '
|
|
291
|
+
'pause', 'completionEvidence', 'createdAt', 'updatedAt'
|
|
296
292
|
] as const;
|
|
297
293
|
|
|
298
|
-
function orchestrationStateReasonCodes(value: unknown
|
|
294
|
+
function orchestrationStateReasonCodes(value: unknown): string[] {
|
|
299
295
|
if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS)) return ['top-level-keys'];
|
|
300
296
|
const record = value;
|
|
301
297
|
const reasons: string[] = [];
|
|
302
298
|
if (!exactText(record.taskId)) reasons.push('taskId');
|
|
303
|
-
else if (expectedTaskId !== undefined && record.taskId !== expectedTaskId) reasons.push('taskId-mismatch');
|
|
304
299
|
if (!exactText(record.runId)) reasons.push('runId');
|
|
305
300
|
if (!['running', 'paused', 'completed'].includes(record.status as string)) reasons.push('status');
|
|
306
301
|
if (!(record.nextStage === null || ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(record.nextStage as string))) {
|
|
@@ -315,19 +310,13 @@ function orchestrationStateReasonCodes(value: unknown, expectedTaskId?: string):
|
|
|
315
310
|
if (!(record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) reasons.push('pendingDelegation');
|
|
316
311
|
if (!Array.isArray(record.receipts) || !record.receipts.every(isDelegationReceipt)) reasons.push('receipts');
|
|
317
312
|
if (!(record.pause === null || isPause(record.pause))) reasons.push('pause');
|
|
318
|
-
if (!hasExactKeys(record.commitAuthorization, ['issuedAt', 'consumedAt'])) {
|
|
319
|
-
reasons.push('commitAuthorization');
|
|
320
|
-
} else {
|
|
321
|
-
if (!nullableText(record.commitAuthorization.issuedAt)) reasons.push('commitAuthorization.issuedAt');
|
|
322
|
-
if (!nullableText(record.commitAuthorization.consumedAt)) reasons.push('commitAuthorization.consumedAt');
|
|
323
|
-
}
|
|
324
313
|
if (!(record.completionEvidence === null || isCompletionEvidence(record.completionEvidence))) reasons.push('completionEvidence');
|
|
325
314
|
if (!exactText(record.createdAt)) reasons.push('createdAt');
|
|
326
315
|
if (!exactText(record.updatedAt)) reasons.push('updatedAt');
|
|
327
316
|
if (Array.isArray(record.receipts) && record.receipts.every(isDelegationReceipt)
|
|
328
317
|
&& (record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) {
|
|
329
318
|
const receipts = [...record.receipts, ...(record.pendingDelegation ? [record.pendingDelegation] : [])];
|
|
330
|
-
if (receipts.some((receipt) => receipt.
|
|
319
|
+
if (receipts.some((receipt) => receipt.runId !== record.runId)) {
|
|
331
320
|
reasons.push('receipt-identity');
|
|
332
321
|
}
|
|
333
322
|
}
|
|
@@ -375,7 +364,7 @@ function fileObservation(file: string): OrchestrationDiagnosticFields {
|
|
|
375
364
|
}
|
|
376
365
|
|
|
377
366
|
function parseOrchestrationRun(value: unknown, expectedTaskId?: string): OrchestrationRun {
|
|
378
|
-
const reasonCodes = orchestrationStateReasonCodes(value
|
|
367
|
+
const reasonCodes = orchestrationStateReasonCodes(value);
|
|
379
368
|
if (reasonCodes.length > 0) throw new OrchestrationStateError(expectedTaskId ?? null, reasonCodes);
|
|
380
369
|
return value as OrchestrationRun;
|
|
381
370
|
}
|
|
@@ -420,7 +409,100 @@ function readRun(taskDir: string, options: Pick<OrchestrationOptions, 'diagnosti
|
|
|
420
409
|
}
|
|
421
410
|
}
|
|
422
411
|
|
|
412
|
+
function findDelegation(run: OrchestrationRun, receiptId: string): Readonly<{
|
|
413
|
+
receipt: DelegationReceipt;
|
|
414
|
+
location: 'pending' | 'history';
|
|
415
|
+
}> | null {
|
|
416
|
+
if (run.pendingDelegation?.id === receiptId) return { receipt: run.pendingDelegation, location: 'pending' };
|
|
417
|
+
const receipt = run.receipts.find((entry) => entry.id === receiptId);
|
|
418
|
+
return receipt ? { receipt, location: 'history' } : null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function readDelegationAdapterEvidence(
|
|
422
|
+
taskRef: string,
|
|
423
|
+
receiptId: string,
|
|
424
|
+
adapterId: AgentClientId,
|
|
425
|
+
options: OrchestrationOptions = {}
|
|
426
|
+
): unknown {
|
|
427
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
428
|
+
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
429
|
+
const run = readRun(resolved.taskDir, options);
|
|
430
|
+
const found = run && findDelegation(run, receiptId);
|
|
431
|
+
if (!found || found.receipt.client !== adapterId) return null;
|
|
432
|
+
return found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function mutateDelegationAdapterEvidence(
|
|
436
|
+
taskRef: string,
|
|
437
|
+
receiptId: string,
|
|
438
|
+
adapterId: AgentClientId,
|
|
439
|
+
mutate: (current: unknown) => unknown,
|
|
440
|
+
options: OrchestrationOptions = {}
|
|
441
|
+
): unknown {
|
|
442
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
443
|
+
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
444
|
+
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, false);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function mutateDelegationAdapterEvidenceWithinTaskLock(
|
|
448
|
+
taskRef: string,
|
|
449
|
+
receiptId: string,
|
|
450
|
+
adapterId: AgentClientId,
|
|
451
|
+
mutate: (current: unknown) => unknown,
|
|
452
|
+
options: OrchestrationOptions = {}
|
|
453
|
+
): unknown {
|
|
454
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
455
|
+
if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
456
|
+
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function mutateDelegationAdapterEvidenceAtResolved(
|
|
460
|
+
resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
|
|
461
|
+
receiptId: string,
|
|
462
|
+
adapterId: AgentClientId,
|
|
463
|
+
mutate: (current: unknown) => unknown,
|
|
464
|
+
options: OrchestrationOptions,
|
|
465
|
+
taskLockHeld: boolean
|
|
466
|
+
): unknown {
|
|
467
|
+
const update = () => {
|
|
468
|
+
try {
|
|
469
|
+
const run = readRun(resolved.taskDir, options);
|
|
470
|
+
const found = run && findDelegation(run, receiptId);
|
|
471
|
+
if (!run || !found || found.receipt.client !== adapterId) {
|
|
472
|
+
throw new Error('ORCHESTRATION_DELEGATION_MISSING: adapter evidence has no matching task receipt');
|
|
473
|
+
}
|
|
474
|
+
const current = found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
475
|
+
const nextState = mutate(current);
|
|
476
|
+
const serialized = JSON.stringify(nextState);
|
|
477
|
+
if (serialized === undefined) throw new Error('Agent adapter evidence must be JSON serializable');
|
|
478
|
+
if (serialized === JSON.stringify(current)) return JSON.parse(serialized) as unknown;
|
|
479
|
+
const adapterEvidence = Object.freeze({
|
|
480
|
+
...(found.receipt.adapterEvidence ?? {}),
|
|
481
|
+
[adapterId]: JSON.parse(serialized) as unknown
|
|
482
|
+
});
|
|
483
|
+
const nextReceipt = Object.freeze({ ...found.receipt, adapterEvidence });
|
|
484
|
+
const updated = found.location === 'pending'
|
|
485
|
+
? withUpdatedRun(run, { pendingDelegation: nextReceipt }, options.now)
|
|
486
|
+
: withUpdatedRun(run, {
|
|
487
|
+
receipts: Object.freeze(run.receipts.map((receipt) => receipt.id === receiptId ? nextReceipt : receipt))
|
|
488
|
+
}, options.now);
|
|
489
|
+
saveRun(resolved.taskDir, updated);
|
|
490
|
+
return JSON.parse(serialized) as unknown;
|
|
491
|
+
} catch (error) {
|
|
492
|
+
mutationError = error;
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
let mutationError: unknown;
|
|
497
|
+
const result = taskLockHeld
|
|
498
|
+
? update()
|
|
499
|
+
: withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
|
|
500
|
+
if (mutationError) throw mutationError;
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
|
|
423
504
|
function atomicWrite(file: string, value: unknown): void {
|
|
505
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
424
506
|
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
425
507
|
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
426
508
|
fs.renameSync(temp, file);
|
|
@@ -483,17 +565,16 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
|
|
|
483
565
|
return normalizeAgentClients(raw).state[client].orchestration;
|
|
484
566
|
}
|
|
485
567
|
|
|
486
|
-
function
|
|
568
|
+
function canRecoverUnsupportedClientPause(run: OrchestrationRun): boolean {
|
|
487
569
|
return run.status === 'paused'
|
|
488
570
|
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
489
|
-
&& run.modelPolicySource?.client
|
|
571
|
+
&& isAgentClientId(run.modelPolicySource?.client)
|
|
572
|
+
&& supportsLifecycleDelegation(run.modelPolicySource.client)
|
|
490
573
|
&& run.stepCount === 0
|
|
491
574
|
&& run.nextStage === null
|
|
492
575
|
&& run.baseline === ''
|
|
493
576
|
&& run.pendingDelegation === null
|
|
494
577
|
&& run.receipts.length === 0
|
|
495
|
-
&& run.commitAuthorization?.issuedAt === null
|
|
496
|
-
&& run.commitAuthorization?.consumedAt === null
|
|
497
578
|
&& run.completionEvidence == null
|
|
498
579
|
&& run.recoveryHistory.length === 0;
|
|
499
580
|
}
|
|
@@ -525,24 +606,22 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
525
606
|
}
|
|
526
607
|
}
|
|
527
608
|
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
528
|
-
if (existing
|
|
529
|
-
|| !canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
|
|
609
|
+
if (!canRecoverUnsupportedClientPause(existing)) {
|
|
530
610
|
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
531
611
|
}
|
|
532
612
|
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
533
|
-
const recovery:
|
|
613
|
+
const recovery: ClientCapabilityRecovery = {
|
|
534
614
|
code: 'CLIENT_CAPABILITY_ENABLED',
|
|
535
615
|
recoveredAt: now,
|
|
536
616
|
previousStatus: 'paused',
|
|
537
|
-
previousPause: existing.pause as
|
|
538
|
-
client:
|
|
617
|
+
previousPause: existing.pause as ClientCapabilityRecovery['previousPause'],
|
|
618
|
+
client: existing.modelPolicySource.client,
|
|
539
619
|
guards: {
|
|
540
620
|
stepCount: 0,
|
|
541
621
|
nextStage: null,
|
|
542
622
|
baselineEmpty: true,
|
|
543
623
|
receiptCount: 0,
|
|
544
624
|
pendingDelegation: false,
|
|
545
|
-
commitAuthorizationUnused: true,
|
|
546
625
|
completionEvidenceAbsent: true
|
|
547
626
|
},
|
|
548
627
|
resultingStatus: 'running'
|
|
@@ -595,7 +674,6 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
595
674
|
pendingDelegation: null,
|
|
596
675
|
receipts: [],
|
|
597
676
|
pause: null,
|
|
598
|
-
commitAuthorization: { issuedAt: null, consumedAt: null },
|
|
599
677
|
completionEvidence: null,
|
|
600
678
|
createdAt: now,
|
|
601
679
|
updatedAt: now
|
|
@@ -828,13 +906,58 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
|
|
|
828
906
|
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
829
907
|
}
|
|
830
908
|
|
|
909
|
+
function validatePreparedModelPolicy(
|
|
910
|
+
taskId: string,
|
|
911
|
+
run: OrchestrationRun,
|
|
912
|
+
next: NonNullable<OrchestrationResult['next']>,
|
|
913
|
+
input: Readonly<{ requestedModel?: string; requestedReasoningEffort?: string }>
|
|
914
|
+
): OrchestrationResult | null {
|
|
915
|
+
if (!validModel(input.requestedModel)) {
|
|
916
|
+
return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', taskId);
|
|
917
|
+
}
|
|
918
|
+
if (!validModel(input.requestedReasoningEffort)) {
|
|
919
|
+
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', taskId);
|
|
920
|
+
}
|
|
921
|
+
const expectedPolicy = run.modelPolicy[next.role];
|
|
922
|
+
if (input.requestedModel !== expectedPolicy.model) {
|
|
923
|
+
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, taskId);
|
|
924
|
+
}
|
|
925
|
+
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
926
|
+
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, taskId);
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function captureTaskSnapshot(taskId: string, repoRoot: string, options: OrchestrationOptions): string {
|
|
932
|
+
return (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
933
|
+
gitRoot: gitRootFor(repoRoot, options),
|
|
934
|
+
stateRoot: repoRoot,
|
|
935
|
+
taskId
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function validateDelegationRun(
|
|
940
|
+
taskRef: string,
|
|
941
|
+
taskId: string,
|
|
942
|
+
run: OrchestrationRun | null,
|
|
943
|
+
options: OrchestrationOptions
|
|
944
|
+
): OrchestrationResult | null {
|
|
945
|
+
if (!run || run.status !== 'running') {
|
|
946
|
+
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', taskId);
|
|
947
|
+
}
|
|
948
|
+
if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', taskId);
|
|
949
|
+
if (run.stepCount >= run.maxSteps) {
|
|
950
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
|
|
831
955
|
function prepareOrchestrationDelegationUnlocked(
|
|
832
956
|
taskRef: string,
|
|
833
957
|
input: Readonly<{
|
|
834
958
|
client: AgentClientId;
|
|
835
959
|
requestedModel?: string;
|
|
836
960
|
requestedReasoningEffort?: string;
|
|
837
|
-
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
838
961
|
}>,
|
|
839
962
|
options: OrchestrationOptions = {}
|
|
840
963
|
): OrchestrationResult {
|
|
@@ -852,54 +975,34 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
852
975
|
requestedReasoningEffort: input.requestedReasoningEffort ?? null
|
|
853
976
|
});
|
|
854
977
|
const run = readRun(resolved.taskDir, options);
|
|
855
|
-
|
|
856
|
-
if (
|
|
857
|
-
const
|
|
858
|
-
if (repositoryPending.length > 0) {
|
|
859
|
-
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
860
|
-
}
|
|
861
|
-
if (run.stepCount >= run.maxSteps) return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
978
|
+
const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
|
|
979
|
+
if (runFailure) return runFailure;
|
|
980
|
+
const activeRun = run!;
|
|
862
981
|
const routed = routeOrchestration(taskRef, options);
|
|
863
982
|
if (!routed.next) return routed;
|
|
864
983
|
const next = routed.next;
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
if (!validModel(input.requestedReasoningEffort)) {
|
|
869
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
870
|
-
}
|
|
871
|
-
const expectedPolicy = run.modelPolicy[next.role];
|
|
872
|
-
if (input.requestedModel !== expectedPolicy.model) {
|
|
873
|
-
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
874
|
-
}
|
|
875
|
-
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
876
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
877
|
-
}
|
|
984
|
+
const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
|
|
985
|
+
if (policyFailure) return policyFailure;
|
|
878
986
|
let beforeFingerprint: string;
|
|
879
987
|
try {
|
|
880
|
-
beforeFingerprint = (
|
|
881
|
-
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
882
|
-
stateRoot: resolved.repoRoot,
|
|
883
|
-
taskId: resolved.taskId
|
|
884
|
-
});
|
|
988
|
+
beforeFingerprint = captureTaskSnapshot(resolved.taskId, resolved.repoRoot, options);
|
|
885
989
|
} catch (error) {
|
|
886
990
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
887
991
|
}
|
|
888
992
|
const receipt = prepareDelegation({
|
|
889
993
|
taskId: resolved.taskId,
|
|
890
|
-
runId:
|
|
994
|
+
runId: activeRun.runId,
|
|
891
995
|
role: next.role,
|
|
892
996
|
stage: next.stage,
|
|
893
997
|
round: next.round,
|
|
894
998
|
artifact: next.artifact,
|
|
895
999
|
client: input.client,
|
|
896
|
-
requestedModel: input.requestedModel
|
|
897
|
-
requestedReasoningEffort: input.requestedReasoningEffort
|
|
1000
|
+
requestedModel: input.requestedModel!,
|
|
1001
|
+
requestedReasoningEffort: input.requestedReasoningEffort!,
|
|
898
1002
|
workspaceSnapshotScope: 'task',
|
|
899
|
-
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
900
1003
|
beforeFingerprint
|
|
901
1004
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
902
|
-
const updated = withUpdatedRun(
|
|
1005
|
+
const updated = withUpdatedRun(activeRun, {
|
|
903
1006
|
nextStage: next.stage,
|
|
904
1007
|
pendingDelegation: receipt
|
|
905
1008
|
});
|
|
@@ -913,7 +1016,6 @@ function prepareOrchestrationDelegation(
|
|
|
913
1016
|
client: AgentClientId;
|
|
914
1017
|
requestedModel?: string;
|
|
915
1018
|
requestedReasoningEffort?: string;
|
|
916
|
-
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
917
1019
|
}>,
|
|
918
1020
|
options: OrchestrationOptions = {}
|
|
919
1021
|
): OrchestrationResult {
|
|
@@ -922,14 +1024,9 @@ function prepareOrchestrationDelegation(
|
|
|
922
1024
|
try {
|
|
923
1025
|
return withTaskExecutionLock(
|
|
924
1026
|
resolved.repoRoot,
|
|
925
|
-
|
|
926
|
-
'task-orchestration.prepare.
|
|
927
|
-
() =>
|
|
928
|
-
resolved.repoRoot,
|
|
929
|
-
resolved.taskId,
|
|
930
|
-
'task-orchestration.prepare.task',
|
|
931
|
-
() => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
|
|
932
|
-
)
|
|
1027
|
+
resolved.taskId,
|
|
1028
|
+
'task-orchestration.prepare.task',
|
|
1029
|
+
() => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
|
|
933
1030
|
);
|
|
934
1031
|
} catch (error) {
|
|
935
1032
|
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
@@ -967,14 +1064,9 @@ function dispatchOrchestrationDelegation(
|
|
|
967
1064
|
try {
|
|
968
1065
|
return withTaskExecutionLock(
|
|
969
1066
|
resolved.repoRoot,
|
|
970
|
-
|
|
971
|
-
'task-orchestration.dispatch.
|
|
972
|
-
() =>
|
|
973
|
-
resolved.repoRoot,
|
|
974
|
-
resolved.taskId,
|
|
975
|
-
'task-orchestration.dispatch.task',
|
|
976
|
-
() => dispatchOrchestrationDelegationUnlocked(taskRef, options)
|
|
977
|
-
)
|
|
1067
|
+
resolved.taskId,
|
|
1068
|
+
'task-orchestration.dispatch.task',
|
|
1069
|
+
() => dispatchOrchestrationDelegationUnlocked(taskRef, options)
|
|
978
1070
|
);
|
|
979
1071
|
} catch (error) {
|
|
980
1072
|
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
@@ -982,49 +1074,28 @@ function dispatchOrchestrationDelegation(
|
|
|
982
1074
|
}
|
|
983
1075
|
}
|
|
984
1076
|
|
|
1077
|
+
type MatchingDelegationsResult =
|
|
1078
|
+
| Readonly<{ ok: true; matches: readonly { taskId: string; run: OrchestrationRun }[] }>
|
|
1079
|
+
| Readonly<{ ok: false; error: OrchestrationResult }>;
|
|
1080
|
+
|
|
985
1081
|
function matchingDelegations(
|
|
986
1082
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
987
1083
|
options: OrchestrationOptions
|
|
988
|
-
):
|
|
1084
|
+
): MatchingDelegationsResult {
|
|
1085
|
+
const taskId: unknown = options.taskId;
|
|
1086
|
+
if (taskId === undefined || taskId === '') {
|
|
1087
|
+
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_REQUIRED', 'taskId is required for lifecycle matching') };
|
|
1088
|
+
}
|
|
1089
|
+
if (typeof taskId !== 'string' || !TASK_ID_RE.test(taskId)) {
|
|
1090
|
+
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
|
|
1091
|
+
}
|
|
989
1092
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
.filter((entry) => entry.isDirectory())
|
|
997
|
-
.flatMap((entry) => {
|
|
998
|
-
const candidateTaskId = entry.name;
|
|
999
|
-
const candidateTaskDir = path.join(activeRoot, candidateTaskId);
|
|
1000
|
-
emitDiagnostic(options, 'orchestration-active-scan-candidate', {
|
|
1001
|
-
candidateTaskId,
|
|
1002
|
-
candidateTaskDir,
|
|
1003
|
-
orchestrationPath: orchestrationPath(candidateTaskDir)
|
|
1004
|
-
});
|
|
1005
|
-
let run: OrchestrationRun | null;
|
|
1006
|
-
try {
|
|
1007
|
-
run = readRun(candidateTaskDir, options);
|
|
1008
|
-
} catch (error) {
|
|
1009
|
-
emitDiagnostic(options, 'orchestration-active-scan-candidate-failed', {
|
|
1010
|
-
candidateTaskId,
|
|
1011
|
-
candidateTaskDir,
|
|
1012
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
1013
|
-
reasonCodes: error instanceof OrchestrationStateError ? error.reasonCodes.join(',') : null
|
|
1014
|
-
});
|
|
1015
|
-
throw error;
|
|
1016
|
-
}
|
|
1017
|
-
if (!run || run.status !== 'running' || !run.pendingDelegation) return [];
|
|
1018
|
-
const receipt = run.pendingDelegation;
|
|
1019
|
-
return predicate(receipt)
|
|
1020
|
-
? [{ taskId: candidateTaskId, run }]
|
|
1021
|
-
: [];
|
|
1022
|
-
});
|
|
1023
|
-
emitDiagnostic(options, 'orchestration-active-scan-finished', {
|
|
1024
|
-
activeRoot,
|
|
1025
|
-
matchingDelegations: matches.length
|
|
1026
|
-
});
|
|
1027
|
-
return matches;
|
|
1093
|
+
const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
|
|
1094
|
+
const run = readRun(taskDir, options);
|
|
1095
|
+
const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
|
|
1096
|
+
? [{ taskId, run }]
|
|
1097
|
+
: [];
|
|
1098
|
+
return { ok: true, matches };
|
|
1028
1099
|
}
|
|
1029
1100
|
|
|
1030
1101
|
function uniqueMatchingDelegation(
|
|
@@ -1032,12 +1103,13 @@ function uniqueMatchingDelegation(
|
|
|
1032
1103
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
1033
1104
|
options: OrchestrationOptions
|
|
1034
1105
|
): { taskId: string; run: OrchestrationRun } | OrchestrationResult {
|
|
1035
|
-
const
|
|
1106
|
+
const lookup = matchingDelegations(
|
|
1036
1107
|
(receipt) => receipt.client === client && predicate(receipt),
|
|
1037
1108
|
options
|
|
1038
1109
|
);
|
|
1110
|
+
if (!lookup.ok) return lookup.error;
|
|
1111
|
+
const matches = lookup.matches;
|
|
1039
1112
|
if (matches.length === 0) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
1040
|
-
if (matches.length > 1) return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
1041
1113
|
return matches[0]!;
|
|
1042
1114
|
}
|
|
1043
1115
|
|
|
@@ -1046,13 +1118,14 @@ function hasActivatableOrchestrationDelegation(
|
|
|
1046
1118
|
childId: string,
|
|
1047
1119
|
options: OrchestrationOptions = {}
|
|
1048
1120
|
): boolean {
|
|
1049
|
-
|
|
1121
|
+
const lookup = matchingDelegations(
|
|
1050
1122
|
(receipt) => receipt.client === client && (
|
|
1051
1123
|
receipt.status === 'prepared'
|
|
1052
1124
|
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))
|
|
1053
1125
|
),
|
|
1054
1126
|
options
|
|
1055
|
-
)
|
|
1127
|
+
);
|
|
1128
|
+
return lookup.ok && lookup.matches.length > 0;
|
|
1056
1129
|
}
|
|
1057
1130
|
|
|
1058
1131
|
function hasSealableOrchestrationDelegation(
|
|
@@ -1060,12 +1133,13 @@ function hasSealableOrchestrationDelegation(
|
|
|
1060
1133
|
childId: string,
|
|
1061
1134
|
options: OrchestrationOptions = {}
|
|
1062
1135
|
): boolean {
|
|
1063
|
-
|
|
1136
|
+
const lookup = matchingDelegations(
|
|
1064
1137
|
(receipt) => receipt.client === client
|
|
1065
1138
|
&& receipt.childId === childId
|
|
1066
1139
|
&& ['stage-completed', 'sealed'].includes(receipt.status),
|
|
1067
1140
|
options
|
|
1068
|
-
)
|
|
1141
|
+
);
|
|
1142
|
+
return lookup.ok && lookup.matches.length > 0;
|
|
1069
1143
|
}
|
|
1070
1144
|
|
|
1071
1145
|
function activateMatchingOrchestrationDelegation(
|
|
@@ -1095,7 +1169,8 @@ function activateMatchingOrchestrationDelegation(
|
|
|
1095
1169
|
&& (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
|
|
1096
1170
|
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
1097
1171
|
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
1098
|
-
&&
|
|
1172
|
+
&& (event.clientEvidence === undefined
|
|
1173
|
+
|| JSON.stringify(receipt.adapterEvidence?.[client]) === JSON.stringify(event.clientEvidence));
|
|
1099
1174
|
if (!sameEvidence) {
|
|
1100
1175
|
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
1101
1176
|
}
|
|
@@ -1128,7 +1203,8 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1128
1203
|
modelFallbackReason?: string;
|
|
1129
1204
|
reasoningEffortFallbackReason?: string;
|
|
1130
1205
|
}>,
|
|
1131
|
-
options: OrchestrationOptions = {}
|
|
1206
|
+
options: OrchestrationOptions = {},
|
|
1207
|
+
beforeSeal?: () => OrchestrationResult | null
|
|
1132
1208
|
): OrchestrationResult {
|
|
1133
1209
|
const role = managedDelegationRole(event.nativeAgent);
|
|
1134
1210
|
if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
@@ -1161,7 +1237,7 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1161
1237
|
actualReasoningEffort: event.actualReasoningEffort,
|
|
1162
1238
|
modelFallbackReason: event.modelFallbackReason,
|
|
1163
1239
|
reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
|
|
1164
|
-
}, options);
|
|
1240
|
+
}, { runId: receipt.runId, receiptId: receipt.id }, options, beforeSeal);
|
|
1165
1241
|
} catch (error) {
|
|
1166
1242
|
return pauseOrchestration(
|
|
1167
1243
|
matched.taskId,
|
|
@@ -1173,68 +1249,6 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1173
1249
|
}
|
|
1174
1250
|
}
|
|
1175
1251
|
|
|
1176
|
-
function sealMatchingOrchestrationDelegationWithHostEvidence(
|
|
1177
|
-
client: AgentClientId,
|
|
1178
|
-
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
1179
|
-
consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
|
|
1180
|
-
stopRevision: number;
|
|
1181
|
-
consumer: string;
|
|
1182
|
-
consumedAt: string;
|
|
1183
|
-
}>,
|
|
1184
|
-
options: OrchestrationOptions = {}
|
|
1185
|
-
): OrchestrationResult {
|
|
1186
|
-
const role = managedDelegationRole(event.nativeAgent);
|
|
1187
|
-
if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
1188
|
-
const matched = uniqueMatchingDelegation(
|
|
1189
|
-
client,
|
|
1190
|
-
(receipt) => (
|
|
1191
|
-
(receipt.status === 'stage-completed' || receipt.status === 'sealed')
|
|
1192
|
-
&& receipt.childId === event.childId
|
|
1193
|
-
),
|
|
1194
|
-
options
|
|
1195
|
-
);
|
|
1196
|
-
if ('status' in matched) return matched;
|
|
1197
|
-
const receipt = matched.run.pendingDelegation!;
|
|
1198
|
-
if (receipt.status === 'sealed') {
|
|
1199
|
-
return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
|
|
1200
|
-
}
|
|
1201
|
-
if (receipt.role !== role) {
|
|
1202
|
-
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
1203
|
-
}
|
|
1204
|
-
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1205
|
-
try {
|
|
1206
|
-
const gitRoot = gitRootFor(repoRoot, options);
|
|
1207
|
-
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
1208
|
-
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1209
|
-
gitRoot,
|
|
1210
|
-
stateRoot: repoRoot,
|
|
1211
|
-
taskId: snapshotTaskId
|
|
1212
|
-
});
|
|
1213
|
-
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1214
|
-
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1215
|
-
const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
|
|
1216
|
-
if (!validated.ok) {
|
|
1217
|
-
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1218
|
-
}
|
|
1219
|
-
const hostEvidence = consumeEvidence(receipt);
|
|
1220
|
-
const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
|
|
1221
|
-
if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
|
|
1222
|
-
const resolved = resolveTaskRef(matched.taskId, { repoRoot });
|
|
1223
|
-
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1224
|
-
const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
|
|
1225
|
-
saveRun(resolved.taskDir, updated);
|
|
1226
|
-
return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
return pauseOrchestration(
|
|
1229
|
-
matched.taskId,
|
|
1230
|
-
'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
|
|
1231
|
-
error instanceof Error ? error.message : String(error),
|
|
1232
|
-
true,
|
|
1233
|
-
options
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
1252
|
function reconcileMatchingOrchestrationDelegation(
|
|
1239
1253
|
client: AgentClientId,
|
|
1240
1254
|
childId: string,
|
|
@@ -1247,15 +1261,11 @@ function reconcileMatchingOrchestrationDelegation(
|
|
|
1247
1261
|
);
|
|
1248
1262
|
if ('status' in matched) return matched;
|
|
1249
1263
|
const receipt = matched.run.pendingDelegation!;
|
|
1250
|
-
if (
|
|
1251
|
-
receipt.status !== 'sealed'
|
|
1252
|
-
|| receipt.hostEvidence?.consumer !== receipt.id
|
|
1253
|
-
|| receipt.hostEvidence.consumedAt === null
|
|
1254
|
-
) {
|
|
1264
|
+
if (receipt.status !== 'sealed' || receipt.childId !== childId) {
|
|
1255
1265
|
return pauseOrchestration(
|
|
1256
1266
|
matched.taskId,
|
|
1257
|
-
'
|
|
1258
|
-
'
|
|
1267
|
+
'ORCHESTRATION_DELEGATION_RECONCILIATION_FAILED',
|
|
1268
|
+
'native child did not produce one sealed delegation receipt',
|
|
1259
1269
|
true,
|
|
1260
1270
|
options
|
|
1261
1271
|
);
|
|
@@ -1275,9 +1285,21 @@ function activateOrchestrationDelegation(
|
|
|
1275
1285
|
if (run.status !== 'running') {
|
|
1276
1286
|
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1277
1287
|
}
|
|
1288
|
+
const validateAdapterEvidence = getAgentClientAdapter(run.pendingDelegation.client)
|
|
1289
|
+
.orchestrationAdapter?.validateActivationEvidence;
|
|
1290
|
+
if (validateAdapterEvidence && !validateAdapterEvidence(event.clientEvidence, event.parentId)) {
|
|
1291
|
+
return pauseOrchestration(
|
|
1292
|
+
taskRef,
|
|
1293
|
+
'DELEGATION_ADAPTER_EVIDENCE_REQUIRED',
|
|
1294
|
+
'native activation lacks valid evidence from the agent adapter',
|
|
1295
|
+
true,
|
|
1296
|
+
options
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1278
1299
|
const result = activateDelegation(run.pendingDelegation, event, {
|
|
1279
1300
|
now: options.now,
|
|
1280
|
-
monotonicNow: options.monotonicNow
|
|
1301
|
+
monotonicNow: options.monotonicNow,
|
|
1302
|
+
evidencePolicy: getAgentClientDelegationEvidence(run.pendingDelegation.client)
|
|
1281
1303
|
});
|
|
1282
1304
|
if (!result.ok) {
|
|
1283
1305
|
if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
@@ -1380,22 +1402,12 @@ function recoverPreparedOrchestrationDelegation(
|
|
|
1380
1402
|
) {
|
|
1381
1403
|
return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
|
|
1382
1404
|
}
|
|
1383
|
-
const
|
|
1384
|
-
?? (
|
|
1385
|
-
|
|
1386
|
-
&& receipt.lifecycleProvenance
|
|
1387
|
-
&& hasActiveCodexLifecycleEvidence(
|
|
1388
|
-
resolveAgentRuntimeStoreRoot({ repoRoot: resolved.repoRoot, store: 'lifecycle' }),
|
|
1389
|
-
{
|
|
1390
|
-
nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
|
|
1391
|
-
hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
|
|
1392
|
-
}
|
|
1393
|
-
)
|
|
1394
|
-
);
|
|
1395
|
-
if (activeLifecycleEvidence) {
|
|
1405
|
+
const activeAdapterEvidence = options.hasActiveLifecycleEvidence?.(receipt)
|
|
1406
|
+
?? Object.keys(receipt.adapterEvidence ?? {}).length > 0;
|
|
1407
|
+
if (activeAdapterEvidence) {
|
|
1396
1408
|
return failed(
|
|
1397
1409
|
'ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE',
|
|
1398
|
-
'active
|
|
1410
|
+
'active agent adapter evidence blocks prepared receipt recovery',
|
|
1399
1411
|
resolved.taskId
|
|
1400
1412
|
);
|
|
1401
1413
|
}
|
|
@@ -1436,18 +1448,14 @@ type ActivatedRecoveryEvent = Readonly<{
|
|
|
1436
1448
|
artifact: string;
|
|
1437
1449
|
startedAgent: string;
|
|
1438
1450
|
childId: string;
|
|
1439
|
-
|
|
1440
|
-
consumer: string;
|
|
1441
|
-
consumedAt: string;
|
|
1451
|
+
clientEvidence?: unknown;
|
|
1442
1452
|
}>;
|
|
1443
1453
|
|
|
1444
1454
|
function recoveryReceiptMatches(
|
|
1445
1455
|
receipt: DelegationReceipt,
|
|
1446
|
-
taskId: string,
|
|
1447
1456
|
event: ActivatedRecoveryEvent
|
|
1448
1457
|
): boolean {
|
|
1449
|
-
return receipt.
|
|
1450
|
-
&& receipt.stage === event.stage
|
|
1458
|
+
return receipt.stage === event.stage
|
|
1451
1459
|
&& receipt.round === event.round
|
|
1452
1460
|
&& receipt.artifact === event.artifact
|
|
1453
1461
|
&& receipt.childId === event.childId
|
|
@@ -1469,7 +1477,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(
|
|
|
1469
1477
|
];
|
|
1470
1478
|
const receipt = matches.find((candidate) => candidate.id === event.receiptId);
|
|
1471
1479
|
if (!receipt) return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
|
|
1472
|
-
if (!recoveryReceiptMatches(receipt,
|
|
1480
|
+
if (!recoveryReceiptMatches(receipt, event)) {
|
|
1473
1481
|
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
|
|
1474
1482
|
}
|
|
1475
1483
|
if (receipt.status === 'aborted' && run.pendingDelegation === null) {
|
|
@@ -1486,9 +1494,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(
|
|
|
1486
1494
|
}
|
|
1487
1495
|
const aborted = abortActivatedDelegation(receipt, {
|
|
1488
1496
|
childId: event.childId,
|
|
1489
|
-
|
|
1490
|
-
consumer: event.consumer,
|
|
1491
|
-
consumedAt: event.consumedAt
|
|
1497
|
+
clientEvidence: event.clientEvidence
|
|
1492
1498
|
});
|
|
1493
1499
|
if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1494
1500
|
const updated = withUpdatedRun(run, {
|
|
@@ -1585,17 +1591,32 @@ function commitOrchestrationStageCompletion(plan: OrchestrationStageCompletion):
|
|
|
1585
1591
|
function sealOrchestrationDelegation(
|
|
1586
1592
|
taskRef: string,
|
|
1587
1593
|
event: Parameters<typeof sealDelegation>[1],
|
|
1588
|
-
|
|
1594
|
+
expected?: Readonly<{ runId: string; receiptId: string }>,
|
|
1595
|
+
options: OrchestrationOptions = {},
|
|
1596
|
+
beforeSeal?: () => OrchestrationResult | null
|
|
1589
1597
|
): OrchestrationResult {
|
|
1590
|
-
const
|
|
1598
|
+
const orchestrationOptions = options;
|
|
1599
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
|
|
1591
1600
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1601
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.seal', () => {
|
|
1602
|
+
const run = readRun(resolved.taskDir, orchestrationOptions);
|
|
1603
|
+
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1604
|
+
if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
|
|
1605
|
+
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed while collecting completion evidence', resolved.taskId);
|
|
1606
|
+
}
|
|
1607
|
+
const preSealFailure = beforeSeal?.();
|
|
1608
|
+
if (preSealFailure) return preSealFailure;
|
|
1609
|
+
const current = beforeSeal ? readRun(resolved.taskDir, orchestrationOptions) : run;
|
|
1610
|
+
if (!current?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1611
|
+
if (expected && (current.runId !== expected.runId || current.pendingDelegation.id !== expected.receiptId)) {
|
|
1612
|
+
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed before it was sealed', resolved.taskId);
|
|
1613
|
+
}
|
|
1614
|
+
const result = sealDelegation(current.pendingDelegation, event, { now: orchestrationOptions.now });
|
|
1615
|
+
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, orchestrationOptions);
|
|
1616
|
+
const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
|
|
1617
|
+
saveRun(resolved.taskDir, updated);
|
|
1618
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1619
|
+
});
|
|
1599
1620
|
}
|
|
1600
1621
|
|
|
1601
1622
|
function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
@@ -1649,6 +1670,9 @@ export {
|
|
|
1649
1670
|
hasSealableOrchestrationDelegation,
|
|
1650
1671
|
OrchestrationStateError,
|
|
1651
1672
|
orchestrationPath,
|
|
1673
|
+
readDelegationAdapterEvidence,
|
|
1674
|
+
mutateDelegationAdapterEvidence,
|
|
1675
|
+
mutateDelegationAdapterEvidenceWithinTaskLock,
|
|
1652
1676
|
pauseMatchingOrchestrationDelegation,
|
|
1653
1677
|
pauseOrchestration,
|
|
1654
1678
|
planOrchestrationStageCompletion,
|
|
@@ -1659,7 +1683,6 @@ export {
|
|
|
1659
1683
|
recoverActivatedOrchestrationDelegationUnderLock,
|
|
1660
1684
|
routeOrchestration,
|
|
1661
1685
|
sealMatchingOrchestrationDelegation,
|
|
1662
|
-
sealMatchingOrchestrationDelegationWithHostEvidence,
|
|
1663
1686
|
sealOrchestrationDelegation,
|
|
1664
1687
|
statusOrchestration,
|
|
1665
1688
|
};
|