@fitlab-ai/agent-infra 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +134 -123
- package/dist/lib/agent-clients/adapter.js +92 -2
- package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
- package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
- package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
- package/dist/lib/agent-clients/adapters/opencode.js +15 -3
- package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/dist/lib/agent-clients/registry.js +9 -1
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- package/dist/lib/internal/task-ledger.js +12 -3
- package/dist/lib/internal/task-lifecycle.js +19 -1
- package/dist/lib/internal/task-orchestration.js +122 -18
- package/dist/lib/platform/github-client.js +19 -2
- package/dist/lib/platform/pr-checks.js +1 -1
- package/dist/lib/platform/pull-requests.js +47 -1
- package/dist/lib/platform/releases.js +13 -5
- package/dist/lib/process-data/index.js +239 -0
- package/dist/lib/process-data/normalize.js +198 -0
- package/dist/lib/process-data/privacy.js +33 -0
- package/dist/lib/process-data/reconcile.js +95 -0
- package/dist/lib/process-data/repair.js +58 -0
- package/dist/lib/process-data/sources.js +345 -0
- package/dist/lib/process-data/store.js +282 -0
- package/dist/lib/process-data/types.js +2 -0
- package/dist/lib/run/index.js +4 -3
- package/dist/lib/run/tui.js +7 -8
- package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
- package/dist/lib/sandbox/capture.js +7 -2
- package/dist/lib/sandbox/commands/create.js +109 -228
- package/dist/lib/sandbox/commands/enter.js +15 -47
- package/dist/lib/sandbox/commands/list-running.js +16 -6
- package/dist/lib/sandbox/commands/ls.js +10 -6
- package/dist/lib/sandbox/commands/rebuild.js +4 -3
- package/dist/lib/sandbox/commands/refresh.js +4 -84
- package/dist/lib/sandbox/commands/rm.js +24 -3
- package/dist/lib/sandbox/commands/show.js +12 -3
- package/dist/lib/sandbox/commands/start.js +7 -4
- package/dist/lib/sandbox/config.js +2 -0
- package/dist/lib/sandbox/constants.js +6 -0
- package/dist/lib/sandbox/control/client.js +44 -0
- package/dist/lib/sandbox/control/protocol.js +35 -0
- package/dist/lib/sandbox/control/server.js +109 -0
- package/dist/lib/sandbox/dockerfile.js +36 -13
- package/dist/lib/sandbox/mounts.js +17 -4
- package/dist/lib/sandbox/recovery.js +128 -5
- package/dist/lib/sandbox/redaction.js +12 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/dist/lib/sandbox/workspace-identity.js +95 -0
- package/dist/lib/sandbox/workspace-view.js +103 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/commit-finalization.js +267 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/decision-intents.js +31 -17
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/implementation-inputs.js +54 -7
- package/dist/lib/task/ledger-intents.js +35 -1
- package/dist/lib/task/orchestration.js +363 -28
- package/dist/lib/task/resolve-ref.js +41 -1
- package/lib/agent-clients/adapter.ts +126 -2
- package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
- package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
- package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
- package/lib/agent-clients/adapters/claude-code.ts +21 -3
- package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
- package/lib/agent-clients/adapters/opencode.ts +18 -3
- package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
- package/lib/agent-clients/registry.ts +9 -1
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +81 -11
- package/lib/platform/github-client.ts +20 -2
- package/lib/platform/pr-checks.ts +1 -1
- package/lib/platform/pull-requests.ts +49 -1
- package/lib/platform/releases.ts +16 -4
- package/lib/process-data/index.ts +224 -0
- package/lib/process-data/normalize.ts +202 -0
- package/lib/process-data/privacy.ts +41 -0
- package/lib/process-data/reconcile.ts +110 -0
- package/lib/process-data/repair.ts +64 -0
- package/lib/process-data/sources.ts +354 -0
- package/lib/process-data/store.ts +315 -0
- package/lib/process-data/types.ts +135 -0
- package/lib/run/index.ts +5 -3
- package/lib/run/tui.ts +11 -8
- package/lib/sandbox/agent-client-reconciler.ts +44 -2
- package/lib/sandbox/capture.ts +9 -2
- package/lib/sandbox/commands/create.ts +126 -260
- package/lib/sandbox/commands/enter.ts +13 -59
- package/lib/sandbox/commands/list-running.ts +23 -6
- package/lib/sandbox/commands/ls.ts +22 -6
- package/lib/sandbox/commands/rebuild.ts +4 -3
- package/lib/sandbox/commands/refresh.ts +4 -126
- package/lib/sandbox/commands/rm.ts +24 -3
- package/lib/sandbox/commands/show.ts +29 -3
- package/lib/sandbox/commands/start.ts +6 -4
- package/lib/sandbox/config.ts +4 -0
- package/lib/sandbox/constants.ts +8 -0
- package/lib/sandbox/control/client.ts +55 -0
- package/lib/sandbox/control/protocol.ts +75 -0
- package/lib/sandbox/control/server.ts +117 -0
- package/lib/sandbox/dockerfile.ts +66 -13
- package/lib/sandbox/mounts.ts +24 -4
- package/lib/sandbox/recovery.ts +163 -6
- package/lib/sandbox/redaction.ts +14 -0
- package/lib/sandbox/runtimes/base.dockerfile +24 -74
- package/lib/sandbox/tool-types.ts +16 -0
- package/lib/sandbox/workspace-identity.ts +123 -0
- package/lib/sandbox/workspace-view.ts +143 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/commit-finalization.ts +356 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/decision-intents.ts +32 -12
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/implementation-inputs.ts +62 -8
- package/lib/task/ledger-intents.ts +43 -3
- package/lib/task/orchestration.ts +501 -33
- package/lib/task/resolve-ref.ts +46 -1
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +11 -2
- package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
- package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +2 -0
- package/templates/.agents/rules/task-management.zh-CN.md +2 -0
- 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/commit/SKILL.en.md +21 -11
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.claude/rules/tool-preferences.en.md +20 -0
- package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
- package/templates/.opencode/commands/watch-pr.en.md +1 -1
- package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
5
|
|
|
5
6
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
6
7
|
import { parseReviewSummary } from './review-artifacts.ts';
|
|
@@ -36,6 +37,24 @@ import {
|
|
|
36
37
|
} from './workspace-snapshot.ts';
|
|
37
38
|
import type { RepositorySnapshot } from './workspace-snapshot.ts';
|
|
38
39
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
40
|
+
import {
|
|
41
|
+
CommitIntentError,
|
|
42
|
+
commitIntentPath,
|
|
43
|
+
createCommitIntent,
|
|
44
|
+
digest,
|
|
45
|
+
readCommitIntent,
|
|
46
|
+
removeCommitIntent,
|
|
47
|
+
removeCommitIntentByDigest,
|
|
48
|
+
serialize,
|
|
49
|
+
updateCommitIntent
|
|
50
|
+
} from './commit-intent.ts';
|
|
51
|
+
import type { CommitIntent, PushEvidence } from './commit-intent.ts';
|
|
52
|
+
import {
|
|
53
|
+
inspectCommitFinalization,
|
|
54
|
+
planCommitTaskFinalization
|
|
55
|
+
} from './commit-finalization.ts';
|
|
56
|
+
import type { CommitFinalizationInspection } from './commit-finalization.ts';
|
|
57
|
+
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
39
58
|
|
|
40
59
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
41
60
|
type LegacyOrchestrationModelPolicy = Readonly<{
|
|
@@ -144,8 +163,36 @@ type OrchestrationOptions = {
|
|
|
144
163
|
}>;
|
|
145
164
|
diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
|
|
146
165
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
166
|
+
token?: () => string;
|
|
147
167
|
};
|
|
148
168
|
|
|
169
|
+
type CommitIntentResult = Readonly<{
|
|
170
|
+
status: 'ready' | 'failed';
|
|
171
|
+
changed: boolean;
|
|
172
|
+
taskId: string | null;
|
|
173
|
+
intent: Readonly<{
|
|
174
|
+
mode: CommitIntent['mode'];
|
|
175
|
+
phase: CommitIntent['phase'];
|
|
176
|
+
baselineHead: string;
|
|
177
|
+
currentHead: string;
|
|
178
|
+
committedHead: string | null;
|
|
179
|
+
pushEvidence: PushEvidence | null;
|
|
180
|
+
runId: string | null;
|
|
181
|
+
receiptId: string | null;
|
|
182
|
+
}> | null;
|
|
183
|
+
finalization?: Readonly<{
|
|
184
|
+
disposition: CommitFinalizationInspection['disposition'];
|
|
185
|
+
code: CommitFinalizationInspection['code'];
|
|
186
|
+
message: string;
|
|
187
|
+
currentHead: string;
|
|
188
|
+
committedHead: string | null;
|
|
189
|
+
needsAnchor: boolean;
|
|
190
|
+
needsLog: boolean;
|
|
191
|
+
}>;
|
|
192
|
+
token?: string;
|
|
193
|
+
error: Readonly<{ code: string; message: string }> | null;
|
|
194
|
+
}>;
|
|
195
|
+
|
|
149
196
|
function supportsLifecycleDelegation(client: AgentClientId): boolean {
|
|
150
197
|
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
151
198
|
&& getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
|
|
@@ -187,8 +234,8 @@ function saveRun(taskDir: string, run: OrchestrationRun): void {
|
|
|
187
234
|
atomicWrite(orchestrationPath(taskDir), run);
|
|
188
235
|
}
|
|
189
236
|
|
|
190
|
-
function withUpdatedRun(run: OrchestrationRun, updates: Partial<OrchestrationRun
|
|
191
|
-
return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
|
|
237
|
+
function withUpdatedRun(run: OrchestrationRun, updates: Partial<OrchestrationRun>, now?: () => string): OrchestrationRun {
|
|
238
|
+
return Object.freeze({ ...run, ...updates, updatedAt: (now ?? (() => new Date().toISOString()))() });
|
|
192
239
|
}
|
|
193
240
|
|
|
194
241
|
function failed(
|
|
@@ -200,6 +247,73 @@ function failed(
|
|
|
200
247
|
return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
|
|
201
248
|
}
|
|
202
249
|
|
|
250
|
+
function commitIntentFailed(code: string, message: string, taskId: string | null = null): CommitIntentResult {
|
|
251
|
+
return { status: 'failed', changed: false, taskId, intent: null, error: { code, message } };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function repositoryHead(repoRoot: string): string {
|
|
255
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
256
|
+
cwd: repoRoot,
|
|
257
|
+
encoding: 'utf8',
|
|
258
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
259
|
+
}).trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function isAncestor(repoRoot: string, ancestor: string, descendant: string): boolean {
|
|
263
|
+
return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
|
|
264
|
+
cwd: repoRoot,
|
|
265
|
+
stdio: 'ignore'
|
|
266
|
+
}).status === 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function commitIntentView(intent: CommitIntent, currentHead: string): NonNullable<CommitIntentResult['intent']> {
|
|
270
|
+
return {
|
|
271
|
+
mode: intent.mode,
|
|
272
|
+
phase: intent.phase,
|
|
273
|
+
baselineHead: intent.baselineHead,
|
|
274
|
+
currentHead,
|
|
275
|
+
committedHead: intent.committedHead,
|
|
276
|
+
pushEvidence: intent.pushEvidence,
|
|
277
|
+
runId: intent.orchestration?.runId ?? null,
|
|
278
|
+
receiptId: intent.orchestration?.receiptId ?? null
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function commitFinalizationView(inspection: CommitFinalizationInspection): NonNullable<CommitIntentResult['finalization']> {
|
|
283
|
+
return {
|
|
284
|
+
disposition: inspection.disposition,
|
|
285
|
+
code: inspection.code,
|
|
286
|
+
message: inspection.message,
|
|
287
|
+
currentHead: inspection.currentHead,
|
|
288
|
+
committedHead: inspection.committedHead,
|
|
289
|
+
needsAnchor: inspection.needsAnchor,
|
|
290
|
+
needsLog: inspection.needsLog
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function validCommitRun(run: OrchestrationRun | null, taskId: string): run is OrchestrationRun {
|
|
295
|
+
return run !== null
|
|
296
|
+
&& [1, 2].includes(run.schemaVersion)
|
|
297
|
+
&& run.taskId === taskId
|
|
298
|
+
&& ['running', 'paused', 'completed'].includes(run.status)
|
|
299
|
+
&& Array.isArray(run.receipts)
|
|
300
|
+
&& Object.prototype.hasOwnProperty.call(run, 'pendingDelegation')
|
|
301
|
+
&& typeof run.commitAuthorization === 'object'
|
|
302
|
+
&& run.commitAuthorization !== null
|
|
303
|
+
&& Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'issuedAt')
|
|
304
|
+
&& Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'consumedAt');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function mapCommitIntentError(error: unknown, taskId: string | null): CommitIntentResult {
|
|
308
|
+
if (error instanceof CommitIntentError) return commitIntentFailed(error.code, error.message, taskId);
|
|
309
|
+
if (error instanceof TaskExecutionLockError) return commitIntentFailed(error.code, error.message, taskId);
|
|
310
|
+
return commitIntentFailed(
|
|
311
|
+
'ORCHESTRATION_COMMIT_INTENT_INVALID',
|
|
312
|
+
error instanceof Error ? error.message : String(error),
|
|
313
|
+
taskId
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
203
317
|
function validModel(value: unknown): value is string {
|
|
204
318
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
205
319
|
}
|
|
@@ -634,6 +748,357 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
634
748
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
635
749
|
}
|
|
636
750
|
|
|
751
|
+
function beginCommitIntent(
|
|
752
|
+
taskRef: string,
|
|
753
|
+
input: Readonly<{ agent: string; orchestrated: boolean; baselineHead: string }>,
|
|
754
|
+
options: OrchestrationOptions = {}
|
|
755
|
+
): CommitIntentResult {
|
|
756
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
757
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
758
|
+
try {
|
|
759
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
|
|
760
|
+
const currentHead = repositoryHead(resolved.repoRoot);
|
|
761
|
+
if (currentHead !== input.baselineHead) {
|
|
762
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
|
|
763
|
+
}
|
|
764
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
765
|
+
try {
|
|
766
|
+
readCommitIntent(resolved.taskDir, resolved.taskId);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
769
|
+
}
|
|
770
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const runFile = orchestrationPath(resolved.taskDir);
|
|
774
|
+
let run: OrchestrationRun | null = null;
|
|
775
|
+
let sourceRunBytes: string | null = null;
|
|
776
|
+
if (fs.existsSync(runFile)) {
|
|
777
|
+
sourceRunBytes = fs.readFileSync(runFile, 'utf8');
|
|
778
|
+
try {
|
|
779
|
+
run = readRun(resolved.taskDir);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
return commitIntentFailed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
782
|
+
}
|
|
783
|
+
if (!validCommitRun(run, resolved.taskId)) {
|
|
784
|
+
return commitIntentFailed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
789
|
+
const failAndPause = (code: string, message: string): CommitIntentResult => {
|
|
790
|
+
if (run?.status === 'running') {
|
|
791
|
+
saveRun(resolved.taskDir, withUpdatedRun(run, {
|
|
792
|
+
status: 'paused', pause: { code, message, recoverable: true }
|
|
793
|
+
}, () => now));
|
|
794
|
+
}
|
|
795
|
+
return commitIntentFailed(code, message, resolved.taskId);
|
|
796
|
+
};
|
|
797
|
+
let orchestration: CommitIntent['orchestration'] = null;
|
|
798
|
+
if (!input.orchestrated) {
|
|
799
|
+
if (run?.pendingDelegation) {
|
|
800
|
+
return commitIntentFailed(
|
|
801
|
+
'ORCHESTRATION_STANDALONE_BUSY',
|
|
802
|
+
'standalone commit is blocked by a pending orchestration delegation',
|
|
803
|
+
resolved.taskId
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
if (!run || run.status !== 'running') {
|
|
808
|
+
return commitIntentFailed('ORCHESTRATION_RUN_NOT_RUNNING', 'orchestrated commit requires a running orchestration', resolved.taskId);
|
|
809
|
+
}
|
|
810
|
+
const receipt = run.pendingDelegation;
|
|
811
|
+
if (
|
|
812
|
+
!receipt
|
|
813
|
+
|| receipt.status !== 'activated'
|
|
814
|
+
|| receipt.stage !== 'commit'
|
|
815
|
+
|| receipt.round !== 1
|
|
816
|
+
|| receipt.artifact !== 'commit'
|
|
817
|
+
|| receipt.role !== 'executor'
|
|
818
|
+
) {
|
|
819
|
+
return failAndPause(
|
|
820
|
+
'ORCHESTRATION_PROVENANCE_MISMATCH',
|
|
821
|
+
'orchestrated commit requires one matching activated commit delegation'
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
if (!run.commitAuthorization.issuedAt || run.commitAuthorization.consumedAt) {
|
|
825
|
+
return failAndPause(
|
|
826
|
+
'ORCHESTRATION_COMMIT_AUTHORIZATION_INVALID',
|
|
827
|
+
'orchestrated commit requires an unconsumed one-use authorization'
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
const planned = planOrchestrationStageCompletion(taskRef, {
|
|
831
|
+
stage: 'commit', round: 1, artifact: 'commit', role: 'executor', agent: input.agent
|
|
832
|
+
}, { ...options, now: () => now });
|
|
833
|
+
if (planned.result.status === 'failed' || !planned.plan?.updatedRun.pendingDelegation) {
|
|
834
|
+
return failAndPause(
|
|
835
|
+
planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH',
|
|
836
|
+
planned.result.error?.message ?? 'commit delegation could not be completed'
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const plannedRunBytes = serialize(planned.plan.updatedRun);
|
|
840
|
+
orchestration = {
|
|
841
|
+
runId: run.runId,
|
|
842
|
+
receiptId: receipt.id,
|
|
843
|
+
authorizationIssuedAt: run.commitAuthorization.issuedAt,
|
|
844
|
+
sourceRunDigest: digest(sourceRunBytes!),
|
|
845
|
+
plannedRunDigest: digest(plannedRunBytes),
|
|
846
|
+
completionUpdatedAt: planned.plan.updatedRun.updatedAt,
|
|
847
|
+
plannedReceipt: planned.plan.updatedRun.pendingDelegation
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const created = createCommitIntent(resolved.taskDir, {
|
|
852
|
+
taskId: resolved.taskId,
|
|
853
|
+
mode: input.orchestrated ? 'orchestrated' : 'standalone',
|
|
854
|
+
phase: 'prepared',
|
|
855
|
+
baselineHead: input.baselineHead,
|
|
856
|
+
committedHead: null,
|
|
857
|
+
pushEvidence: null,
|
|
858
|
+
orchestration,
|
|
859
|
+
createdAt: now,
|
|
860
|
+
updatedAt: now
|
|
861
|
+
}, { token: options.token });
|
|
862
|
+
return {
|
|
863
|
+
status: 'ready', changed: true, taskId: resolved.taskId,
|
|
864
|
+
intent: commitIntentView(created.intent, currentHead), token: created.token, error: null
|
|
865
|
+
};
|
|
866
|
+
});
|
|
867
|
+
} catch (error) {
|
|
868
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function checkpointCommitIntent(
|
|
873
|
+
taskRef: string,
|
|
874
|
+
input: Readonly<{
|
|
875
|
+
token: string;
|
|
876
|
+
kind: 'committed' | 'pushed';
|
|
877
|
+
head: string;
|
|
878
|
+
remote?: string;
|
|
879
|
+
ref?: string;
|
|
880
|
+
}>,
|
|
881
|
+
options: OrchestrationOptions = {}
|
|
882
|
+
): CommitIntentResult {
|
|
883
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
884
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
885
|
+
try {
|
|
886
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
|
|
887
|
+
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
888
|
+
const currentHead = repositoryHead(resolved.repoRoot);
|
|
889
|
+
if (currentHead !== input.head) {
|
|
890
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
|
|
891
|
+
}
|
|
892
|
+
if (input.kind === 'committed' && !isAncestor(resolved.repoRoot, intent.baselineHead, input.head)) {
|
|
893
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
|
|
894
|
+
}
|
|
895
|
+
if (input.kind === 'pushed') {
|
|
896
|
+
const expectedHead = intent.committedHead ?? intent.baselineHead;
|
|
897
|
+
if (input.head !== expectedHead || !input.remote || !input.ref) {
|
|
898
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', 'pushed checkpoint requires matching remote, ref and HEAD', resolved.taskId);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
902
|
+
const updated = updateCommitIntent(resolved.taskDir, resolved.taskId, input.token, input.kind === 'committed'
|
|
903
|
+
? { phase: 'committed', committedHead: input.head, updatedAt: now }
|
|
904
|
+
: {
|
|
905
|
+
phase: 'pushed', committedHead: intent.committedHead ?? intent.baselineHead, updatedAt: now,
|
|
906
|
+
pushEvidence: { remote: input.remote!, ref: input.ref!, head: input.head }
|
|
907
|
+
});
|
|
908
|
+
return {
|
|
909
|
+
status: 'ready', changed: true, taskId: resolved.taskId,
|
|
910
|
+
intent: commitIntentView(updated, currentHead), error: null
|
|
911
|
+
};
|
|
912
|
+
});
|
|
913
|
+
} catch (error) {
|
|
914
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function completeCommitOrchestration(
|
|
919
|
+
resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
|
|
920
|
+
intent: CommitIntent,
|
|
921
|
+
agent: string
|
|
922
|
+
): Readonly<{ code: string; message: string }> | null {
|
|
923
|
+
if (intent.orchestration === null) return null;
|
|
924
|
+
if (intent.orchestration.plannedReceipt.agent !== agent) {
|
|
925
|
+
return { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'completion agent does not match the planned receipt' };
|
|
926
|
+
}
|
|
927
|
+
const runFile = orchestrationPath(resolved.taskDir);
|
|
928
|
+
if (!fs.existsSync(runFile)) {
|
|
929
|
+
return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
|
|
930
|
+
}
|
|
931
|
+
const currentBytes = fs.readFileSync(runFile, 'utf8');
|
|
932
|
+
const currentDigest = digest(currentBytes);
|
|
933
|
+
if (currentDigest === intent.orchestration.sourceRunDigest) {
|
|
934
|
+
const run = readRun(resolved.taskDir);
|
|
935
|
+
if (!run) return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
|
|
936
|
+
const plannedRun: OrchestrationRun = Object.freeze({
|
|
937
|
+
...run,
|
|
938
|
+
pendingDelegation: intent.orchestration.plannedReceipt,
|
|
939
|
+
updatedAt: intent.orchestration.completionUpdatedAt
|
|
940
|
+
});
|
|
941
|
+
if (digest(serialize(plannedRun)) !== intent.orchestration.plannedRunDigest) {
|
|
942
|
+
return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'planned orchestration bytes no longer match the intent' };
|
|
943
|
+
}
|
|
944
|
+
atomicWrite(runFile, plannedRun);
|
|
945
|
+
if (digest(fs.readFileSync(runFile)) !== intent.orchestration.plannedRunDigest) {
|
|
946
|
+
return { code: 'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', message: 'orchestration completion could not be verified' };
|
|
947
|
+
}
|
|
948
|
+
} else if (currentDigest !== intent.orchestration.plannedRunDigest) {
|
|
949
|
+
return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'orchestration state changed after commit begin' };
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function finalizeCommitIntentUnlocked(
|
|
955
|
+
resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
|
|
956
|
+
inspection: CommitFinalizationInspection,
|
|
957
|
+
agent: string,
|
|
958
|
+
remove: () => void
|
|
959
|
+
): CommitIntentResult {
|
|
960
|
+
if (inspection.disposition !== 'recoverable' || !inspection.intent) {
|
|
961
|
+
return commitIntentFailed(
|
|
962
|
+
inspection.code ?? 'COMMIT_FINALIZATION_PENDING',
|
|
963
|
+
inspection.message,
|
|
964
|
+
resolved.taskId
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
if (
|
|
968
|
+
inspection.intent.orchestration !== null
|
|
969
|
+
&& inspection.intent.orchestration.plannedReceipt.agent !== agent
|
|
970
|
+
) {
|
|
971
|
+
return commitIntentFailed(
|
|
972
|
+
'ORCHESTRATION_PROVENANCE_MISMATCH',
|
|
973
|
+
'completion agent does not match the planned receipt',
|
|
974
|
+
resolved.taskId
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
const metadata = captureTaskWriteMetadata();
|
|
978
|
+
const taskPlan = planCommitTaskFinalization(
|
|
979
|
+
resolved.taskDir,
|
|
980
|
+
inspection,
|
|
981
|
+
agent,
|
|
982
|
+
metadata.timestamp
|
|
983
|
+
);
|
|
984
|
+
const written = writeTask({
|
|
985
|
+
taskRef: resolved.taskId,
|
|
986
|
+
expectedState: 'active',
|
|
987
|
+
mutations: taskPlan.mutations
|
|
988
|
+
}, {
|
|
989
|
+
repoRoot: resolved.repoRoot,
|
|
990
|
+
metadataProvider: () => metadata
|
|
991
|
+
});
|
|
992
|
+
if (written.status === 'failed') {
|
|
993
|
+
return commitIntentFailed(written.error.code, written.error.message, resolved.taskId);
|
|
994
|
+
}
|
|
995
|
+
const orchestrationError = completeCommitOrchestration(resolved, inspection.intent, agent);
|
|
996
|
+
if (orchestrationError) {
|
|
997
|
+
return commitIntentFailed(orchestrationError.code, orchestrationError.message, resolved.taskId);
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
remove();
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
return commitIntentFailed(
|
|
1003
|
+
'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL',
|
|
1004
|
+
error instanceof Error ? error.message : String(error),
|
|
1005
|
+
resolved.taskId
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function completeCommitIntent(
|
|
1012
|
+
taskRef: string,
|
|
1013
|
+
input: Readonly<{ token: string; agent: string }>,
|
|
1014
|
+
options: OrchestrationOptions = {}
|
|
1015
|
+
): CommitIntentResult {
|
|
1016
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1017
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1018
|
+
try {
|
|
1019
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
|
|
1020
|
+
readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1021
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
|
|
1022
|
+
return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
|
|
1023
|
+
removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function recoverCommitIntent(
|
|
1032
|
+
taskRef: string,
|
|
1033
|
+
input: Readonly<{ agent: string }>,
|
|
1034
|
+
options: OrchestrationOptions = {}
|
|
1035
|
+
): CommitIntentResult {
|
|
1036
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1037
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1038
|
+
try {
|
|
1039
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
|
|
1040
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
|
|
1041
|
+
if (inspection.disposition === 'prepared' && inspection.intentDigest) {
|
|
1042
|
+
removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
|
|
1043
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
1044
|
+
}
|
|
1045
|
+
if (!inspection.intentDigest) {
|
|
1046
|
+
return commitIntentFailed(
|
|
1047
|
+
inspection.code ?? 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
|
|
1048
|
+
inspection.message,
|
|
1049
|
+
resolved.taskId
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
|
|
1053
|
+
removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest!);
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function abortCommitIntent(
|
|
1062
|
+
taskRef: string,
|
|
1063
|
+
input: Readonly<{ token: string; expectedHead: string }>,
|
|
1064
|
+
options: OrchestrationOptions = {}
|
|
1065
|
+
): CommitIntentResult {
|
|
1066
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1067
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1068
|
+
try {
|
|
1069
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
|
|
1070
|
+
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1071
|
+
const currentHead = repositoryHead(resolved.repoRoot);
|
|
1072
|
+
if (
|
|
1073
|
+
intent.phase !== 'prepared'
|
|
1074
|
+
|| intent.pushEvidence !== null
|
|
1075
|
+
|| input.expectedHead !== intent.baselineHead
|
|
1076
|
+
|| currentHead !== intent.baselineHead
|
|
1077
|
+
) {
|
|
1078
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent has side effects or repository drift', resolved.taskId);
|
|
1079
|
+
}
|
|
1080
|
+
removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1081
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
1082
|
+
});
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function statusCommitIntent(taskRef: string, options: OrchestrationOptions = {}): CommitIntentResult {
|
|
1089
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1090
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1091
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
|
|
1092
|
+
return {
|
|
1093
|
+
status: 'ready',
|
|
1094
|
+
changed: false,
|
|
1095
|
+
taskId: resolved.taskId,
|
|
1096
|
+
intent: inspection.intent ? commitIntentView(inspection.intent, inspection.currentHead) : null,
|
|
1097
|
+
finalization: commitFinalizationView(inspection),
|
|
1098
|
+
error: null
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
637
1102
|
function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
638
1103
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
639
1104
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -649,6 +1114,18 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
649
1114
|
): OrchestrationResult {
|
|
650
1115
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
651
1116
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1117
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
1118
|
+
try {
|
|
1119
|
+
readCommitIntent(resolved.taskDir, resolved.taskId);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
return failed(
|
|
1122
|
+
'ORCHESTRATION_COMMIT_INTENT_INVALID',
|
|
1123
|
+
error instanceof Error ? error.message : String(error),
|
|
1124
|
+
resolved.taskId
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
return failed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent blocks delegation preparation', resolved.taskId);
|
|
1128
|
+
}
|
|
652
1129
|
if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
|
|
653
1130
|
return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
|
|
654
1131
|
}
|
|
@@ -656,8 +1133,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
656
1133
|
if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
|
|
657
1134
|
if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
|
|
658
1135
|
if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
659
|
-
const repositoryPending = (
|
|
660
|
-
.flatMap((client) => matchingDelegations(client, () => true, options));
|
|
1136
|
+
const repositoryPending = matchingDelegations(() => true, options);
|
|
661
1137
|
if (repositoryPending.length > 0) {
|
|
662
1138
|
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
663
1139
|
}
|
|
@@ -718,9 +1194,14 @@ function prepareOrchestrationDelegation(
|
|
|
718
1194
|
try {
|
|
719
1195
|
return withTaskExecutionLock(
|
|
720
1196
|
resolved.repoRoot,
|
|
721
|
-
|
|
722
|
-
'task-orchestration.prepare',
|
|
723
|
-
() =>
|
|
1197
|
+
'__repository__',
|
|
1198
|
+
'task-orchestration.prepare.repository',
|
|
1199
|
+
() => withTaskExecutionLock(
|
|
1200
|
+
resolved.repoRoot,
|
|
1201
|
+
resolved.taskId,
|
|
1202
|
+
'task-orchestration.prepare.task',
|
|
1203
|
+
() => prepareOrchestrationDelegationUnlocked(taskRef, input, options)
|
|
1204
|
+
)
|
|
724
1205
|
);
|
|
725
1206
|
} catch (error) {
|
|
726
1207
|
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
@@ -729,7 +1210,6 @@ function prepareOrchestrationDelegation(
|
|
|
729
1210
|
}
|
|
730
1211
|
|
|
731
1212
|
function matchingDelegations(
|
|
732
|
-
client: AgentClientId,
|
|
733
1213
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
734
1214
|
options: OrchestrationOptions
|
|
735
1215
|
): Array<{ taskId: string; run: OrchestrationRun }> {
|
|
@@ -742,7 +1222,7 @@ function matchingDelegations(
|
|
|
742
1222
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
743
1223
|
if (!run?.pendingDelegation) return [];
|
|
744
1224
|
const receipt = run.pendingDelegation;
|
|
745
|
-
return
|
|
1225
|
+
return predicate(receipt)
|
|
746
1226
|
? [{ taskId: entry.name, run }]
|
|
747
1227
|
: [];
|
|
748
1228
|
});
|
|
@@ -753,7 +1233,10 @@ function uniqueMatchingDelegation(
|
|
|
753
1233
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
754
1234
|
options: OrchestrationOptions
|
|
755
1235
|
): { taskId: string; run: OrchestrationRun } | OrchestrationResult {
|
|
756
|
-
const matches = matchingDelegations(
|
|
1236
|
+
const matches = matchingDelegations(
|
|
1237
|
+
(receipt) => receipt.client === client && predicate(receipt),
|
|
1238
|
+
options
|
|
1239
|
+
);
|
|
757
1240
|
if (matches.length === 0) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
758
1241
|
if (matches.length > 1) return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
759
1242
|
return matches[0]!;
|
|
@@ -901,7 +1384,7 @@ function planOrchestrationStageCompletion(
|
|
|
901
1384
|
}
|
|
902
1385
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
903
1386
|
if (!resolved.ok) return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
|
|
904
|
-
const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt });
|
|
1387
|
+
const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt }, options.now);
|
|
905
1388
|
return {
|
|
906
1389
|
result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
|
|
907
1390
|
plan: {
|
|
@@ -916,27 +1399,6 @@ function commitOrchestrationStageCompletion(plan: OrchestrationStageCompletion):
|
|
|
916
1399
|
saveRun(plan.taskDir, plan.updatedRun);
|
|
917
1400
|
}
|
|
918
1401
|
|
|
919
|
-
function completeCommitOrchestrationStage(
|
|
920
|
-
taskRef: string,
|
|
921
|
-
agent: string,
|
|
922
|
-
options: OrchestrationOptions = {}
|
|
923
|
-
): OrchestrationResult {
|
|
924
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
925
|
-
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
926
|
-
const run = readRun(resolved.taskDir);
|
|
927
|
-
if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
928
|
-
const receipt = run.pendingDelegation;
|
|
929
|
-
if (!receipt || receipt.stage !== 'commit') {
|
|
930
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
|
|
931
|
-
}
|
|
932
|
-
return completeOrchestrationStage(taskRef, {
|
|
933
|
-
stage: receipt.stage,
|
|
934
|
-
round: receipt.round,
|
|
935
|
-
artifact: receipt.artifact,
|
|
936
|
-
agent
|
|
937
|
-
}, options);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
1402
|
function sealOrchestrationDelegation(
|
|
941
1403
|
taskRef: string,
|
|
942
1404
|
event: Parameters<typeof sealDelegation>[1],
|
|
@@ -995,9 +1457,12 @@ export {
|
|
|
995
1457
|
activateMatchingOrchestrationDelegation,
|
|
996
1458
|
activateOrchestrationDelegation,
|
|
997
1459
|
advanceOrchestration,
|
|
1460
|
+
abortCommitIntent,
|
|
1461
|
+
beginCommitIntent,
|
|
998
1462
|
beginOrResumeOrchestration,
|
|
1463
|
+
checkpointCommitIntent,
|
|
999
1464
|
commitOrchestrationStageCompletion,
|
|
1000
|
-
|
|
1465
|
+
completeCommitIntent,
|
|
1001
1466
|
completeOrchestrationStage,
|
|
1002
1467
|
inspectOrchestrationStage,
|
|
1003
1468
|
orchestrationPath,
|
|
@@ -1005,12 +1470,15 @@ export {
|
|
|
1005
1470
|
planOrchestrationStageCompletion,
|
|
1006
1471
|
prepareOrchestrationDelegation,
|
|
1007
1472
|
readRun,
|
|
1473
|
+
recoverCommitIntent,
|
|
1008
1474
|
routeOrchestration,
|
|
1009
1475
|
sealMatchingOrchestrationDelegation,
|
|
1010
1476
|
sealOrchestrationDelegation,
|
|
1477
|
+
statusCommitIntent,
|
|
1011
1478
|
statusOrchestration
|
|
1012
1479
|
};
|
|
1013
1480
|
export type {
|
|
1481
|
+
CommitIntentResult,
|
|
1014
1482
|
OrchestrationCompletionPlanResult,
|
|
1015
1483
|
OrchestrationModelPolicy,
|
|
1016
1484
|
OrchestrationNext,
|