@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.
Files changed (175) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -123
  6. package/dist/lib/agent-clients/adapter.js +92 -2
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  12. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  13. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  14. package/dist/lib/agent-clients/registry.js +9 -1
  15. package/dist/lib/internal/sandbox-control.js +24 -0
  16. package/dist/lib/internal/task-activity.js +45 -13
  17. package/dist/lib/internal/task-ledger.js +12 -3
  18. package/dist/lib/internal/task-lifecycle.js +19 -1
  19. package/dist/lib/internal/task-orchestration.js +122 -18
  20. package/dist/lib/platform/github-client.js +19 -2
  21. package/dist/lib/platform/pr-checks.js +1 -1
  22. package/dist/lib/platform/pull-requests.js +47 -1
  23. package/dist/lib/platform/releases.js +13 -5
  24. package/dist/lib/process-data/index.js +239 -0
  25. package/dist/lib/process-data/normalize.js +198 -0
  26. package/dist/lib/process-data/privacy.js +33 -0
  27. package/dist/lib/process-data/reconcile.js +95 -0
  28. package/dist/lib/process-data/repair.js +58 -0
  29. package/dist/lib/process-data/sources.js +345 -0
  30. package/dist/lib/process-data/store.js +282 -0
  31. package/dist/lib/process-data/types.js +2 -0
  32. package/dist/lib/run/index.js +4 -3
  33. package/dist/lib/run/tui.js +7 -8
  34. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  35. package/dist/lib/sandbox/capture.js +7 -2
  36. package/dist/lib/sandbox/commands/create.js +109 -228
  37. package/dist/lib/sandbox/commands/enter.js +15 -47
  38. package/dist/lib/sandbox/commands/list-running.js +16 -6
  39. package/dist/lib/sandbox/commands/ls.js +10 -6
  40. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  41. package/dist/lib/sandbox/commands/refresh.js +4 -84
  42. package/dist/lib/sandbox/commands/rm.js +24 -3
  43. package/dist/lib/sandbox/commands/show.js +12 -3
  44. package/dist/lib/sandbox/commands/start.js +7 -4
  45. package/dist/lib/sandbox/config.js +2 -0
  46. package/dist/lib/sandbox/constants.js +6 -0
  47. package/dist/lib/sandbox/control/client.js +44 -0
  48. package/dist/lib/sandbox/control/protocol.js +35 -0
  49. package/dist/lib/sandbox/control/server.js +109 -0
  50. package/dist/lib/sandbox/dockerfile.js +36 -13
  51. package/dist/lib/sandbox/mounts.js +17 -4
  52. package/dist/lib/sandbox/recovery.js +128 -5
  53. package/dist/lib/sandbox/redaction.js +12 -0
  54. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  55. package/dist/lib/sandbox/workspace-identity.js +95 -0
  56. package/dist/lib/sandbox/workspace-view.js +103 -0
  57. package/dist/lib/task/activity-intent.js +257 -51
  58. package/dist/lib/task/commit-finalization.js +267 -0
  59. package/dist/lib/task/commit-intent.js +165 -0
  60. package/dist/lib/task/decision-intents.js +31 -17
  61. package/dist/lib/task/delegation-receipts.js +2 -2
  62. package/dist/lib/task/implementation-inputs.js +54 -7
  63. package/dist/lib/task/ledger-intents.js +35 -1
  64. package/dist/lib/task/orchestration.js +363 -28
  65. package/dist/lib/task/resolve-ref.js +41 -1
  66. package/lib/agent-clients/adapter.ts +126 -2
  67. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  68. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  69. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  71. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  72. package/lib/agent-clients/adapters/opencode.ts +18 -3
  73. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  74. package/lib/agent-clients/registry.ts +9 -1
  75. package/lib/internal/sandbox-control.ts +24 -0
  76. package/lib/internal/task-activity.ts +50 -14
  77. package/lib/internal/task-ledger.ts +12 -3
  78. package/lib/internal/task-lifecycle.ts +22 -1
  79. package/lib/internal/task-orchestration.ts +81 -11
  80. package/lib/platform/github-client.ts +20 -2
  81. package/lib/platform/pr-checks.ts +1 -1
  82. package/lib/platform/pull-requests.ts +49 -1
  83. package/lib/platform/releases.ts +16 -4
  84. package/lib/process-data/index.ts +224 -0
  85. package/lib/process-data/normalize.ts +202 -0
  86. package/lib/process-data/privacy.ts +41 -0
  87. package/lib/process-data/reconcile.ts +110 -0
  88. package/lib/process-data/repair.ts +64 -0
  89. package/lib/process-data/sources.ts +354 -0
  90. package/lib/process-data/store.ts +315 -0
  91. package/lib/process-data/types.ts +135 -0
  92. package/lib/run/index.ts +5 -3
  93. package/lib/run/tui.ts +11 -8
  94. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  95. package/lib/sandbox/capture.ts +9 -2
  96. package/lib/sandbox/commands/create.ts +126 -260
  97. package/lib/sandbox/commands/enter.ts +13 -59
  98. package/lib/sandbox/commands/list-running.ts +23 -6
  99. package/lib/sandbox/commands/ls.ts +22 -6
  100. package/lib/sandbox/commands/rebuild.ts +4 -3
  101. package/lib/sandbox/commands/refresh.ts +4 -126
  102. package/lib/sandbox/commands/rm.ts +24 -3
  103. package/lib/sandbox/commands/show.ts +29 -3
  104. package/lib/sandbox/commands/start.ts +6 -4
  105. package/lib/sandbox/config.ts +4 -0
  106. package/lib/sandbox/constants.ts +8 -0
  107. package/lib/sandbox/control/client.ts +55 -0
  108. package/lib/sandbox/control/protocol.ts +75 -0
  109. package/lib/sandbox/control/server.ts +117 -0
  110. package/lib/sandbox/dockerfile.ts +66 -13
  111. package/lib/sandbox/mounts.ts +24 -4
  112. package/lib/sandbox/recovery.ts +163 -6
  113. package/lib/sandbox/redaction.ts +14 -0
  114. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  115. package/lib/sandbox/tool-types.ts +16 -0
  116. package/lib/sandbox/workspace-identity.ts +123 -0
  117. package/lib/sandbox/workspace-view.ts +143 -0
  118. package/lib/task/activity-intent.ts +350 -61
  119. package/lib/task/commit-finalization.ts +356 -0
  120. package/lib/task/commit-intent.ts +230 -0
  121. package/lib/task/decision-intents.ts +32 -12
  122. package/lib/task/delegation-receipts.ts +2 -2
  123. package/lib/task/implementation-inputs.ts +62 -8
  124. package/lib/task/ledger-intents.ts +43 -3
  125. package/lib/task/orchestration.ts +501 -33
  126. package/lib/task/resolve-ref.ts +46 -1
  127. package/package.json +1 -1
  128. package/templates/.agents/rules/README.en.md +1 -1
  129. package/templates/.agents/rules/README.zh-CN.md +1 -1
  130. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  131. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  132. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  133. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  134. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  135. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  136. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  137. package/templates/.agents/rules/task-management.en.md +2 -0
  138. package/templates/.agents/rules/task-management.zh-CN.md +2 -0
  139. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.en.md +21 -11
  142. package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
  143. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  144. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  145. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  146. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  147. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  149. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  150. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  151. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  152. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  153. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  156. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  157. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  158. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  159. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  160. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  161. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  162. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  163. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  164. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  165. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  166. package/templates/.agents/templates/task.en.md +1 -1
  167. package/templates/.agents/templates/task.zh-CN.md +1 -1
  168. package/templates/.claude/commands/watch-pr.en.md +1 -1
  169. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  170. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  171. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  172. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  173. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  174. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  175. 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';
