@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
|
@@ -6,16 +6,14 @@ import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
|
6
6
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
7
7
|
import { parseReviewSummary } from './review-artifacts.js';
|
|
8
8
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
9
|
-
import { resolveTaskRef } from './resolve-ref.js';
|
|
9
|
+
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
10
10
|
import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
11
11
|
import { normalizeAgentClients } from '../agent-clients/config.js';
|
|
12
|
-
import {
|
|
13
|
-
import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
12
|
+
import { getAgentClientAdapter, getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
14
13
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
15
14
|
import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
16
15
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
|
|
17
16
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
18
|
-
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
|
|
19
17
|
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.js';
|
|
20
18
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
21
19
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
@@ -29,7 +27,7 @@ function supportsLifecycleDelegation(client) {
|
|
|
29
27
|
&& getAgentClientDelegationEvidence(client).actualReasoningEffort !== 'unavailable';
|
|
30
28
|
}
|
|
31
29
|
function orchestrationPath(taskDir) {
|
|
32
|
-
return path.join(taskDir, 'orchestration.json');
|
|
30
|
+
return path.join(taskDir, '.runtime', 'orchestration.json');
|
|
33
31
|
}
|
|
34
32
|
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';
|
|
35
33
|
class OrchestrationStateError extends Error {
|
|
@@ -79,7 +77,7 @@ function isModelPolicy(value) {
|
|
|
79
77
|
}
|
|
80
78
|
const RECOVERY_GUARD_KEYS = [
|
|
81
79
|
'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
|
|
82
|
-
'
|
|
80
|
+
'completionEvidenceAbsent'
|
|
83
81
|
];
|
|
84
82
|
function isRecovery(value) {
|
|
85
83
|
if (!hasExactKeys(value, [
|
|
@@ -92,14 +90,13 @@ function isRecovery(value) {
|
|
|
92
90
|
&& value.previousStatus === 'paused'
|
|
93
91
|
&& isPause(value.previousPause)
|
|
94
92
|
&& value.previousPause.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
95
|
-
&& value.client
|
|
93
|
+
&& isAgentClientId(value.client)
|
|
96
94
|
&& hasExactKeys(guards, RECOVERY_GUARD_KEYS)
|
|
97
95
|
&& guards.stepCount === 0
|
|
98
96
|
&& guards.nextStage === null
|
|
99
97
|
&& guards.baselineEmpty === true
|
|
100
98
|
&& guards.receiptCount === 0
|
|
101
99
|
&& guards.pendingDelegation === false
|
|
102
|
-
&& guards.commitAuthorizationUnused === true
|
|
103
100
|
&& guards.completionEvidenceAbsent === true
|
|
104
101
|
&& value.resultingStatus === 'running';
|
|
105
102
|
}
|
|
@@ -119,17 +116,15 @@ export function isCompletionEvidence(value) {
|
|
|
119
116
|
const ORCHESTRATION_RUN_KEYS = [
|
|
120
117
|
'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
|
|
121
118
|
'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
|
|
122
|
-
'pause', '
|
|
119
|
+
'pause', 'completionEvidence', 'createdAt', 'updatedAt'
|
|
123
120
|
];
|
|
124
|
-
function orchestrationStateReasonCodes(value
|
|
121
|
+
function orchestrationStateReasonCodes(value) {
|
|
125
122
|
if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS))
|
|
126
123
|
return ['top-level-keys'];
|
|
127
124
|
const record = value;
|
|
128
125
|
const reasons = [];
|
|
129
126
|
if (!exactText(record.taskId))
|
|
130
127
|
reasons.push('taskId');
|
|
131
|
-
else if (expectedTaskId !== undefined && record.taskId !== expectedTaskId)
|
|
132
|
-
reasons.push('taskId-mismatch');
|
|
133
128
|
if (!exactText(record.runId))
|
|
134
129
|
reasons.push('runId');
|
|
135
130
|
if (!['running', 'paused', 'completed'].includes(record.status))
|
|
@@ -155,15 +150,6 @@ function orchestrationStateReasonCodes(value, expectedTaskId) {
|
|
|
155
150
|
reasons.push('receipts');
|
|
156
151
|
if (!(record.pause === null || isPause(record.pause)))
|
|
157
152
|
reasons.push('pause');
|
|
158
|
-
if (!hasExactKeys(record.commitAuthorization, ['issuedAt', 'consumedAt'])) {
|
|
159
|
-
reasons.push('commitAuthorization');
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
if (!nullableText(record.commitAuthorization.issuedAt))
|
|
163
|
-
reasons.push('commitAuthorization.issuedAt');
|
|
164
|
-
if (!nullableText(record.commitAuthorization.consumedAt))
|
|
165
|
-
reasons.push('commitAuthorization.consumedAt');
|
|
166
|
-
}
|
|
167
153
|
if (!(record.completionEvidence === null || isCompletionEvidence(record.completionEvidence)))
|
|
168
154
|
reasons.push('completionEvidence');
|
|
169
155
|
if (!exactText(record.createdAt))
|
|
@@ -173,7 +159,7 @@ function orchestrationStateReasonCodes(value, expectedTaskId) {
|
|
|
173
159
|
if (Array.isArray(record.receipts) && record.receipts.every(isDelegationReceipt)
|
|
174
160
|
&& (record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) {
|
|
175
161
|
const receipts = [...record.receipts, ...(record.pendingDelegation ? [record.pendingDelegation] : [])];
|
|
176
|
-
if (receipts.some((receipt) => receipt.
|
|
162
|
+
if (receipts.some((receipt) => receipt.runId !== record.runId)) {
|
|
177
163
|
reasons.push('receipt-identity');
|
|
178
164
|
}
|
|
179
165
|
}
|
|
@@ -223,7 +209,7 @@ function fileObservation(file) {
|
|
|
223
209
|
}
|
|
224
210
|
}
|
|
225
211
|
function parseOrchestrationRun(value, expectedTaskId) {
|
|
226
|
-
const reasonCodes = orchestrationStateReasonCodes(value
|
|
212
|
+
const reasonCodes = orchestrationStateReasonCodes(value);
|
|
227
213
|
if (reasonCodes.length > 0)
|
|
228
214
|
throw new OrchestrationStateError(expectedTaskId ?? null, reasonCodes);
|
|
229
215
|
return value;
|
|
@@ -268,7 +254,77 @@ function readRun(taskDir, options = {}) {
|
|
|
268
254
|
throw new OrchestrationStateError(taskId, reasonCodes);
|
|
269
255
|
}
|
|
270
256
|
}
|
|
257
|
+
function findDelegation(run, receiptId) {
|
|
258
|
+
if (run.pendingDelegation?.id === receiptId)
|
|
259
|
+
return { receipt: run.pendingDelegation, location: 'pending' };
|
|
260
|
+
const receipt = run.receipts.find((entry) => entry.id === receiptId);
|
|
261
|
+
return receipt ? { receipt, location: 'history' } : null;
|
|
262
|
+
}
|
|
263
|
+
function readDelegationAdapterEvidence(taskRef, receiptId, adapterId, options = {}) {
|
|
264
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
265
|
+
if (!resolved.ok)
|
|
266
|
+
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
267
|
+
const run = readRun(resolved.taskDir, options);
|
|
268
|
+
const found = run && findDelegation(run, receiptId);
|
|
269
|
+
if (!found || found.receipt.client !== adapterId)
|
|
270
|
+
return null;
|
|
271
|
+
return found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
272
|
+
}
|
|
273
|
+
function mutateDelegationAdapterEvidence(taskRef, receiptId, adapterId, mutate, options = {}) {
|
|
274
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
275
|
+
if (!resolved.ok)
|
|
276
|
+
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
277
|
+
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, false);
|
|
278
|
+
}
|
|
279
|
+
function mutateDelegationAdapterEvidenceWithinTaskLock(taskRef, receiptId, adapterId, mutate, options = {}) {
|
|
280
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
281
|
+
if (!resolved.ok)
|
|
282
|
+
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
283
|
+
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, true);
|
|
284
|
+
}
|
|
285
|
+
function mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options, taskLockHeld) {
|
|
286
|
+
const update = () => {
|
|
287
|
+
try {
|
|
288
|
+
const run = readRun(resolved.taskDir, options);
|
|
289
|
+
const found = run && findDelegation(run, receiptId);
|
|
290
|
+
if (!run || !found || found.receipt.client !== adapterId) {
|
|
291
|
+
throw new Error('ORCHESTRATION_DELEGATION_MISSING: adapter evidence has no matching task receipt');
|
|
292
|
+
}
|
|
293
|
+
const current = found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
294
|
+
const nextState = mutate(current);
|
|
295
|
+
const serialized = JSON.stringify(nextState);
|
|
296
|
+
if (serialized === undefined)
|
|
297
|
+
throw new Error('Agent adapter evidence must be JSON serializable');
|
|
298
|
+
if (serialized === JSON.stringify(current))
|
|
299
|
+
return JSON.parse(serialized);
|
|
300
|
+
const adapterEvidence = Object.freeze({
|
|
301
|
+
...(found.receipt.adapterEvidence ?? {}),
|
|
302
|
+
[adapterId]: JSON.parse(serialized)
|
|
303
|
+
});
|
|
304
|
+
const nextReceipt = Object.freeze({ ...found.receipt, adapterEvidence });
|
|
305
|
+
const updated = found.location === 'pending'
|
|
306
|
+
? withUpdatedRun(run, { pendingDelegation: nextReceipt }, options.now)
|
|
307
|
+
: withUpdatedRun(run, {
|
|
308
|
+
receipts: Object.freeze(run.receipts.map((receipt) => receipt.id === receiptId ? nextReceipt : receipt))
|
|
309
|
+
}, options.now);
|
|
310
|
+
saveRun(resolved.taskDir, updated);
|
|
311
|
+
return JSON.parse(serialized);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
mutationError = error;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
let mutationError;
|
|
319
|
+
const result = taskLockHeld
|
|
320
|
+
? update()
|
|
321
|
+
: withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `agent-adapter.${adapterId}.evidence`, update);
|
|
322
|
+
if (mutationError)
|
|
323
|
+
throw mutationError;
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
271
326
|
function atomicWrite(file, value) {
|
|
327
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
272
328
|
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
273
329
|
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
274
330
|
fs.renameSync(temp, file);
|
|
@@ -316,17 +372,16 @@ function resolveProjectPolicy(repoRoot, client) {
|
|
|
316
372
|
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
317
373
|
return normalizeAgentClients(raw).state[client].orchestration;
|
|
318
374
|
}
|
|
319
|
-
function
|
|
375
|
+
function canRecoverUnsupportedClientPause(run) {
|
|
320
376
|
return run.status === 'paused'
|
|
321
377
|
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
322
|
-
&& run.modelPolicySource?.client
|
|
378
|
+
&& isAgentClientId(run.modelPolicySource?.client)
|
|
379
|
+
&& supportsLifecycleDelegation(run.modelPolicySource.client)
|
|
323
380
|
&& run.stepCount === 0
|
|
324
381
|
&& run.nextStage === null
|
|
325
382
|
&& run.baseline === ''
|
|
326
383
|
&& run.pendingDelegation === null
|
|
327
384
|
&& run.receipts.length === 0
|
|
328
|
-
&& run.commitAuthorization?.issuedAt === null
|
|
329
|
-
&& run.commitAuthorization?.consumedAt === null
|
|
330
385
|
&& run.completionEvidence == null
|
|
331
386
|
&& run.recoveryHistory.length === 0;
|
|
332
387
|
}
|
|
@@ -360,8 +415,7 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
360
415
|
}
|
|
361
416
|
}
|
|
362
417
|
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
363
|
-
if (existing
|
|
364
|
-
|| !canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
|
|
418
|
+
if (!canRecoverUnsupportedClientPause(existing)) {
|
|
365
419
|
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
366
420
|
}
|
|
367
421
|
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
@@ -370,14 +424,13 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
370
424
|
recoveredAt: now,
|
|
371
425
|
previousStatus: 'paused',
|
|
372
426
|
previousPause: existing.pause,
|
|
373
|
-
client:
|
|
427
|
+
client: existing.modelPolicySource.client,
|
|
374
428
|
guards: {
|
|
375
429
|
stepCount: 0,
|
|
376
430
|
nextStage: null,
|
|
377
431
|
baselineEmpty: true,
|
|
378
432
|
receiptCount: 0,
|
|
379
433
|
pendingDelegation: false,
|
|
380
|
-
commitAuthorizationUnused: true,
|
|
381
434
|
completionEvidenceAbsent: true
|
|
382
435
|
},
|
|
383
436
|
resultingStatus: 'running'
|
|
@@ -431,7 +484,6 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
|
431
484
|
pendingDelegation: null,
|
|
432
485
|
receipts: [],
|
|
433
486
|
pause: null,
|
|
434
|
-
commitAuthorization: { issuedAt: null, consumedAt: null },
|
|
435
487
|
completionEvidence: null,
|
|
436
488
|
createdAt: now,
|
|
437
489
|
updatedAt: now
|
|
@@ -657,6 +709,40 @@ function statusOrchestration(taskRef, options = {}) {
|
|
|
657
709
|
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
658
710
|
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
659
711
|
}
|
|
712
|
+
function validatePreparedModelPolicy(taskId, run, next, input) {
|
|
713
|
+
if (!validModel(input.requestedModel)) {
|
|
714
|
+
return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', taskId);
|
|
715
|
+
}
|
|
716
|
+
if (!validModel(input.requestedReasoningEffort)) {
|
|
717
|
+
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', taskId);
|
|
718
|
+
}
|
|
719
|
+
const expectedPolicy = run.modelPolicy[next.role];
|
|
720
|
+
if (input.requestedModel !== expectedPolicy.model) {
|
|
721
|
+
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, taskId);
|
|
722
|
+
}
|
|
723
|
+
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
724
|
+
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, taskId);
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
function captureTaskSnapshot(taskId, repoRoot, options) {
|
|
729
|
+
return (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
730
|
+
gitRoot: gitRootFor(repoRoot, options),
|
|
731
|
+
stateRoot: repoRoot,
|
|
732
|
+
taskId
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function validateDelegationRun(taskRef, taskId, run, options) {
|
|
736
|
+
if (!run || run.status !== 'running') {
|
|
737
|
+
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', taskId);
|
|
738
|
+
}
|
|
739
|
+
if (run.pendingDelegation)
|
|
740
|
+
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', taskId);
|
|
741
|
+
if (run.stepCount >= run.maxSteps) {
|
|
742
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
660
746
|
function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
661
747
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
662
748
|
if (!resolved.ok)
|
|
@@ -673,47 +759,27 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
673
759
|
requestedReasoningEffort: input.requestedReasoningEffort ?? null
|
|
674
760
|
});
|
|
675
761
|
const run = readRun(resolved.taskDir, options);
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const repositoryPending = matchingDelegations(() => true, options);
|
|
681
|
-
if (repositoryPending.length > 0) {
|
|
682
|
-
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
683
|
-
}
|
|
684
|
-
if (run.stepCount >= run.maxSteps)
|
|
685
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
762
|
+
const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
|
|
763
|
+
if (runFailure)
|
|
764
|
+
return runFailure;
|
|
765
|
+
const activeRun = run;
|
|
686
766
|
const routed = routeOrchestration(taskRef, options);
|
|
687
767
|
if (!routed.next)
|
|
688
768
|
return routed;
|
|
689
769
|
const next = routed.next;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (!validModel(input.requestedReasoningEffort)) {
|
|
694
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
695
|
-
}
|
|
696
|
-
const expectedPolicy = run.modelPolicy[next.role];
|
|
697
|
-
if (input.requestedModel !== expectedPolicy.model) {
|
|
698
|
-
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
699
|
-
}
|
|
700
|
-
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
701
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
702
|
-
}
|
|
770
|
+
const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
|
|
771
|
+
if (policyFailure)
|
|
772
|
+
return policyFailure;
|
|
703
773
|
let beforeFingerprint;
|
|
704
774
|
try {
|
|
705
|
-
beforeFingerprint = (
|
|
706
|
-
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
707
|
-
stateRoot: resolved.repoRoot,
|
|
708
|
-
taskId: resolved.taskId
|
|
709
|
-
});
|
|
775
|
+
beforeFingerprint = captureTaskSnapshot(resolved.taskId, resolved.repoRoot, options);
|
|
710
776
|
}
|
|
711
777
|
catch (error) {
|
|
712
778
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
713
779
|
}
|
|
714
780
|
const receipt = prepareDelegation({
|
|
715
781
|
taskId: resolved.taskId,
|
|
716
|
-
runId:
|
|
782
|
+
runId: activeRun.runId,
|
|
717
783
|
role: next.role,
|
|
718
784
|
stage: next.stage,
|
|
719
785
|
round: next.round,
|
|
@@ -722,10 +788,9 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
722
788
|
requestedModel: input.requestedModel,
|
|
723
789
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
724
790
|
workspaceSnapshotScope: 'task',
|
|
725
|
-
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
726
791
|
beforeFingerprint
|
|
727
792
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
728
|
-
const updated = withUpdatedRun(
|
|
793
|
+
const updated = withUpdatedRun(activeRun, {
|
|
729
794
|
nextStage: next.stage,
|
|
730
795
|
pendingDelegation: receipt
|
|
731
796
|
});
|
|
@@ -737,7 +802,7 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
|
737
802
|
if (!resolved.ok)
|
|
738
803
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
739
804
|
try {
|
|
740
|
-
return withTaskExecutionLock(resolved.repoRoot,
|
|
805
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare.task', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options));
|
|
741
806
|
}
|
|
742
807
|
catch (error) {
|
|
743
808
|
if (error instanceof TaskExecutionLockError)
|
|
@@ -770,7 +835,7 @@ function dispatchOrchestrationDelegation(taskRef, options = {}) {
|
|
|
770
835
|
if (!resolved.ok)
|
|
771
836
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
772
837
|
try {
|
|
773
|
-
return withTaskExecutionLock(resolved.repoRoot,
|
|
838
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.dispatch.task', () => dispatchOrchestrationDelegationUnlocked(taskRef, options));
|
|
774
839
|
}
|
|
775
840
|
catch (error) {
|
|
776
841
|
if (error instanceof TaskExecutionLockError)
|
|
@@ -779,65 +844,40 @@ function dispatchOrchestrationDelegation(taskRef, options = {}) {
|
|
|
779
844
|
}
|
|
780
845
|
}
|
|
781
846
|
function matchingDelegations(predicate, options) {
|
|
847
|
+
const taskId = options.taskId;
|
|
848
|
+
if (taskId === undefined || taskId === '') {
|
|
849
|
+
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_REQUIRED', 'taskId is required for lifecycle matching') };
|
|
850
|
+
}
|
|
851
|
+
if (typeof taskId !== 'string' || !TASK_ID_RE.test(taskId)) {
|
|
852
|
+
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
|
|
853
|
+
}
|
|
782
854
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
const matches = fs.readdirSync(activeRoot, { withFileTypes: true })
|
|
790
|
-
.filter((entry) => entry.isDirectory())
|
|
791
|
-
.flatMap((entry) => {
|
|
792
|
-
const candidateTaskId = entry.name;
|
|
793
|
-
const candidateTaskDir = path.join(activeRoot, candidateTaskId);
|
|
794
|
-
emitDiagnostic(options, 'orchestration-active-scan-candidate', {
|
|
795
|
-
candidateTaskId,
|
|
796
|
-
candidateTaskDir,
|
|
797
|
-
orchestrationPath: orchestrationPath(candidateTaskDir)
|
|
798
|
-
});
|
|
799
|
-
let run;
|
|
800
|
-
try {
|
|
801
|
-
run = readRun(candidateTaskDir, options);
|
|
802
|
-
}
|
|
803
|
-
catch (error) {
|
|
804
|
-
emitDiagnostic(options, 'orchestration-active-scan-candidate-failed', {
|
|
805
|
-
candidateTaskId,
|
|
806
|
-
candidateTaskDir,
|
|
807
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
808
|
-
reasonCodes: error instanceof OrchestrationStateError ? error.reasonCodes.join(',') : null
|
|
809
|
-
});
|
|
810
|
-
throw error;
|
|
811
|
-
}
|
|
812
|
-
if (!run || run.status !== 'running' || !run.pendingDelegation)
|
|
813
|
-
return [];
|
|
814
|
-
const receipt = run.pendingDelegation;
|
|
815
|
-
return predicate(receipt)
|
|
816
|
-
? [{ taskId: candidateTaskId, run }]
|
|
817
|
-
: [];
|
|
818
|
-
});
|
|
819
|
-
emitDiagnostic(options, 'orchestration-active-scan-finished', {
|
|
820
|
-
activeRoot,
|
|
821
|
-
matchingDelegations: matches.length
|
|
822
|
-
});
|
|
823
|
-
return matches;
|
|
855
|
+
const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
|
|
856
|
+
const run = readRun(taskDir, options);
|
|
857
|
+
const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
|
|
858
|
+
? [{ taskId, run }]
|
|
859
|
+
: [];
|
|
860
|
+
return { ok: true, matches };
|
|
824
861
|
}
|
|
825
862
|
function uniqueMatchingDelegation(client, predicate, options) {
|
|
826
|
-
const
|
|
863
|
+
const lookup = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
|
|
864
|
+
if (!lookup.ok)
|
|
865
|
+
return lookup.error;
|
|
866
|
+
const matches = lookup.matches;
|
|
827
867
|
if (matches.length === 0)
|
|
828
868
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
829
|
-
if (matches.length > 1)
|
|
830
|
-
return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
831
869
|
return matches[0];
|
|
832
870
|
}
|
|
833
871
|
function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
|
|
834
|
-
|
|
835
|
-
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options)
|
|
872
|
+
const lookup = matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
|
|
873
|
+
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options);
|
|
874
|
+
return lookup.ok && lookup.matches.length > 0;
|
|
836
875
|
}
|
|
837
876
|
function hasSealableOrchestrationDelegation(client, childId, options = {}) {
|
|
838
|
-
|
|
877
|
+
const lookup = matchingDelegations((receipt) => receipt.client === client
|
|
839
878
|
&& receipt.childId === childId
|
|
840
|
-
&& ['stage-completed', 'sealed'].includes(receipt.status), options)
|
|
879
|
+
&& ['stage-completed', 'sealed'].includes(receipt.status), options);
|
|
880
|
+
return lookup.ok && lookup.matches.length > 0;
|
|
841
881
|
}
|
|
842
882
|
function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
843
883
|
const role = managedDelegationRole(event.nativeAgent);
|
|
@@ -857,7 +897,8 @@ function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
|
857
897
|
&& (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
|
|
858
898
|
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
859
899
|
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
860
|
-
&&
|
|
900
|
+
&& (event.clientEvidence === undefined
|
|
901
|
+
|| JSON.stringify(receipt.adapterEvidence?.[client]) === JSON.stringify(event.clientEvidence));
|
|
861
902
|
if (!sameEvidence) {
|
|
862
903
|
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
863
904
|
}
|
|
@@ -874,7 +915,7 @@ function pauseMatchingOrchestrationDelegation(client, code, message, options = {
|
|
|
874
915
|
return matched;
|
|
875
916
|
return pauseOrchestration(matched.taskId, code, message, true, options);
|
|
876
917
|
}
|
|
877
|
-
function sealMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
918
|
+
function sealMatchingOrchestrationDelegation(client, event, options = {}, beforeSeal) {
|
|
878
919
|
const role = managedDelegationRole(event.nativeAgent);
|
|
879
920
|
if (!role)
|
|
880
921
|
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
@@ -904,66 +945,19 @@ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
|
904
945
|
actualReasoningEffort: event.actualReasoningEffort,
|
|
905
946
|
modelFallbackReason: event.modelFallbackReason,
|
|
906
947
|
reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
|
|
907
|
-
}, options);
|
|
948
|
+
}, { runId: receipt.runId, receiptId: receipt.id }, options, beforeSeal);
|
|
908
949
|
}
|
|
909
950
|
catch (error) {
|
|
910
951
|
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), true, options);
|
|
911
952
|
}
|
|
912
953
|
}
|
|
913
|
-
function sealMatchingOrchestrationDelegationWithHostEvidence(client, event, consumeEvidence, options = {}) {
|
|
914
|
-
const role = managedDelegationRole(event.nativeAgent);
|
|
915
|
-
if (!role)
|
|
916
|
-
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
917
|
-
const matched = uniqueMatchingDelegation(client, (receipt) => ((receipt.status === 'stage-completed' || receipt.status === 'sealed')
|
|
918
|
-
&& receipt.childId === event.childId), options);
|
|
919
|
-
if ('status' in matched)
|
|
920
|
-
return matched;
|
|
921
|
-
const receipt = matched.run.pendingDelegation;
|
|
922
|
-
if (receipt.status === 'sealed') {
|
|
923
|
-
return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
|
|
924
|
-
}
|
|
925
|
-
if (receipt.role !== role) {
|
|
926
|
-
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
927
|
-
}
|
|
928
|
-
const repoRoot = options.repoRoot ?? process.cwd();
|
|
929
|
-
try {
|
|
930
|
-
const gitRoot = gitRootFor(repoRoot, options);
|
|
931
|
-
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
932
|
-
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
933
|
-
gitRoot,
|
|
934
|
-
stateRoot: repoRoot,
|
|
935
|
-
taskId: snapshotTaskId
|
|
936
|
-
});
|
|
937
|
-
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
938
|
-
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
939
|
-
const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
|
|
940
|
-
if (!validated.ok) {
|
|
941
|
-
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
942
|
-
}
|
|
943
|
-
const hostEvidence = consumeEvidence(receipt);
|
|
944
|
-
const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
|
|
945
|
-
if (!sealed.ok)
|
|
946
|
-
return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
|
|
947
|
-
const resolved = resolveTaskRef(matched.taskId, { repoRoot });
|
|
948
|
-
if (!resolved.ok)
|
|
949
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
950
|
-
const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
|
|
951
|
-
saveRun(resolved.taskDir, updated);
|
|
952
|
-
return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
|
|
953
|
-
}
|
|
954
|
-
catch (error) {
|
|
955
|
-
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_CODEX_EVIDENCE_FAILED', error instanceof Error ? error.message : String(error), true, options);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
954
|
function reconcileMatchingOrchestrationDelegation(client, childId, options = {}) {
|
|
959
955
|
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.childId === childId, options);
|
|
960
956
|
if ('status' in matched)
|
|
961
957
|
return matched;
|
|
962
958
|
const receipt = matched.run.pendingDelegation;
|
|
963
|
-
if (receipt.status !== 'sealed'
|
|
964
|
-
|
|
965
|
-
|| receipt.hostEvidence.consumedAt === null) {
|
|
966
|
-
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_CODEX_RECONCILIATION_FAILED', 'Codex native spawn did not produce one sealed receipt with consumed host evidence', true, options);
|
|
959
|
+
if (receipt.status !== 'sealed' || receipt.childId !== childId) {
|
|
960
|
+
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_DELEGATION_RECONCILIATION_FAILED', 'native child did not produce one sealed delegation receipt', true, options);
|
|
967
961
|
}
|
|
968
962
|
return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
|
|
969
963
|
}
|
|
@@ -977,9 +971,15 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
|
977
971
|
if (run.status !== 'running') {
|
|
978
972
|
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
979
973
|
}
|
|
974
|
+
const validateAdapterEvidence = getAgentClientAdapter(run.pendingDelegation.client)
|
|
975
|
+
.orchestrationAdapter?.validateActivationEvidence;
|
|
976
|
+
if (validateAdapterEvidence && !validateAdapterEvidence(event.clientEvidence, event.parentId)) {
|
|
977
|
+
return pauseOrchestration(taskRef, 'DELEGATION_ADAPTER_EVIDENCE_REQUIRED', 'native activation lacks valid evidence from the agent adapter', true, options);
|
|
978
|
+
}
|
|
980
979
|
const result = activateDelegation(run.pendingDelegation, event, {
|
|
981
980
|
now: options.now,
|
|
982
|
-
monotonicNow: options.monotonicNow
|
|
981
|
+
monotonicNow: options.monotonicNow,
|
|
982
|
+
evidencePolicy: getAgentClientDelegationEvidence(run.pendingDelegation.client)
|
|
983
983
|
});
|
|
984
984
|
if (!result.ok) {
|
|
985
985
|
if (result.code === 'DELEGATION_IGNORED')
|
|
@@ -1048,15 +1048,10 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
|
|
|
1048
1048
|
&& wallNow <= Date.parse(receipt.activationDeadlineAt)) {
|
|
1049
1049
|
return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
|
|
1050
1050
|
}
|
|
1051
|
-
const
|
|
1052
|
-
?? (receipt.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
|
|
1056
|
-
hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
|
|
1057
|
-
}));
|
|
1058
|
-
if (activeLifecycleEvidence) {
|
|
1059
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active Codex lifecycle evidence blocks prepared receipt recovery', resolved.taskId);
|
|
1051
|
+
const activeAdapterEvidence = options.hasActiveLifecycleEvidence?.(receipt)
|
|
1052
|
+
?? Object.keys(receipt.adapterEvidence ?? {}).length > 0;
|
|
1053
|
+
if (activeAdapterEvidence) {
|
|
1054
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active agent adapter evidence blocks prepared receipt recovery', resolved.taskId);
|
|
1060
1055
|
}
|
|
1061
1056
|
const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1062
1057
|
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
@@ -1086,9 +1081,8 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
|
|
|
1086
1081
|
return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1087
1082
|
}
|
|
1088
1083
|
}
|
|
1089
|
-
function recoveryReceiptMatches(receipt,
|
|
1090
|
-
return receipt.
|
|
1091
|
-
&& receipt.stage === event.stage
|
|
1084
|
+
function recoveryReceiptMatches(receipt, event) {
|
|
1085
|
+
return receipt.stage === event.stage
|
|
1092
1086
|
&& receipt.round === event.round
|
|
1093
1087
|
&& receipt.artifact === event.artifact
|
|
1094
1088
|
&& receipt.childId === event.childId
|
|
@@ -1108,7 +1102,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
|
|
|
1108
1102
|
const receipt = matches.find((candidate) => candidate.id === event.receiptId);
|
|
1109
1103
|
if (!receipt)
|
|
1110
1104
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
|
|
1111
|
-
if (!recoveryReceiptMatches(receipt,
|
|
1105
|
+
if (!recoveryReceiptMatches(receipt, event)) {
|
|
1112
1106
|
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
|
|
1113
1107
|
}
|
|
1114
1108
|
if (receipt.status === 'aborted' && run.pendingDelegation === null) {
|
|
@@ -1125,9 +1119,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
|
|
|
1125
1119
|
}
|
|
1126
1120
|
const aborted = abortActivatedDelegation(receipt, {
|
|
1127
1121
|
childId: event.childId,
|
|
1128
|
-
|
|
1129
|
-
consumer: event.consumer,
|
|
1130
|
-
consumedAt: event.consumedAt
|
|
1122
|
+
clientEvidence: event.clientEvidence
|
|
1131
1123
|
});
|
|
1132
1124
|
if (!aborted.ok)
|
|
1133
1125
|
return failed(aborted.code, aborted.message, resolved.taskId);
|
|
@@ -1207,19 +1199,34 @@ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
|
|
|
1207
1199
|
function commitOrchestrationStageCompletion(plan) {
|
|
1208
1200
|
saveRun(plan.taskDir, plan.updatedRun);
|
|
1209
1201
|
}
|
|
1210
|
-
function sealOrchestrationDelegation(taskRef, event, options = {}) {
|
|
1211
|
-
const
|
|
1202
|
+
function sealOrchestrationDelegation(taskRef, event, expected, options = {}, beforeSeal) {
|
|
1203
|
+
const orchestrationOptions = options;
|
|
1204
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
|
|
1212
1205
|
if (!resolved.ok)
|
|
1213
1206
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1207
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.seal', () => {
|
|
1208
|
+
const run = readRun(resolved.taskDir, orchestrationOptions);
|
|
1209
|
+
if (!run?.pendingDelegation)
|
|
1210
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1211
|
+
if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
|
|
1212
|
+
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed while collecting completion evidence', resolved.taskId);
|
|
1213
|
+
}
|
|
1214
|
+
const preSealFailure = beforeSeal?.();
|
|
1215
|
+
if (preSealFailure)
|
|
1216
|
+
return preSealFailure;
|
|
1217
|
+
const current = beforeSeal ? readRun(resolved.taskDir, orchestrationOptions) : run;
|
|
1218
|
+
if (!current?.pendingDelegation)
|
|
1219
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1220
|
+
if (expected && (current.runId !== expected.runId || current.pendingDelegation.id !== expected.receiptId)) {
|
|
1221
|
+
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed before it was sealed', resolved.taskId);
|
|
1222
|
+
}
|
|
1223
|
+
const result = sealDelegation(current.pendingDelegation, event, { now: orchestrationOptions.now });
|
|
1224
|
+
if (!result.ok)
|
|
1225
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, orchestrationOptions);
|
|
1226
|
+
const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
|
|
1227
|
+
saveRun(resolved.taskDir, updated);
|
|
1228
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1229
|
+
});
|
|
1223
1230
|
}
|
|
1224
1231
|
function advanceOrchestration(taskRef, options = {}) {
|
|
1225
1232
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1255,5 +1262,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
|
1255
1262
|
saveRun(resolved.taskDir, updated);
|
|
1256
1263
|
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1257
1264
|
}
|
|
1258
|
-
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegationUnderLock, routeOrchestration, sealMatchingOrchestrationDelegation,
|
|
1265
|
+
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, readDelegationAdapterEvidence, mutateDelegationAdapterEvidence, mutateDelegationAdapterEvidenceWithinTaskLock, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegationUnderLock, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, };
|
|
1259
1266
|
//# sourceMappingURL=orchestration.js.map
|