@fitlab-ai/agent-infra 0.9.3 → 0.9.5
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 +3 -1
- package/README.zh-CN.md +3 -1
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +144 -123
- package/dist/lib/agent-clients/adapter.js +94 -4
- 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/codex-lifecycle/app-server.js +382 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
- package/dist/lib/agent-clients/adapters/codex.js +2 -2
- 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/git/worktree-identity.js +64 -0
- package/dist/lib/internal/codex-lifecycle.js +202 -0
- package/dist/lib/internal/sandbox-control.js +24 -0
- package/dist/lib/internal/task-activity.js +45 -13
- package/dist/lib/internal/task-create.js +48 -0
- 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 +170 -27
- 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 +110 -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 +58 -0
- package/dist/lib/sandbox/control/protocol.js +54 -0
- package/dist/lib/sandbox/control/server.js +144 -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 +109 -0
- package/dist/lib/task/activity-intent.js +257 -51
- package/dist/lib/task/activity-log.js +32 -5
- package/dist/lib/task/commit-finalization.js +278 -0
- package/dist/lib/task/commit-intent.js +165 -0
- package/dist/lib/task/create-service.js +137 -0
- package/dist/lib/task/create.js +333 -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 +505 -34
- package/dist/lib/task/resolve-ref.js +41 -1
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/workspace-snapshot.js +78 -3
- package/lib/agent-clients/adapter.ts +130 -6
- 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/codex-lifecycle/app-server.ts +442 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
- package/lib/agent-clients/adapters/codex.ts +2 -2
- 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/git/worktree-identity.ts +86 -0
- package/lib/internal/codex-lifecycle.ts +204 -0
- package/lib/internal/sandbox-control.ts +24 -0
- package/lib/internal/task-activity.ts +50 -14
- package/lib/internal/task-create.ts +49 -0
- package/lib/internal/task-ledger.ts +12 -3
- package/lib/internal/task-lifecycle.ts +22 -1
- package/lib/internal/task-orchestration.ts +117 -21
- 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 +127 -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 +79 -0
- package/lib/sandbox/control/protocol.ts +110 -0
- package/lib/sandbox/control/server.ts +155 -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 +150 -0
- package/lib/task/activity-intent.ts +350 -61
- package/lib/task/activity-log.ts +46 -7
- package/lib/task/commit-finalization.ts +376 -0
- package/lib/task/commit-intent.ts +230 -0
- package/lib/task/create-service.ts +171 -0
- package/lib/task/create.ts +372 -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 +673 -41
- package/lib/task/resolve-ref.ts +46 -1
- package/lib/task/review-fingerprint.ts +9 -0
- package/lib/task/workspace-snapshot.ts +98 -4
- package/package.json +7 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
- 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 +3 -0
- package/templates/.agents/rules/task-management.zh-CN.md +3 -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 -17
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -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/SKILL.en.md +26 -43
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
- 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/.codex/hooks.json +50 -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
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
6
|
import { parseReviewSummary } from './review-artifacts.js';
|
|
6
7
|
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
@@ -12,7 +13,12 @@ import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
|
|
|
12
13
|
import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
13
14
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
14
15
|
import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
16
|
+
import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
|
|
15
17
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
18
|
+
import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
|
|
19
|
+
import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
|
|
20
|
+
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
21
|
+
import { appendActivityEntry, commitAttemptStartedNote, locateActivityLog, pairEntries } from './activity-log.js';
|
|
16
22
|
function supportsLifecycleDelegation(client) {
|
|
17
23
|
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
18
24
|
&& getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
|
|
@@ -51,12 +57,76 @@ function atomicWrite(file, value) {
|
|
|
51
57
|
function saveRun(taskDir, run) {
|
|
52
58
|
atomicWrite(orchestrationPath(taskDir), run);
|
|
53
59
|
}
|
|
54
|
-
function withUpdatedRun(run, updates) {
|
|
55
|
-
return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
|
|
60
|
+
function withUpdatedRun(run, updates, now) {
|
|
61
|
+
return Object.freeze({ ...run, ...updates, updatedAt: (now ?? (() => new Date().toISOString()))() });
|
|
56
62
|
}
|
|
57
63
|
function failed(code, message, taskId = null, details = {}) {
|
|
58
64
|
return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
|
|
59
65
|
}
|
|
66
|
+
function commitIntentFailed(code, message, taskId = null) {
|
|
67
|
+
return { status: 'failed', changed: false, taskId, intent: null, error: { code, message } };
|
|
68
|
+
}
|
|
69
|
+
function repositoryHead(repoRoot) {
|
|
70
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
71
|
+
cwd: repoRoot,
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
74
|
+
}).trim();
|
|
75
|
+
}
|
|
76
|
+
function gitRootFor(stateRoot, options) {
|
|
77
|
+
if (!options.gitWorktreeRoot)
|
|
78
|
+
return stateRoot;
|
|
79
|
+
return assertGitRepositoryBinding(stateRoot, options.gitWorktreeRoot).worktreeRoot;
|
|
80
|
+
}
|
|
81
|
+
function isAncestor(repoRoot, ancestor, descendant) {
|
|
82
|
+
return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
|
|
83
|
+
cwd: repoRoot,
|
|
84
|
+
stdio: 'ignore'
|
|
85
|
+
}).status === 0;
|
|
86
|
+
}
|
|
87
|
+
function commitIntentView(intent, currentHead) {
|
|
88
|
+
return {
|
|
89
|
+
mode: intent.mode,
|
|
90
|
+
phase: intent.phase,
|
|
91
|
+
baselineHead: intent.baselineHead,
|
|
92
|
+
currentHead,
|
|
93
|
+
committedHead: intent.committedHead,
|
|
94
|
+
pushEvidence: intent.pushEvidence,
|
|
95
|
+
runId: intent.orchestration?.runId ?? null,
|
|
96
|
+
receiptId: intent.orchestration?.receiptId ?? null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function commitFinalizationView(inspection) {
|
|
100
|
+
return {
|
|
101
|
+
disposition: inspection.disposition,
|
|
102
|
+
code: inspection.code,
|
|
103
|
+
message: inspection.message,
|
|
104
|
+
currentHead: inspection.currentHead,
|
|
105
|
+
committedHead: inspection.committedHead,
|
|
106
|
+
needsAnchor: inspection.needsAnchor,
|
|
107
|
+
needsLog: inspection.needsLog,
|
|
108
|
+
attempt: inspection.attempt
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function validCommitRun(run, taskId) {
|
|
112
|
+
return run !== null
|
|
113
|
+
&& [1, 2].includes(run.schemaVersion)
|
|
114
|
+
&& run.taskId === taskId
|
|
115
|
+
&& ['running', 'paused', 'completed'].includes(run.status)
|
|
116
|
+
&& Array.isArray(run.receipts)
|
|
117
|
+
&& Object.prototype.hasOwnProperty.call(run, 'pendingDelegation')
|
|
118
|
+
&& typeof run.commitAuthorization === 'object'
|
|
119
|
+
&& run.commitAuthorization !== null
|
|
120
|
+
&& Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'issuedAt')
|
|
121
|
+
&& Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'consumedAt');
|
|
122
|
+
}
|
|
123
|
+
function mapCommitIntentError(error, taskId) {
|
|
124
|
+
if (error instanceof CommitIntentError)
|
|
125
|
+
return commitIntentFailed(error.code, error.message, taskId);
|
|
126
|
+
if (error instanceof TaskExecutionLockError)
|
|
127
|
+
return commitIntentFailed(error.code, error.message, taskId);
|
|
128
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), taskId);
|
|
129
|
+
}
|
|
60
130
|
function validModel(value) {
|
|
61
131
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
62
132
|
}
|
|
@@ -337,6 +407,13 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
337
407
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
338
408
|
if (!resolved.ok)
|
|
339
409
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
410
|
+
let gitRoot;
|
|
411
|
+
try {
|
|
412
|
+
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
return failed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
416
|
+
}
|
|
340
417
|
let content;
|
|
341
418
|
try {
|
|
342
419
|
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
@@ -407,7 +484,7 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
407
484
|
const capture = options.captureRepository ?? captureRepositorySnapshot;
|
|
408
485
|
let before;
|
|
409
486
|
try {
|
|
410
|
-
before = capture(
|
|
487
|
+
before = capture(gitRoot);
|
|
411
488
|
}
|
|
412
489
|
catch (error) {
|
|
413
490
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
@@ -422,7 +499,7 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
422
499
|
const inspect = options.inspectPullRequest ?? inspectPlatformPullRequest;
|
|
423
500
|
let inspection;
|
|
424
501
|
try {
|
|
425
|
-
inspection = inspect(resolved.taskId, { cwd:
|
|
502
|
+
inspection = inspect(resolved.taskId, { cwd: gitRoot });
|
|
426
503
|
}
|
|
427
504
|
catch (error) {
|
|
428
505
|
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
@@ -443,7 +520,7 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
443
520
|
}
|
|
444
521
|
let after;
|
|
445
522
|
try {
|
|
446
|
-
after = capture(
|
|
523
|
+
after = capture(gitRoot);
|
|
447
524
|
}
|
|
448
525
|
catch (error) {
|
|
449
526
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
@@ -476,6 +553,401 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
476
553
|
}
|
|
477
554
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
478
555
|
}
|
|
556
|
+
function appendCommitActivity(resolved, step, agent, note) {
|
|
557
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
558
|
+
const activity = locateActivityLog(taskContent);
|
|
559
|
+
if (!activity)
|
|
560
|
+
return { code: 'ORCHESTRATION_TASK_INVALID', message: 'task activity log is missing or ambiguous' };
|
|
561
|
+
const metadata = captureTaskWriteMetadata();
|
|
562
|
+
const written = writeTask({
|
|
563
|
+
taskRef: resolved.taskId,
|
|
564
|
+
expectedState: 'active',
|
|
565
|
+
mutations: [{
|
|
566
|
+
kind: 'section',
|
|
567
|
+
aliases: ['活动日志', 'Activity Log'],
|
|
568
|
+
heading: activity.heading,
|
|
569
|
+
body: appendActivityEntry(activity, { time: metadata.timestamp, step, agent, note })
|
|
570
|
+
}]
|
|
571
|
+
}, { repoRoot: resolved.repoRoot, metadataProvider: () => metadata });
|
|
572
|
+
return written.status === 'failed' ? written.error : null;
|
|
573
|
+
}
|
|
574
|
+
function startCommitAttempt(taskRef, input, options = {}) {
|
|
575
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
576
|
+
if (!resolved.ok)
|
|
577
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
578
|
+
try {
|
|
579
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
580
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.start', () => {
|
|
581
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
582
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
|
|
583
|
+
}
|
|
584
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
585
|
+
const activity = locateActivityLog(taskContent);
|
|
586
|
+
if (!activity)
|
|
587
|
+
return commitIntentFailed('ORCHESTRATION_TASK_INVALID', 'task activity log is missing or ambiguous', resolved.taskId);
|
|
588
|
+
const open = pairEntries(activity.entries).filter((row) => row.step === 'Commit' && row.started !== '' && row.done === '');
|
|
589
|
+
if (open.length > 0) {
|
|
590
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_ATTEMPT_BUSY', 'an open Commit activity entry already exists', resolved.taskId);
|
|
591
|
+
}
|
|
592
|
+
const currentHead = repositoryHead(gitRoot);
|
|
593
|
+
const attempt = (options.id ?? randomUUID)();
|
|
594
|
+
const error = appendCommitActivity(resolved, 'Commit [started]', input.agent, commitAttemptStartedNote({
|
|
595
|
+
attempt,
|
|
596
|
+
baseline: currentHead,
|
|
597
|
+
agent: input.agent
|
|
598
|
+
}));
|
|
599
|
+
if (error)
|
|
600
|
+
return commitIntentFailed(error.code, error.message, resolved.taskId);
|
|
601
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
602
|
+
return {
|
|
603
|
+
status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
|
|
604
|
+
finalization: commitFinalizationView(inspection), error: null
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function terminateCommitAttempt(taskRef, input, options = {}) {
|
|
613
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
614
|
+
if (!resolved.ok)
|
|
615
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
616
|
+
try {
|
|
617
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
618
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.terminate', () => {
|
|
619
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
620
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'an active commit intent must be aborted or recovered first', resolved.taskId);
|
|
621
|
+
}
|
|
622
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
623
|
+
if (inspection.disposition !== 'retryable-start'
|
|
624
|
+
|| inspection.attempt?.attempt !== input.attempt
|
|
625
|
+
|| inspection.attempt.agent !== input.agent
|
|
626
|
+
|| inspection.attempt.baseline !== inspection.currentHead) {
|
|
627
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit attempt identity or repository HEAD has drifted', resolved.taskId);
|
|
628
|
+
}
|
|
629
|
+
const error = appendCommitActivity(resolved, 'Commit [aborted]', input.agent, `aborted; attempt=${input.attempt}; code=${input.code}`);
|
|
630
|
+
if (error)
|
|
631
|
+
return commitIntentFailed(error.code, error.message, resolved.taskId);
|
|
632
|
+
return {
|
|
633
|
+
status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
|
|
634
|
+
finalization: commitFinalizationView(inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId)),
|
|
635
|
+
error: null
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function beginCommitIntent(taskRef, input, options = {}) {
|
|
644
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
645
|
+
if (!resolved.ok)
|
|
646
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
647
|
+
try {
|
|
648
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
649
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
|
|
650
|
+
const currentHead = repositoryHead(gitRoot);
|
|
651
|
+
if (currentHead !== input.baselineHead) {
|
|
652
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
|
|
653
|
+
}
|
|
654
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
655
|
+
try {
|
|
656
|
+
readCommitIntent(resolved.taskDir, resolved.taskId);
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
660
|
+
}
|
|
661
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
|
|
662
|
+
}
|
|
663
|
+
const attemptInspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
664
|
+
if (attemptInspection.disposition !== 'retryable-start'
|
|
665
|
+
|| attemptInspection.attempt?.attempt !== input.attempt
|
|
666
|
+
|| attemptInspection.attempt.agent !== input.agent
|
|
667
|
+
|| attemptInspection.attempt.baseline !== input.baselineHead) {
|
|
668
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_ATTEMPT_MISMATCH', 'commit begin requires one matching retryable Commit attempt', resolved.taskId);
|
|
669
|
+
}
|
|
670
|
+
const runFile = orchestrationPath(resolved.taskDir);
|
|
671
|
+
let run = null;
|
|
672
|
+
let sourceRunBytes = null;
|
|
673
|
+
if (fs.existsSync(runFile)) {
|
|
674
|
+
sourceRunBytes = fs.readFileSync(runFile, 'utf8');
|
|
675
|
+
try {
|
|
676
|
+
run = readRun(resolved.taskDir);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
return commitIntentFailed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
680
|
+
}
|
|
681
|
+
if (!validCommitRun(run, resolved.taskId)) {
|
|
682
|
+
return commitIntentFailed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
686
|
+
const failAndPause = (code, message) => {
|
|
687
|
+
if (run?.status === 'running') {
|
|
688
|
+
saveRun(resolved.taskDir, withUpdatedRun(run, {
|
|
689
|
+
status: 'paused', pause: { code, message, recoverable: true }
|
|
690
|
+
}, () => now));
|
|
691
|
+
}
|
|
692
|
+
return commitIntentFailed(code, message, resolved.taskId);
|
|
693
|
+
};
|
|
694
|
+
let orchestration = null;
|
|
695
|
+
if (!input.orchestrated) {
|
|
696
|
+
if (run?.pendingDelegation) {
|
|
697
|
+
return commitIntentFailed('ORCHESTRATION_STANDALONE_BUSY', 'standalone commit is blocked by a pending orchestration delegation', resolved.taskId);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
if (!run || run.status !== 'running') {
|
|
702
|
+
return commitIntentFailed('ORCHESTRATION_RUN_NOT_RUNNING', 'orchestrated commit requires a running orchestration', resolved.taskId);
|
|
703
|
+
}
|
|
704
|
+
const receipt = run.pendingDelegation;
|
|
705
|
+
if (!receipt
|
|
706
|
+
|| receipt.status !== 'activated'
|
|
707
|
+
|| receipt.stage !== 'commit'
|
|
708
|
+
|| receipt.round !== 1
|
|
709
|
+
|| receipt.artifact !== 'commit'
|
|
710
|
+
|| receipt.role !== 'executor') {
|
|
711
|
+
return failAndPause('ORCHESTRATION_PROVENANCE_MISMATCH', 'orchestrated commit requires one matching activated commit delegation');
|
|
712
|
+
}
|
|
713
|
+
if (!run.commitAuthorization.issuedAt || run.commitAuthorization.consumedAt) {
|
|
714
|
+
return failAndPause('ORCHESTRATION_COMMIT_AUTHORIZATION_INVALID', 'orchestrated commit requires an unconsumed one-use authorization');
|
|
715
|
+
}
|
|
716
|
+
const planned = planOrchestrationStageCompletion(taskRef, {
|
|
717
|
+
stage: 'commit', round: 1, artifact: 'commit', role: 'executor', agent: input.agent
|
|
718
|
+
}, { ...options, now: () => now });
|
|
719
|
+
if (planned.result.status === 'failed' || !planned.plan?.updatedRun.pendingDelegation) {
|
|
720
|
+
return failAndPause(planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH', planned.result.error?.message ?? 'commit delegation could not be completed');
|
|
721
|
+
}
|
|
722
|
+
const plannedRunBytes = serialize(planned.plan.updatedRun);
|
|
723
|
+
orchestration = {
|
|
724
|
+
runId: run.runId,
|
|
725
|
+
receiptId: receipt.id,
|
|
726
|
+
authorizationIssuedAt: run.commitAuthorization.issuedAt,
|
|
727
|
+
sourceRunDigest: digest(sourceRunBytes),
|
|
728
|
+
plannedRunDigest: digest(plannedRunBytes),
|
|
729
|
+
completionUpdatedAt: planned.plan.updatedRun.updatedAt,
|
|
730
|
+
plannedReceipt: planned.plan.updatedRun.pendingDelegation
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const created = createCommitIntent(resolved.taskDir, {
|
|
734
|
+
taskId: resolved.taskId,
|
|
735
|
+
mode: input.orchestrated ? 'orchestrated' : 'standalone',
|
|
736
|
+
phase: 'prepared',
|
|
737
|
+
baselineHead: input.baselineHead,
|
|
738
|
+
committedHead: null,
|
|
739
|
+
pushEvidence: null,
|
|
740
|
+
orchestration,
|
|
741
|
+
createdAt: now,
|
|
742
|
+
updatedAt: now
|
|
743
|
+
}, { token: options.token });
|
|
744
|
+
return {
|
|
745
|
+
status: 'ready', changed: true, taskId: resolved.taskId,
|
|
746
|
+
intent: commitIntentView(created.intent, currentHead), token: created.token, error: null
|
|
747
|
+
};
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function checkpointCommitIntent(taskRef, input, options = {}) {
|
|
755
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
756
|
+
if (!resolved.ok)
|
|
757
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
758
|
+
try {
|
|
759
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
760
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
|
|
761
|
+
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
762
|
+
const currentHead = repositoryHead(gitRoot);
|
|
763
|
+
if (currentHead !== input.head) {
|
|
764
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
|
|
765
|
+
}
|
|
766
|
+
if (input.kind === 'committed' && !isAncestor(gitRoot, intent.baselineHead, input.head)) {
|
|
767
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
|
|
768
|
+
}
|
|
769
|
+
if (input.kind === 'pushed') {
|
|
770
|
+
const expectedHead = intent.committedHead ?? intent.baselineHead;
|
|
771
|
+
if (input.head !== expectedHead || !input.remote || !input.ref) {
|
|
772
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', 'pushed checkpoint requires matching remote, ref and HEAD', resolved.taskId);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
776
|
+
const updated = updateCommitIntent(resolved.taskDir, resolved.taskId, input.token, input.kind === 'committed'
|
|
777
|
+
? { phase: 'committed', committedHead: input.head, updatedAt: now }
|
|
778
|
+
: {
|
|
779
|
+
phase: 'pushed', committedHead: intent.committedHead ?? intent.baselineHead, updatedAt: now,
|
|
780
|
+
pushEvidence: { remote: input.remote, ref: input.ref, head: input.head }
|
|
781
|
+
});
|
|
782
|
+
return {
|
|
783
|
+
status: 'ready', changed: true, taskId: resolved.taskId,
|
|
784
|
+
intent: commitIntentView(updated, currentHead), error: null
|
|
785
|
+
};
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function completeCommitOrchestration(resolved, intent, agent) {
|
|
793
|
+
if (intent.orchestration === null)
|
|
794
|
+
return null;
|
|
795
|
+
if (intent.orchestration.plannedReceipt.agent !== agent) {
|
|
796
|
+
return { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'completion agent does not match the planned receipt' };
|
|
797
|
+
}
|
|
798
|
+
const runFile = orchestrationPath(resolved.taskDir);
|
|
799
|
+
if (!fs.existsSync(runFile)) {
|
|
800
|
+
return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
|
|
801
|
+
}
|
|
802
|
+
const currentBytes = fs.readFileSync(runFile, 'utf8');
|
|
803
|
+
const currentDigest = digest(currentBytes);
|
|
804
|
+
if (currentDigest === intent.orchestration.sourceRunDigest) {
|
|
805
|
+
const run = readRun(resolved.taskDir);
|
|
806
|
+
if (!run)
|
|
807
|
+
return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
|
|
808
|
+
const plannedRun = Object.freeze({
|
|
809
|
+
...run,
|
|
810
|
+
pendingDelegation: intent.orchestration.plannedReceipt,
|
|
811
|
+
updatedAt: intent.orchestration.completionUpdatedAt
|
|
812
|
+
});
|
|
813
|
+
if (digest(serialize(plannedRun)) !== intent.orchestration.plannedRunDigest) {
|
|
814
|
+
return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'planned orchestration bytes no longer match the intent' };
|
|
815
|
+
}
|
|
816
|
+
atomicWrite(runFile, plannedRun);
|
|
817
|
+
if (digest(fs.readFileSync(runFile)) !== intent.orchestration.plannedRunDigest) {
|
|
818
|
+
return { code: 'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', message: 'orchestration completion could not be verified' };
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
else if (currentDigest !== intent.orchestration.plannedRunDigest) {
|
|
822
|
+
return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'orchestration state changed after commit begin' };
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
function finalizeCommitIntentUnlocked(resolved, inspection, agent, remove) {
|
|
827
|
+
if (inspection.disposition !== 'recoverable' || !inspection.intent) {
|
|
828
|
+
return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_PENDING', inspection.message, resolved.taskId);
|
|
829
|
+
}
|
|
830
|
+
if (inspection.intent.orchestration !== null
|
|
831
|
+
&& inspection.intent.orchestration.plannedReceipt.agent !== agent) {
|
|
832
|
+
return commitIntentFailed('ORCHESTRATION_PROVENANCE_MISMATCH', 'completion agent does not match the planned receipt', resolved.taskId);
|
|
833
|
+
}
|
|
834
|
+
const metadata = captureTaskWriteMetadata();
|
|
835
|
+
const taskPlan = planCommitTaskFinalization(resolved.taskDir, inspection, agent, metadata.timestamp);
|
|
836
|
+
const written = writeTask({
|
|
837
|
+
taskRef: resolved.taskId,
|
|
838
|
+
expectedState: 'active',
|
|
839
|
+
mutations: taskPlan.mutations
|
|
840
|
+
}, {
|
|
841
|
+
repoRoot: resolved.repoRoot,
|
|
842
|
+
metadataProvider: () => metadata
|
|
843
|
+
});
|
|
844
|
+
if (written.status === 'failed') {
|
|
845
|
+
return commitIntentFailed(written.error.code, written.error.message, resolved.taskId);
|
|
846
|
+
}
|
|
847
|
+
const orchestrationError = completeCommitOrchestration(resolved, inspection.intent, agent);
|
|
848
|
+
if (orchestrationError) {
|
|
849
|
+
return commitIntentFailed(orchestrationError.code, orchestrationError.message, resolved.taskId);
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
remove();
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
856
|
+
}
|
|
857
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
858
|
+
}
|
|
859
|
+
function completeCommitIntent(taskRef, input, options = {}) {
|
|
860
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
861
|
+
if (!resolved.ok)
|
|
862
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
863
|
+
try {
|
|
864
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
865
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
|
|
866
|
+
readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
867
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
868
|
+
return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
|
|
869
|
+
removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
function recoverCommitIntent(taskRef, input, options = {}) {
|
|
878
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
879
|
+
if (!resolved.ok)
|
|
880
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
881
|
+
try {
|
|
882
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
883
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
|
|
884
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
885
|
+
if (inspection.disposition === 'retryable-start') {
|
|
886
|
+
return {
|
|
887
|
+
status: 'ready', changed: false, taskId: resolved.taskId, intent: null,
|
|
888
|
+
finalization: commitFinalizationView(inspection), error: null
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
if (inspection.disposition === 'prepared' && inspection.intentDigest) {
|
|
892
|
+
removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
|
|
893
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
894
|
+
}
|
|
895
|
+
if (!inspection.intentDigest) {
|
|
896
|
+
return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_EVIDENCE_MISSING', inspection.message, resolved.taskId);
|
|
897
|
+
}
|
|
898
|
+
return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
|
|
899
|
+
removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function abortCommitIntent(taskRef, input, options = {}) {
|
|
908
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
909
|
+
if (!resolved.ok)
|
|
910
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
911
|
+
try {
|
|
912
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
913
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
|
|
914
|
+
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
915
|
+
const currentHead = repositoryHead(gitRoot);
|
|
916
|
+
if (intent.phase !== 'prepared'
|
|
917
|
+
|| intent.pushEvidence !== null
|
|
918
|
+
|| input.expectedHead !== intent.baselineHead
|
|
919
|
+
|| currentHead !== intent.baselineHead) {
|
|
920
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent has side effects or repository drift', resolved.taskId);
|
|
921
|
+
}
|
|
922
|
+
removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
923
|
+
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
catch (error) {
|
|
927
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function statusCommitIntent(taskRef, options = {}) {
|
|
931
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
932
|
+
if (!resolved.ok)
|
|
933
|
+
return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
934
|
+
let gitRoot;
|
|
935
|
+
try {
|
|
936
|
+
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
return commitIntentFailed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
940
|
+
}
|
|
941
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
942
|
+
return {
|
|
943
|
+
status: 'ready',
|
|
944
|
+
changed: false,
|
|
945
|
+
taskId: resolved.taskId,
|
|
946
|
+
intent: inspection.intent ? commitIntentView(inspection.intent, inspection.currentHead) : null,
|
|
947
|
+
finalization: commitFinalizationView(inspection),
|
|
948
|
+
error: null
|
|
949
|
+
};
|
|
950
|
+
}
|
|
479
951
|
function statusOrchestration(taskRef, options = {}) {
|
|
480
952
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
481
953
|
if (!resolved.ok)
|
|
@@ -489,6 +961,15 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
489
961
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
490
962
|
if (!resolved.ok)
|
|
491
963
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
964
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
965
|
+
try {
|
|
966
|
+
readCommitIntent(resolved.taskDir, resolved.taskId);
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
return failed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
970
|
+
}
|
|
971
|
+
return failed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent blocks delegation preparation', resolved.taskId);
|
|
972
|
+
}
|
|
492
973
|
if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
|
|
493
974
|
return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
|
|
494
975
|
}
|
|
@@ -499,8 +980,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
499
980
|
return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
|
|
500
981
|
if (run.pendingDelegation)
|
|
501
982
|
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
502
|
-
const repositoryPending =
|
|
503
|
-
.flatMap((client) => matchingDelegations(client, () => true, options));
|
|
983
|
+
const repositoryPending = matchingDelegations(() => true, options);
|
|
504
984
|
if (repositoryPending.length > 0) {
|
|
505
985
|
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
506
986
|
}
|
|
@@ -525,7 +1005,11 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
525
1005
|
}
|
|
526
1006
|
let beforeFingerprint;
|
|
527
1007
|
try {
|
|
528
|
-
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(
|
|
1008
|
+
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1009
|
+
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
1010
|
+
stateRoot: resolved.repoRoot,
|
|
1011
|
+
taskId: resolved.taskId
|
|
1012
|
+
});
|
|
529
1013
|
}
|
|
530
1014
|
catch (error) {
|
|
531
1015
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
@@ -558,7 +1042,7 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
|
558
1042
|
if (!resolved.ok)
|
|
559
1043
|
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
560
1044
|
try {
|
|
561
|
-
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options));
|
|
1045
|
+
return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.prepare.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare.task', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)));
|
|
562
1046
|
}
|
|
563
1047
|
catch (error) {
|
|
564
1048
|
if (error instanceof TaskExecutionLockError)
|
|
@@ -566,7 +1050,7 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
|
566
1050
|
throw error;
|
|
567
1051
|
}
|
|
568
1052
|
}
|
|
569
|
-
function matchingDelegations(
|
|
1053
|
+
function matchingDelegations(predicate, options) {
|
|
570
1054
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
571
1055
|
const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
572
1056
|
if (!fs.existsSync(activeRoot))
|
|
@@ -578,13 +1062,13 @@ function matchingDelegations(client, predicate, options) {
|
|
|
578
1062
|
if (!run?.pendingDelegation)
|
|
579
1063
|
return [];
|
|
580
1064
|
const receipt = run.pendingDelegation;
|
|
581
|
-
return
|
|
1065
|
+
return predicate(receipt)
|
|
582
1066
|
? [{ taskId: entry.name, run }]
|
|
583
1067
|
: [];
|
|
584
1068
|
});
|
|
585
1069
|
}
|
|
586
1070
|
function uniqueMatchingDelegation(client, predicate, options) {
|
|
587
|
-
const matches = matchingDelegations(client
|
|
1071
|
+
const matches = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
|
|
588
1072
|
if (matches.length === 0)
|
|
589
1073
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
590
1074
|
if (matches.length > 1)
|
|
@@ -616,9 +1100,14 @@ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
|
616
1100
|
}
|
|
617
1101
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
618
1102
|
try {
|
|
1103
|
+
const gitRoot = gitRootFor(repoRoot, options);
|
|
619
1104
|
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
620
|
-
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(
|
|
621
|
-
|
|
1105
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1106
|
+
gitRoot,
|
|
1107
|
+
stateRoot: repoRoot,
|
|
1108
|
+
taskId: snapshotTaskId
|
|
1109
|
+
});
|
|
1110
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
622
1111
|
return sealOrchestrationDelegation(matched.taskId, {
|
|
623
1112
|
childId: event.childId,
|
|
624
1113
|
exitCode: 0,
|
|
@@ -700,7 +1189,7 @@ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
|
|
|
700
1189
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
701
1190
|
if (!resolved.ok)
|
|
702
1191
|
return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
|
|
703
|
-
const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt });
|
|
1192
|
+
const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt }, options.now);
|
|
704
1193
|
return {
|
|
705
1194
|
result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
|
|
706
1195
|
plan: {
|
|
@@ -713,24 +1202,6 @@ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
|
|
|
713
1202
|
function commitOrchestrationStageCompletion(plan) {
|
|
714
1203
|
saveRun(plan.taskDir, plan.updatedRun);
|
|
715
1204
|
}
|
|
716
|
-
function completeCommitOrchestrationStage(taskRef, agent, options = {}) {
|
|
717
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
718
|
-
if (!resolved.ok)
|
|
719
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
720
|
-
const run = readRun(resolved.taskDir);
|
|
721
|
-
if (!run)
|
|
722
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
723
|
-
const receipt = run.pendingDelegation;
|
|
724
|
-
if (!receipt || receipt.stage !== 'commit') {
|
|
725
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
|
|
726
|
-
}
|
|
727
|
-
return completeOrchestrationStage(taskRef, {
|
|
728
|
-
stage: receipt.stage,
|
|
729
|
-
round: receipt.round,
|
|
730
|
-
artifact: receipt.artifact,
|
|
731
|
-
agent
|
|
732
|
-
}, options);
|
|
733
|
-
}
|
|
734
1205
|
function sealOrchestrationDelegation(taskRef, event, options = {}) {
|
|
735
1206
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
736
1207
|
if (!resolved.ok)
|
|
@@ -780,5 +1251,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
|
780
1251
|
saveRun(resolved.taskDir, updated);
|
|
781
1252
|
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
782
1253
|
}
|
|
783
|
-
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion,
|
|
1254
|
+
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
|
|
784
1255
|
//# sourceMappingURL=orchestration.js.map
|