@@ -13,6 +14,9 @@ import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentCli
13
14
  import { isAgentClientId } from '../agent-clients/types.js';
14
15
  import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
15
16
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
17
+ import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
18
+ import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
19
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
16
20
  function supportsLifecycleDelegation(client) {
17
21
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
18
22
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -51,12 +55,70 @@ function atomicWrite(file, value) {
51
55
  function saveRun(taskDir, run) {
52
56
  atomicWrite(orchestrationPath(taskDir), run);
53
57
  }
54
- function withUpdatedRun(run, updates) {
55
- return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
58
+ function withUpdatedRun(run, updates, now) {
59
+ return Object.freeze({ ...run, ...updates, updatedAt: (now ?? (() => new Date().toISOString()))() });
56
60
  }
57
61
  function failed(code, message, taskId = null, details = {}) {
58
62
  return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
59
63
  }
64
+ function commitIntentFailed(code, message, taskId = null) {
65
+ return { status: 'failed', changed: false, taskId, intent: null, error: { code, message } };
66
+ }
67
+ function repositoryHead(repoRoot) {
68
+ return execFileSync('git', ['rev-parse', 'HEAD'], {
69
+ cwd: repoRoot,
70
+ encoding: 'utf8',
71
+ stdio: ['ignore', 'pipe', 'pipe']
72
+ }).trim();
73
+ }
74
+ function isAncestor(repoRoot, ancestor, descendant) {
75
+ return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
76
+ cwd: repoRoot,
77
+ stdio: 'ignore'
78
+ }).status === 0;
79
+ }
80
+ function commitIntentView(intent, currentHead) {
81
+ return {
82
+ mode: intent.mode,
83
+ phase: intent.phase,
84
+ baselineHead: intent.baselineHead,
85
+ currentHead,
86
+ committedHead: intent.committedHead,
87
+ pushEvidence: intent.pushEvidence,
88
+ runId: intent.orchestration?.runId ?? null,
89
+ receiptId: intent.orchestration?.receiptId ?? null
90
+ };
91
+ }
92
+ function commitFinalizationView(inspection) {
93
+ return {
94
+ disposition: inspection.disposition,
95
+ code: inspection.code,
96
+ message: inspection.message,
97
+ currentHead: inspection.currentHead,
98
+ committedHead: inspection.committedHead,
99
+ needsAnchor: inspection.needsAnchor,
100
+ needsLog: inspection.needsLog
101
+ };
102
+ }
103
+ function validCommitRun(run, taskId) {
104
+ return run !== null
105
+ && [1, 2].includes(run.schemaVersion)
106
+ && run.taskId === taskId
107
+ && ['running', 'paused', 'completed'].includes(run.status)
108
+ && Array.isArray(run.receipts)
109
+ && Object.prototype.hasOwnProperty.call(run, 'pendingDelegation')
110
+ && typeof run.commitAuthorization === 'object'
111
+ && run.commitAuthorization !== null
112
+ && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'issuedAt')
113
+ && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'consumedAt');
114
+ }
115
+ function mapCommitIntentError(error, taskId) {
116
+ if (error instanceof CommitIntentError)
117
+ return commitIntentFailed(error.code, error.message, taskId);
118
+ if (error instanceof TaskExecutionLockError)
119
+ return commitIntentFailed(error.code, error.message, taskId);
120
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), taskId);
121
+ }
60
122
  function validModel(value) {
61
123
  return typeof value === 'string' && value.length > 0 && value.trim() === value;
62
124
  }
@@ -476,6 +538,289 @@ function routeOrchestration(taskRef, options = {}) {
476
538
  }
477
539
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
478
540
  }
541
+ function beginCommitIntent(taskRef, input, options = {}) {
542
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
543
+ if (!resolved.ok)
544
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
545
+ try {
546
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
547
+ const currentHead = repositoryHead(resolved.repoRoot);
548
+ if (currentHead !== input.baselineHead) {
549
+ return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
550
+ }
551
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
552
+ try {
553
+ readCommitIntent(resolved.taskDir, resolved.taskId);
554
+ }
555
+ catch (error) {
556
+ return mapCommitIntentError(error, resolved.taskId);
557
+ }
558
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
559
+ }
560
+ const runFile = orchestrationPath(resolved.taskDir);
561
+ let run = null;
562
+ let sourceRunBytes = null;
563
+ if (fs.existsSync(runFile)) {
564
+ sourceRunBytes = fs.readFileSync(runFile, 'utf8');
565
+ try {
566
+ run = readRun(resolved.taskDir);
567
+ }
568
+ catch (error) {
569
+ return commitIntentFailed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
570
+ }
571
+ if (!validCommitRun(run, resolved.taskId)) {
572
+ return commitIntentFailed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
573
+ }
574
+ }
575
+ const now = (options.now ?? (() => new Date().toISOString()))();
576
+ const failAndPause = (code, message) => {
577
+ if (run?.status === 'running') {
578
+ saveRun(resolved.taskDir, withUpdatedRun(run, {
579
+ status: 'paused', pause: { code, message, recoverable: true }
580
+ }, () => now));
581
+ }
582
+ return commitIntentFailed(code, message, resolved.taskId);
583
+ };
584
+ let orchestration = null;
585
+ if (!input.orchestrated) {
586
+ if (run?.pendingDelegation) {
587
+ return commitIntentFailed('ORCHESTRATION_STANDALONE_BUSY', 'standalone commit is blocked by a pending orchestration delegation', resolved.taskId);
588
+ }
589
+ }
590
+ else {
591
+ if (!run || run.status !== 'running') {
592
+ return commitIntentFailed('ORCHESTRATION_RUN_NOT_RUNNING', 'orchestrated commit requires a running orchestration', resolved.taskId);
593
+ }
594
+ const receipt = run.pendingDelegation;
595
+ if (!receipt
596
+ || receipt.status !== 'activated'
597
+ || receipt.stage !== 'commit'
598
+ || receipt.round !== 1
599
+ || receipt.artifact !== 'commit'
600
+ || receipt.role !== 'executor') {
601
+ return failAndPause('ORCHESTRATION_PROVENANCE_MISMATCH', 'orchestrated commit requires one matching activated commit delegation');
602
+ }
603
+ if (!run.commitAuthorization.issuedAt || run.commitAuthorization.consumedAt) {
604
+ return failAndPause('ORCHESTRATION_COMMIT_AUTHORIZATION_INVALID', 'orchestrated commit requires an unconsumed one-use authorization');
605
+ }
606
+ const planned = planOrchestrationStageCompletion(taskRef, {
607
+ stage: 'commit', round: 1, artifact: 'commit', role: 'executor', agent: input.agent
608
+ }, { ...options, now: () => now });
609
+ if (planned.result.status === 'failed' || !planned.plan?.updatedRun.pendingDelegation) {
610
+ return failAndPause(planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH', planned.result.error?.message ?? 'commit delegation could not be completed');
611
+ }
612
+ const plannedRunBytes = serialize(planned.plan.updatedRun);
613
+ orchestration = {
614
+ runId: run.runId,
615
+ receiptId: receipt.id,
616
+ authorizationIssuedAt: run.commitAuthorization.issuedAt,
617
+ sourceRunDigest: digest(sourceRunBytes),
618
+ plannedRunDigest: digest(plannedRunBytes),
619
+ completionUpdatedAt: planned.plan.updatedRun.updatedAt,
620
+ plannedReceipt: planned.plan.updatedRun.pendingDelegation
621
+ };
622
+ }
623
+ const created = createCommitIntent(resolved.taskDir, {
624
+ taskId: resolved.taskId,
625
+ mode: input.orchestrated ? 'orchestrated' : 'standalone',
626
+ phase: 'prepared',
627
+ baselineHead: input.baselineHead,
628
+ committedHead: null,
629
+ pushEvidence: null,
630
+ orchestration,
631
+ createdAt: now,
632
+ updatedAt: now
633
+ }, { token: options.token });
634
+ return {
635
+ status: 'ready', changed: true, taskId: resolved.taskId,
636
+ intent: commitIntentView(created.intent, currentHead), token: created.token, error: null
637
+ };
638
+ });
639
+ }
640
+ catch (error) {
641
+ return mapCommitIntentError(error, resolved.taskId);
642
+ }
643
+ }
644
+ function checkpointCommitIntent(taskRef, input, options = {}) {
645
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
646
+ if (!resolved.ok)
647
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
648
+ try {
649
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
650
+ const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
651
+ const currentHead = repositoryHead(resolved.repoRoot);
652
+ if (currentHead !== input.head) {
653
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
654
+ }
655
+ if (input.kind === 'committed' && !isAncestor(resolved.repoRoot, intent.baselineHead, input.head)) {
656
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
657
+ }
658
+ if (input.kind === 'pushed') {
659
+ const expectedHead = intent.committedHead ?? intent.baselineHead;
660
+ if (input.head !== expectedHead || !input.remote || !input.ref) {
661
+ return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', 'pushed checkpoint requires matching remote, ref and HEAD', resolved.taskId);
662
+ }
663
+ }
664
+ const now = (options.now ?? (() => new Date().toISOString()))();
665
+ const updated = updateCommitIntent(resolved.taskDir, resolved.taskId, input.token, input.kind === 'committed'
666
+ ? { phase: 'committed', committedHead: input.head, updatedAt: now }
667
+ : {
668
+ phase: 'pushed', committedHead: intent.committedHead ?? intent.baselineHead, updatedAt: now,
669
+ pushEvidence: { remote: input.remote, ref: input.ref, head: input.head }
670
+ });
671
+ return {
672
+ status: 'ready', changed: true, taskId: resolved.taskId,
673
+ intent: commitIntentView(updated, currentHead), error: null
674
+ };
675
+ });
676
+ }
677
+ catch (error) {
678
+ return mapCommitIntentError(error, resolved.taskId);
679
+ }
680
+ }
681
+ function completeCommitOrchestration(resolved, intent, agent) {
682
+ if (intent.orchestration === null)
683
+ return null;
684
+ if (intent.orchestration.plannedReceipt.agent !== agent) {
685
+ return { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'completion agent does not match the planned receipt' };
686
+ }
687
+ const runFile = orchestrationPath(resolved.taskDir);
688
+ if (!fs.existsSync(runFile)) {
689
+ return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
690
+ }
691
+ const currentBytes = fs.readFileSync(runFile, 'utf8');
692
+ const currentDigest = digest(currentBytes);
693
+ if (currentDigest === intent.orchestration.sourceRunDigest) {
694
+ const run = readRun(resolved.taskDir);
695
+ if (!run)
696
+ return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
697
+ const plannedRun = Object.freeze({
698
+ ...run,
699
+ pendingDelegation: intent.orchestration.plannedReceipt,
700
+ updatedAt: intent.orchestration.completionUpdatedAt
701
+ });
702
+ if (digest(serialize(plannedRun)) !== intent.orchestration.plannedRunDigest) {
703
+ return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'planned orchestration bytes no longer match the intent' };
704
+ }
705
+ atomicWrite(runFile, plannedRun);
706
+ if (digest(fs.readFileSync(runFile)) !== intent.orchestration.plannedRunDigest) {
707
+ return { code: 'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', message: 'orchestration completion could not be verified' };
708
+ }
709
+ }
710
+ else if (currentDigest !== intent.orchestration.plannedRunDigest) {
711
+ return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'orchestration state changed after commit begin' };
712
+ }
713
+ return null;
714
+ }
715
+ function finalizeCommitIntentUnlocked(resolved, inspection, agent, remove) {
716
+ if (inspection.disposition !== 'recoverable' || !inspection.intent) {
717
+ return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_PENDING', inspection.message, resolved.taskId);
718
+ }
719
+ if (inspection.intent.orchestration !== null
720
+ && inspection.intent.orchestration.plannedReceipt.agent !== agent) {
721
+ return commitIntentFailed('ORCHESTRATION_PROVENANCE_MISMATCH', 'completion agent does not match the planned receipt', resolved.taskId);
722
+ }
723
+ const metadata = captureTaskWriteMetadata();
724
+ const taskPlan = planCommitTaskFinalization(resolved.taskDir, inspection, agent, metadata.timestamp);
725
+ const written = writeTask({
726
+ taskRef: resolved.taskId,
727
+ expectedState: 'active',
728
+ mutations: taskPlan.mutations
729
+ }, {
730
+ repoRoot: resolved.repoRoot,
731
+ metadataProvider: () => metadata
732
+ });
733
+ if (written.status === 'failed') {
734
+ return commitIntentFailed(written.error.code, written.error.message, resolved.taskId);
735
+ }
736
+ const orchestrationError = completeCommitOrchestration(resolved, inspection.intent, agent);
737
+ if (orchestrationError) {
738
+ return commitIntentFailed(orchestrationError.code, orchestrationError.message, resolved.taskId);
739
+ }
740
+ try {
741
+ remove();
742
+ }
743
+ catch (error) {
744
+ return commitIntentFailed('ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', error instanceof Error ? error.message : String(error), resolved.taskId);
745
+ }
746
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
747
+ }
748
+ function completeCommitIntent(taskRef, input, options = {}) {
749
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
750
+ if (!resolved.ok)
751
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
752
+ try {
753
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
754
+ readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
755
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
756
+ return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
757
+ removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
758
+ });
759
+ });
760
+ }
761
+ catch (error) {
762
+ return mapCommitIntentError(error, resolved.taskId);
763
+ }
764
+ }
765
+ function recoverCommitIntent(taskRef, input, options = {}) {
766
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
767
+ if (!resolved.ok)
768
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
769
+ try {
770
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
771
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
772
+ if (inspection.disposition === 'prepared' && inspection.intentDigest) {
773
+ removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
774
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
775
+ }
776
+ if (!inspection.intentDigest) {
777
+ return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_EVIDENCE_MISSING', inspection.message, resolved.taskId);
778
+ }
779
+ return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
780
+ removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
781
+ });
782
+ });
783
+ }
784
+ catch (error) {
785
+ return mapCommitIntentError(error, resolved.taskId);
786
+ }
787
+ }
788
+ function abortCommitIntent(taskRef, input, options = {}) {
789
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
790
+ if (!resolved.ok)
791
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
792
+ try {
793
+ return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
794
+ const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
795
+ const currentHead = repositoryHead(resolved.repoRoot);
796
+ if (intent.phase !== 'prepared'
797
+ || intent.pushEvidence !== null
798
+ || input.expectedHead !== intent.baselineHead
799
+ || currentHead !== intent.baselineHead) {
800
+ return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent has side effects or repository drift', resolved.taskId);
801
+ }
802
+ removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
803
+ return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
804
+ });
805
+ }
806
+ catch (error) {
807
+ return mapCommitIntentError(error, resolved.taskId);
808
+ }
809
+ }
810
+ function statusCommitIntent(taskRef, options = {}) {
811
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
812
+ if (!resolved.ok)
813
+ return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
814
+ const inspection = inspectCommitFinalization(resolved.taskDir, resolved.repoRoot, resolved.taskId);
815
+ return {
816
+ status: 'ready',
817
+ changed: false,
818
+ taskId: resolved.taskId,
819
+ intent: inspection.intent ? commitIntentView(inspection.intent, inspection.currentHead) : null,
820
+ finalization: commitFinalizationView(inspection),
821
+ error: null
822
+ };
823
+ }
479
824
  function statusOrchestration(taskRef, options = {}) {
480
825
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
481
826
  if (!resolved.ok)
@@ -489,6 +834,15 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
489
834
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
490
835
  if (!resolved.ok)
491
836
  return failed(resolved.code, resolved.message, resolved.taskId);
837
+ if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
838
+ try {
839
+ readCommitIntent(resolved.taskDir, resolved.taskId);
840
+ }
841
+ catch (error) {
842
+ return failed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
843
+ }
844
+ return failed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent blocks delegation preparation', resolved.taskId);
845
+ }
492
846
  if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
493
847
  return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
494
848
  }
@@ -499,8 +853,7 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
499
853
  return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
500
854
  if (run.pendingDelegation)
501
855
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
502
- const repositoryPending = ['claude-code', 'codex']
503
- .flatMap((client) => matchingDelegations(client, () => true, options));
856
+ const repositoryPending = matchingDelegations(() => true, options);
504
857
  if (repositoryPending.length > 0) {
505
858
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
506
859
  }
@@ -558,7 +911,7 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
558
911
  if (!resolved.ok)
559
912
  return failed(resolved.code, resolved.message, resolved.taskId);
560
913
  try {
561
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options));
914
+ return withTaskExecutionLock(resolved.repoRoot, '__repository__', 'task-orchestration.prepare.repository', () => withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.prepare.task', () => prepareOrchestrationDelegationUnlocked(taskRef, input, options)));
562
915
  }
563
916
  catch (error) {
564
917
  if (error instanceof TaskExecutionLockError)
@@ -566,7 +919,7 @@ function prepareOrchestrationDelegation(taskRef, input, options = {}) {
566
919
  throw error;
567
920
  }
568
921
  }
569
- function matchingDelegations(client, predicate, options) {
922
+ function matchingDelegations(predicate, options) {
570
923
  const repoRoot = options.repoRoot ?? process.cwd();
571
924
  const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
572
925
  if (!fs.existsSync(activeRoot))
@@ -578,13 +931,13 @@ function matchingDelegations(client, predicate, options) {
578
931
  if (!run?.pendingDelegation)
579
932
  return [];
580
933
  const receipt = run.pendingDelegation;
581
- return receipt.client === client && predicate(receipt)
934
+ return predicate(receipt)
582
935
  ? [{ taskId: entry.name, run }]
583
936
  : [];
584
937
  });
585
938
  }
586
939
  function uniqueMatchingDelegation(client, predicate, options) {
587
- const matches = matchingDelegations(client, predicate, options);
940
+ const matches = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
588
941
  if (matches.length === 0)
589
942
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
590
943
  if (matches.length > 1)
@@ -700,7 +1053,7 @@ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
700
1053
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
701
1054
  if (!resolved.ok)
702
1055
  return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
703
- const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt });
1056
+ const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt }, options.now);
704
1057
  return {
705
1058
  result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
706
1059
  plan: {
@@ -713,24 +1066,6 @@ function planOrchestrationStageCompletion(taskRef, event, options = {}) {
713
1066
  function commitOrchestrationStageCompletion(plan) {
714
1067
  saveRun(plan.taskDir, plan.updatedRun);
715
1068
  }
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
1069
  function sealOrchestrationDelegation(taskRef, event, options = {}) {
735
1070
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
736
1071
  if (!resolved.ok)
@@ -780,5 +1115,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
780
1115
  saveRun(resolved.taskDir, updated);
781
1116
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
782
1117
  }
783
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeCommitOrchestrationStage, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration };
1118
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, inspectOrchestrationStage, orchestrationPath, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusCommitIntent, statusOrchestration };
784
1119
  //# sourceMappingURL=orchestration.js.map
@@ -91,6 +91,46 @@ function enumerateTaskDirs(repoRoot) {
91
91
  }
92
92
  return out;
93
93
  }
94
+ /**
95
+ * Enumerate every task directory, including the cold YYYY/MM/DD archive.
96
+ * Invalid directory names, symlinks and entries without task.md are ignored;
97
+ * duplicate task ids fail closed because their identity would be ambiguous.
98
+ */
99
+ function enumerateAllTaskDirs(repoRoot) {
100
+ const out = [];
101
+ const seen = new Set();
102
+ const add = (taskId, taskDir, state) => {
103
+ if (!TASK_ID_RE.test(taskId))
104
+ return;
105
+ const stat = fs.lstatSync(taskDir);
106
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !fs.existsSync(path.join(taskDir, 'task.md')))
107
+ return;
108
+ if (seen.has(taskId))
109
+ throw new Error(`duplicate task identity: ${taskId}`);
110
+ seen.add(taskId);
111
+ out.push({ taskId, taskDir, state });
112
+ };
113
+ for (const state of FLAT_WORKSPACE_DIRS) {
114
+ const base = path.join(repoRoot, '.agents', 'workspace', state);
115
+ if (!fs.existsSync(base))
116
+ continue;
117
+ for (const taskId of fs.readdirSync(base).sort())
118
+ add(taskId, path.join(base, taskId), state);
119
+ }
120
+ const archive = path.join(repoRoot, '.agents', 'workspace', 'archive');
121
+ for (const year of listSortedNumeric(archive, 4).reverse()) {
122
+ const yearDir = path.join(archive, year);
123
+ for (const month of listSortedNumeric(yearDir, 2).reverse()) {
124
+ const monthDir = path.join(yearDir, month);
125
+ for (const day of listSortedNumeric(monthDir, 2).reverse()) {
126
+ const dayDir = path.join(monthDir, day);
127
+ for (const taskId of fs.readdirSync(dayDir).sort())
128
+ add(taskId, path.join(dayDir, taskId), 'archive');
129
+ }
130
+ }
131
+ }
132
+ return out;
133
+ }
94
134
  function locateHotTaskDirs(repoRoot, taskId) {
95
135
  return FLAT_WORKSPACE_DIRS.flatMap((state) => {
96
136
  const taskDir = path.join(repoRoot, '.agents', 'workspace', state, taskId);
@@ -266,5 +306,5 @@ function resolveTaskContext(taskRef, options = {}) {
266
306
  const match = matches[0];
267
307
  return { ok: true, repoRoot, ...match, state: 'active' };
268
308
  }
269
- export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateTaskDirs, locateHotTaskDirs, TASK_ID_RE };
309
+ export { resolveTaskRef, resolveTaskContext, detectRepoRoot, enumerateTaskDirs, enumerateAllTaskDirs, locateHotTaskDirs, TASK_ID_RE };
270
310
  //# sourceMappingURL=resolve-ref.js.map