@fitlab-ai/agent-infra 0.9.7 → 0.9.9

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 (195) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +11 -1
  4. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
  12. package/dist/lib/decide.js +68 -14
  13. package/dist/lib/git/workflow.js +10 -1
  14. package/dist/lib/init.js +1 -1
  15. package/dist/lib/internal/codex-lifecycle.js +19 -5
  16. package/dist/lib/internal/codex-sandbox-controller.js +6 -13
  17. package/dist/lib/internal/git-workflow.js +5 -7
  18. package/dist/lib/internal/platform-pr.js +20 -7
  19. package/dist/lib/internal/release-workflow.js +3 -4
  20. package/dist/lib/internal/sandbox-control.js +79 -2
  21. package/dist/lib/internal/task-activity.js +57 -7
  22. package/dist/lib/internal/task-event.js +57 -4
  23. package/dist/lib/internal/task-finalization.js +54 -0
  24. package/dist/lib/internal/task-ledger.js +59 -5
  25. package/dist/lib/internal/task-lifecycle.js +59 -8
  26. package/dist/lib/internal/task-orchestration.js +161 -288
  27. package/dist/lib/internal/task-override.js +131 -0
  28. package/dist/lib/internal/task-review.js +64 -6
  29. package/dist/lib/internal/task-verify.js +65 -3
  30. package/dist/lib/internal/task-warning.js +57 -4
  31. package/dist/lib/platform/pr-summary.js +53 -13
  32. package/dist/lib/platform/pull-requests.js +205 -41
  33. package/dist/lib/runtime/agent-runtime.js +56 -0
  34. package/dist/lib/sandbox/commands/create.js +389 -311
  35. package/dist/lib/sandbox/commands/rm.js +316 -55
  36. package/dist/lib/sandbox/control/client.js +190 -6
  37. package/dist/lib/sandbox/control/container-identity.js +1 -1
  38. package/dist/lib/sandbox/control/controller-registration.js +273 -0
  39. package/dist/lib/sandbox/control/executor.js +145 -5
  40. package/dist/lib/sandbox/control/lifecycle.js +395 -73
  41. package/dist/lib/sandbox/control/protocol.js +67 -6
  42. package/dist/lib/sandbox/control/state.js +27 -11
  43. package/dist/lib/sandbox/engine.js +14 -7
  44. package/dist/lib/sandbox/index.js +5 -3
  45. package/dist/lib/sandbox/managed-fs.js +5 -0
  46. package/dist/lib/sandbox/mounts.js +20 -5
  47. package/dist/lib/sandbox/recovery.js +78 -10
  48. package/dist/lib/sandbox/task-resolver.js +50 -6
  49. package/dist/lib/sandbox/workspace-identity.js +27 -0
  50. package/dist/lib/sandbox/workspace-view.js +62 -40
  51. package/dist/lib/sandbox/worktree-safety.js +215 -2
  52. package/dist/lib/server/process-state.js +51 -1
  53. package/dist/lib/task/activity-intent.js +4 -1
  54. package/dist/lib/task/codex-orchestration.js +158 -33
  55. package/dist/lib/task/commands/status.js +59 -1
  56. package/dist/lib/task/commit-operation.js +335 -0
  57. package/dist/lib/task/commit-policy.js +18 -0
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +279 -17
  60. package/dist/lib/task/events.js +9 -5
  61. package/dist/lib/task/finalization.js +567 -0
  62. package/dist/lib/task/guard-override.js +31 -0
  63. package/dist/lib/task/human-override.js +1240 -0
  64. package/dist/lib/task/ledger-intents.js +6 -3
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/operation-outcome.js +33 -0
  67. package/dist/lib/task/orchestration.js +199 -719
  68. package/dist/lib/task/review-finalization.js +3 -2
  69. package/dist/lib/task/short-id.js +59 -1
  70. package/dist/lib/task/task-execution-lock.js +6 -1
  71. package/dist/lib/task/verification-engine.js +138 -59
  72. package/dist/lib/task/verification.js +15 -2
  73. package/dist/lib/task/workflow-warning-intents.js +54 -3
  74. package/dist/lib/task/write.js +2 -1
  75. package/dist/lifecycle-build-manifest.json +3 -3
  76. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  77. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
  78. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
  80. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  82. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  83. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
  84. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
  85. package/lib/decide.ts +59 -14
  86. package/lib/git/workflow.ts +11 -2
  87. package/lib/init.ts +1 -1
  88. package/lib/internal/codex-lifecycle.ts +18 -5
  89. package/lib/internal/codex-sandbox-controller.ts +6 -12
  90. package/lib/internal/git-workflow.ts +5 -6
  91. package/lib/internal/platform-pr.ts +14 -7
  92. package/lib/internal/release-workflow.ts +3 -4
  93. package/lib/internal/sandbox-control.ts +92 -2
  94. package/lib/internal/task-activity.ts +46 -6
  95. package/lib/internal/task-event.ts +53 -4
  96. package/lib/internal/task-finalization.ts +68 -0
  97. package/lib/internal/task-ledger.ts +48 -4
  98. package/lib/internal/task-lifecycle.ts +56 -12
  99. package/lib/internal/task-orchestration.ts +32 -107
  100. package/lib/internal/task-override.ts +138 -0
  101. package/lib/internal/task-review.ts +51 -6
  102. package/lib/internal/task-verify.ts +52 -3
  103. package/lib/internal/task-warning.ts +44 -3
  104. package/lib/platform/pr-summary.ts +65 -14
  105. package/lib/platform/pull-requests.ts +211 -41
  106. package/lib/runtime/agent-runtime.ts +76 -0
  107. package/lib/sandbox/commands/create.ts +88 -4
  108. package/lib/sandbox/commands/rm.ts +413 -61
  109. package/lib/sandbox/config.ts +2 -1
  110. package/lib/sandbox/control/client.ts +255 -6
  111. package/lib/sandbox/control/container-identity.ts +3 -3
  112. package/lib/sandbox/control/controller-registration.ts +347 -0
  113. package/lib/sandbox/control/executor.ts +172 -6
  114. package/lib/sandbox/control/lifecycle.ts +465 -77
  115. package/lib/sandbox/control/protocol.ts +85 -9
  116. package/lib/sandbox/control/state.ts +35 -18
  117. package/lib/sandbox/engine.ts +20 -9
  118. package/lib/sandbox/index.ts +5 -3
  119. package/lib/sandbox/managed-fs.ts +5 -0
  120. package/lib/sandbox/mounts.ts +23 -5
  121. package/lib/sandbox/recovery.ts +90 -9
  122. package/lib/sandbox/task-resolver.ts +58 -7
  123. package/lib/sandbox/workspace-identity.ts +50 -2
  124. package/lib/sandbox/workspace-view.ts +73 -43
  125. package/lib/sandbox/worktree-safety.ts +236 -2
  126. package/lib/server/process-state.ts +48 -1
  127. package/lib/task/activity-intent.ts +4 -2
  128. package/lib/task/codex-orchestration.ts +167 -33
  129. package/lib/task/commands/status.ts +78 -1
  130. package/lib/task/commit-operation.ts +430 -0
  131. package/lib/task/commit-policy.ts +30 -0
  132. package/lib/task/decision-intents.ts +4 -2
  133. package/lib/task/delegation-receipts.ts +304 -19
  134. package/lib/task/events.ts +10 -5
  135. package/lib/task/finalization.ts +719 -0
  136. package/lib/task/guard-override.ts +50 -0
  137. package/lib/task/human-override.ts +1503 -0
  138. package/lib/task/ledger-intents.ts +6 -3
  139. package/lib/task/lifecycle.ts +71 -14
  140. package/lib/task/operation-outcome.ts +48 -0
  141. package/lib/task/orchestration.ts +239 -922
  142. package/lib/task/review-finalization.ts +6 -2
  143. package/lib/task/short-id.ts +65 -1
  144. package/lib/task/task-execution-lock.ts +10 -1
  145. package/lib/task/verification-engine.ts +145 -64
  146. package/lib/task/verification.ts +18 -3
  147. package/lib/task/workflow-warning-intents.ts +61 -4
  148. package/lib/task/write.ts +4 -1
  149. package/package.json +2 -2
  150. package/templates/.agents/QUICKSTART.en.md +2 -2
  151. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  152. package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
  153. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
  154. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
  155. package/templates/.agents/rules/task-management.en.md +2 -2
  156. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  157. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  158. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  159. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  160. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  161. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  162. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  163. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  164. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  165. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  166. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  167. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  168. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  170. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  171. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  174. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  175. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  176. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  177. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  178. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  179. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  180. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  181. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  182. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  183. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  184. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  185. package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
  186. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
  187. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
  188. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
  189. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  190. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  191. package/templates/.codex/hooks.json +4 -4
  192. package/dist/lib/task/commit-finalization.js +0 -305
  193. package/dist/lib/task/commit-intent.js +0 -165
  194. package/lib/task/commit-finalization.ts +0 -419
  195. package/lib/task/commit-intent.ts +0 -230
@@ -1,14 +1,14 @@
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';
5
4
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
6
5
  import { parseReviewSummary } from './review-artifacts.js';
7
6
  import { inspectArtifactDirectory } from './artifact-lifecycle.js';
8
7
  import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
8
  import { resolveTaskRef } from './resolve-ref.js';
10
- import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
9
+ import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
10
  import { normalizeAgentClients } from '../agent-clients/config.js';
11
+ import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
12
12
  import { inspectPlatformPullRequest } from '../platform/pull-requests.js';
13
13
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
14
14
  import { isAgentClientId } from '../agent-clients/types.js';
@@ -16,10 +16,6 @@ import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnaps
16
16
  import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
17
17
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
18
18
  import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.js';
19
- import { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent } from './commit-intent.js';
20
- import { inspectCommitFinalization, planCommitTaskFinalization } from './commit-finalization.js';
21
- import { captureTaskWriteMetadata, writeTask } from './write.js';
22
- import { appendActivityEntry, commitAttemptStartedNote, locateActivityLog, pairEntries } from './activity-log.js';
23
19
  function supportsLifecycleDelegation(client) {
24
20
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
25
21
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -29,26 +25,138 @@ function supportsLifecycleDelegation(client) {
29
25
  function orchestrationPath(taskDir) {
30
26
  return path.join(taskDir, 'orchestration.json');
31
27
  }
28
+ const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; finish or clear active runs before upgrading';
29
+ class OrchestrationStateError extends Error {
30
+ code = 'ORCHESTRATION_STATE_INVALID';
31
+ taskId;
32
+ constructor(taskId = null) {
33
+ super(ORCHESTRATION_STATE_INVALID_MESSAGE);
34
+ this.name = 'OrchestrationStateError';
35
+ this.taskId = taskId;
36
+ }
37
+ }
38
+ function isRecord(value) {
39
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
40
+ }
41
+ function hasExactKeys(value, keys) {
42
+ if (!isRecord(value))
43
+ return false;
44
+ const actual = Object.keys(value);
45
+ return actual.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
46
+ }
47
+ function exactText(value) {
48
+ return typeof value === 'string' && value.length > 0 && value.trim() === value;
49
+ }
50
+ function nullableText(value) {
51
+ return value === null || exactText(value);
52
+ }
53
+ function isPause(value) {
54
+ return hasExactKeys(value, ['code', 'message', 'recoverable'])
55
+ && exactText(value.code)
56
+ && exactText(value.message)
57
+ && typeof value.recoverable === 'boolean';
58
+ }
59
+ function isModelPolicySource(value) {
60
+ return hasExactKeys(value, ['kind', 'client', 'resolvedAt'])
61
+ && ['explicit', 'project-config'].includes(value.kind)
62
+ && isAgentClientId(value.client)
63
+ && exactText(value.resolvedAt);
64
+ }
65
+ function isModelPolicy(value) {
66
+ if (!hasExactKeys(value, ['executor', 'reviewer']))
67
+ return false;
68
+ return [value.executor, value.reviewer].every((role) => hasExactKeys(role, ['model', 'reasoningEffort'])
69
+ && exactText(role.model)
70
+ && exactText(role.reasoningEffort));
71
+ }
72
+ const RECOVERY_GUARD_KEYS = [
73
+ 'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
74
+ 'commitAuthorizationUnused', 'completionEvidenceAbsent'
75
+ ];
76
+ function isRecovery(value) {
77
+ if (!hasExactKeys(value, [
78
+ 'code', 'recoveredAt', 'previousStatus', 'previousPause', 'client', 'guards', 'resultingStatus'
79
+ ]))
80
+ return false;
81
+ const guards = value.guards;
82
+ return value.code === 'CLIENT_CAPABILITY_ENABLED'
83
+ && exactText(value.recoveredAt)
84
+ && value.previousStatus === 'paused'
85
+ && isPause(value.previousPause)
86
+ && value.previousPause.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
87
+ && value.client === 'claude-code'
88
+ && hasExactKeys(guards, RECOVERY_GUARD_KEYS)
89
+ && guards.stepCount === 0
90
+ && guards.nextStage === null
91
+ && guards.baselineEmpty === true
92
+ && guards.receiptCount === 0
93
+ && guards.pendingDelegation === false
94
+ && guards.commitAuthorizationUnused === true
95
+ && guards.completionEvidenceAbsent === true
96
+ && value.resultingStatus === 'running';
97
+ }
98
+ function isCompletionEvidence(value) {
99
+ return hasExactKeys(value, [
100
+ 'kind', 'observedAt', 'head', 'headTree', 'worktreeTree', 'lastReviewedCommit', 'prNumber', 'prHead'
101
+ ])
102
+ && value.kind === 'reviewed-head-clean'
103
+ && exactText(value.observedAt)
104
+ && exactText(value.head)
105
+ && exactText(value.headTree)
106
+ && exactText(value.worktreeTree)
107
+ && exactText(value.lastReviewedCommit)
108
+ && Number.isSafeInteger(value.prNumber)
109
+ && value.prNumber > 0
110
+ && exactText(value.prHead);
111
+ }
112
+ const ORCHESTRATION_RUN_KEYS = [
113
+ 'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
114
+ 'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
115
+ 'pause', 'commitAuthorization', 'completionEvidence', 'createdAt', 'updatedAt'
116
+ ];
117
+ function parseOrchestrationRun(value, expectedTaskId) {
118
+ if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS)
119
+ || !exactText(value.taskId)
120
+ || (expectedTaskId !== undefined && value.taskId !== expectedTaskId)
121
+ || !exactText(value.runId)
122
+ || !['running', 'paused', 'completed'].includes(value.status)
123
+ || !(value.nextStage === null || ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(value.nextStage))
124
+ || !Number.isSafeInteger(value.stepCount) || value.stepCount < 0
125
+ || !Number.isSafeInteger(value.maxSteps) || value.maxSteps < 1
126
+ || !isModelPolicy(value.modelPolicy)
127
+ || !isModelPolicySource(value.modelPolicySource)
128
+ || !Array.isArray(value.recoveryHistory) || !value.recoveryHistory.every(isRecovery)
129
+ || typeof value.baseline !== 'string'
130
+ || !(value.pendingDelegation === null || isDelegationReceipt(value.pendingDelegation))
131
+ || !Array.isArray(value.receipts) || !value.receipts.every(isDelegationReceipt)
132
+ || !(value.pause === null || isPause(value.pause))
133
+ || !hasExactKeys(value.commitAuthorization, ['issuedAt', 'consumedAt'])
134
+ || !nullableText(value.commitAuthorization.issuedAt)
135
+ || !nullableText(value.commitAuthorization.consumedAt)
136
+ || !(value.completionEvidence === null || isCompletionEvidence(value.completionEvidence))
137
+ || !exactText(value.createdAt)
138
+ || !exactText(value.updatedAt)) {
139
+ throw new OrchestrationStateError();
140
+ }
141
+ const receipts = [...value.receipts, ...(value.pendingDelegation ? [value.pendingDelegation] : [])];
142
+ if (receipts.some((receipt) => receipt.taskId !== value.taskId || receipt.runId !== value.runId)) {
143
+ throw new OrchestrationStateError();
144
+ }
145
+ return value;
146
+ }
32
147
  function readRun(taskDir) {
33
148
  const file = orchestrationPath(taskDir);
34
149
  if (!fs.existsSync(file))
35
150
  return null;
36
- const run = JSON.parse(fs.readFileSync(file, 'utf8'));
37
- if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy))
38
- return run;
39
- return {
40
- ...run,
41
- modelPolicy: {
42
- executor: {
43
- model: run.modelPolicy.executor.model,
44
- reasoningEffort: run.modelPolicy.executor.reasoningEffort
45
- },
46
- reviewer: {
47
- model: run.modelPolicy.reviewer.model,
48
- reasoningEffort: run.modelPolicy.reviewer.reasoningEffort
49
- }
50
- }
51
- };
151
+ const taskId = path.basename(taskDir);
152
+ try {
153
+ return parseOrchestrationRun(JSON.parse(fs.readFileSync(file, 'utf8')), taskId);
154
+ }
155
+ catch (error) {
156
+ if (error instanceof OrchestrationStateError)
157
+ throw new OrchestrationStateError(taskId);
158
+ throw new OrchestrationStateError(taskId);
159
+ }
52
160
  }
53
161
  function atomicWrite(file, value) {
54
162
  const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
@@ -64,70 +172,11 @@ function withUpdatedRun(run, updates, now) {
64
172
  function failed(code, message, taskId = null, details = {}) {
65
173
  return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
66
174
  }
67
- function commitIntentFailed(code, message, taskId = null) {
68
- return { status: 'failed', changed: false, taskId, intent: null, error: { code, message } };
69
- }
70
- function repositoryHead(repoRoot) {
71
- return execFileSync('git', ['rev-parse', 'HEAD'], {
72
- cwd: repoRoot,
73
- encoding: 'utf8',
74
- stdio: ['ignore', 'pipe', 'pipe']
75
- }).trim();
76
- }
77
175
  function gitRootFor(stateRoot, options) {
78
176
  if (!options.gitWorktreeRoot)
79
177
  return stateRoot;
80
178
  return assertGitRepositoryBinding(stateRoot, options.gitWorktreeRoot).worktreeRoot;
81
179
  }
82
- function isAncestor(repoRoot, ancestor, descendant) {
83
- return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
84
- cwd: repoRoot,
85
- stdio: 'ignore'
86
- }).status === 0;
87
- }
88
- function commitIntentView(intent, currentHead) {
89
- return {
90
- mode: intent.mode,
91
- phase: intent.phase,
92
- baselineHead: intent.baselineHead,
93
- currentHead,
94
- committedHead: intent.committedHead,
95
- pushEvidence: intent.pushEvidence,
96
- runId: intent.orchestration?.runId ?? null,
97
- receiptId: intent.orchestration?.receiptId ?? null
98
- };
99
- }
100
- function commitFinalizationView(inspection) {
101
- return {
102
- disposition: inspection.disposition,
103
- code: inspection.code,
104
- message: inspection.message,
105
- currentHead: inspection.currentHead,
106
- committedHead: inspection.committedHead,
107
- needsAnchor: inspection.needsAnchor,
108
- needsLog: inspection.needsLog,
109
- attempt: inspection.attempt
110
- };
111
- }
112
- function validCommitRun(run, taskId) {
113
- return run !== null
114
- && [1, 2, 3].includes(run.schemaVersion)
115
- && run.taskId === taskId
116
- && ['running', 'paused', 'completed'].includes(run.status)
117
- && Array.isArray(run.receipts)
118
- && Object.prototype.hasOwnProperty.call(run, 'pendingDelegation')
119
- && typeof run.commitAuthorization === 'object'
120
- && run.commitAuthorization !== null
121
- && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'issuedAt')
122
- && Object.prototype.hasOwnProperty.call(run.commitAuthorization, 'consumedAt');
123
- }
124
- function mapCommitIntentError(error, taskId) {
125
- if (error instanceof CommitIntentError)
126
- return commitIntentFailed(error.code, error.message, taskId);
127
- if (error instanceof TaskExecutionLockError)
128
- return commitIntentFailed(error.code, error.message, taskId);
129
- return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), taskId);
130
- }
131
180
  function validModel(value) {
132
181
  return typeof value === 'string' && value.length > 0 && value.trim() === value;
133
182
  }
@@ -147,11 +196,8 @@ function sameModelPolicy(left, right) {
147
196
  && left.reviewer.model === right.reviewer.model
148
197
  && left.reviewer.reasoningEffort === right.reviewer.reasoningEffort;
149
198
  }
150
- function isV2Policy(policy) {
151
- return typeof policy?.executor === 'object' && typeof policy?.reviewer === 'object';
152
- }
153
199
  function rolePolicy(run, role) {
154
- return isV2Policy(run.modelPolicy) ? run.modelPolicy[role] : null;
200
+ return run.modelPolicy[role];
155
201
  }
156
202
  function resolveProjectPolicy(repoRoot, client) {
157
203
  const configPath = path.join(repoRoot, '.agents', '.airc.json');
@@ -160,26 +206,10 @@ function resolveProjectPolicy(repoRoot, client) {
160
206
  const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
161
207
  return normalizeAgentClients(raw).state[client].orchestration;
162
208
  }
163
- function canRecoverCodexUnsupportedPause(run, taskDir) {
164
- return run.schemaVersion === 2
165
- && run.status === 'paused'
209
+ function canRecoverClaudeCodeUnsupportedPause(run, taskDir) {
210
+ return run.status === 'paused'
166
211
  && run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
167
- && run.modelPolicySource?.client === 'codex'
168
- && run.stepCount === 0
169
- && run.nextStage === null
170
- && run.baseline === ''
171
- && run.pendingDelegation === null
172
- && run.receipts.length === 0
173
- && run.commitAuthorization?.issuedAt === null
174
- && run.commitAuthorization?.consumedAt === null
175
- && run.completionEvidence == null
176
- && !fs.existsSync(commitIntentPath(taskDir))
177
- && (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
178
- }
179
- function canMigrateRecoverableV2Pause(run, taskDir) {
180
- return run.schemaVersion === 2
181
- && run.status === 'paused'
182
- && run.pause?.recoverable === true
212
+ && run.modelPolicySource?.client === 'claude-code'
183
213
  && run.stepCount === 0
184
214
  && run.nextStage === null
185
215
  && run.baseline === ''
@@ -188,7 +218,7 @@ function canMigrateRecoverableV2Pause(run, taskDir) {
188
218
  && run.commitAuthorization?.issuedAt === null
189
219
  && run.commitAuthorization?.consumedAt === null
190
220
  && run.completionEvidence == null
191
- && !fs.existsSync(commitIntentPath(taskDir));
221
+ && run.recoveryHistory.length === 0;
192
222
  }
193
223
  function beginOrResumeOrchestration(taskRef, options = {}) {
194
224
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -205,193 +235,57 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
205
235
  return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
206
236
  }
207
237
  if (existing) {
208
- if (![1, 2, 3].includes(existing.schemaVersion)
209
- || !Array.isArray(existing.receipts)
210
- || !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')) {
211
- return failed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
212
- }
213
- if (existing.status === 'completed' && existing.schemaVersion === 1) {
238
+ if (existing.status === 'completed') {
214
239
  return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
215
240
  }
216
- if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
217
- const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
218
- if (policyError
219
- || !existing.modelPolicySource
220
- || !isAgentClientId(existing.modelPolicySource.client)
221
- || !Array.isArray(existing.recoveryHistory)) {
222
- return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
223
- }
224
- if (existing.status === 'completed') {
225
- return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
226
- }
227
- if (existing.schemaVersion === 2 && existing.status === 'running') {
228
- return failed('ORCHESTRATION_STATE_INVALID', 'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3', resolved.taskId);
229
- }
230
- if (existing.modelPolicySource.client !== options.client) {
231
- return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
232
- }
233
- if (options.modelPolicy) {
234
- const suppliedError = validateModelPolicy(options.modelPolicy);
235
- if (suppliedError)
236
- return failed(suppliedError.code, suppliedError.message, resolved.taskId);
237
- if (!sameModelPolicy(existing.modelPolicy, options.modelPolicy)) {
238
- return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
239
- }
240
- }
241
- if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
242
- if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
243
- return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
244
- }
245
- const now = (options.now ?? (() => new Date().toISOString()))();
246
- const previousPause = existing.pause;
247
- const recovery = {
248
- code: 'CLIENT_CAPABILITY_ENABLED',
249
- recoveredAt: now,
250
- previousSchemaVersion: 2,
251
- previousStatus: 'paused',
252
- previousPause,
253
- client: 'codex',
254
- guards: {
255
- stepCount: 0,
256
- nextStage: null,
257
- baselineEmpty: true,
258
- receiptCount: 0,
259
- pendingDelegation: false,
260
- commitAuthorizationUnused: true,
261
- completionEvidenceAbsent: true,
262
- commitIntentAbsent: true
263
- },
264
- resultingStatus: 'running'
265
- };
266
- const resumed = withUpdatedRun(existing, {
267
- schemaVersion: 3,
268
- status: 'running',
269
- pause: null,
270
- recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
271
- }, () => now);
272
- saveRun(resolved.taskDir, resumed);
273
- return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
274
- }
275
- if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
276
- if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
277
- const paused = withUpdatedRun(existing, {
278
- pause: {
279
- code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
280
- message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
281
- recoverable: false
282
- }
283
- });
284
- saveRun(resolved.taskDir, paused);
285
- return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
286
- }
287
- const now = (options.now ?? (() => new Date().toISOString()))();
288
- const recovery = existing.schemaVersion === 2 ? {
289
- code: 'SCHEMA_V3_MIGRATED',
290
- recoveredAt: now,
291
- previousSchemaVersion: 2,
292
- previousStatus: 'paused',
293
- previousPause: existing.pause,
294
- guards: {
295
- stepCount: 0,
296
- nextStage: null,
297
- baselineEmpty: true,
298
- receiptCount: 0,
299
- pendingDelegation: false,
300
- commitAuthorizationUnused: true,
301
- completionEvidenceAbsent: true,
302
- commitIntentAbsent: true
303
- },
304
- resultingStatus: 'running'
305
- } : null;
306
- const resumed = withUpdatedRun(existing, {
307
- schemaVersion: 3,
308
- status: 'running',
309
- pause: null,
310
- ...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
311
- }, () => now);
312
- saveRun(resolved.taskDir, resumed);
313
- return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
314
- }
315
- return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
241
+ if (existing.modelPolicySource.client !== options.client) {
242
+ return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
316
243
  }
317
- let policy = options.modelPolicy;
318
- let sourceKind = 'explicit';
319
- if (!policy) {
320
- try {
321
- policy = resolveProjectPolicy(resolved.repoRoot, options.client);
322
- sourceKind = 'project-config';
323
- }
324
- catch (error) {
325
- return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
244
+ if (options.modelPolicy) {
245
+ const suppliedError = validateModelPolicy(options.modelPolicy);
246
+ if (suppliedError)
247
+ return failed(suppliedError.code, suppliedError.message, resolved.taskId);
248
+ if (!sameModelPolicy(existing.modelPolicy, options.modelPolicy)) {
249
+ return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
326
250
  }
327
251
  }
328
- const policyError = validateModelPolicy(policy);
329
- if (policyError) {
330
- return failed(policyError.code, policyError.message, resolved.taskId, {
331
- client: options.client,
332
- missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
333
- modelSelectionContext: getAgentClientModelSelection(options.client)
334
- });
335
- }
336
- if (existing.pendingDelegation !== null) {
337
- const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_DELEGATION_BUSY';
338
- if (alreadyPaused)
339
- return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
340
- const paused = withUpdatedRun(existing, {
341
- status: 'paused',
342
- pause: { code: 'ORCHESTRATION_DELEGATION_BUSY', message: 'legacy run has a pending delegation', recoverable: false }
343
- });
344
- saveRun(resolved.taskDir, paused);
345
- return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
346
- }
347
- if (existing.receipts.length > 0) {
348
- const alreadyPaused = existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED';
349
- if (alreadyPaused)
252
+ if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
253
+ if (existing.modelPolicySource.client !== 'claude-code'
254
+ || !canRecoverClaudeCodeUnsupportedPause(existing, resolved.taskDir)) {
350
255
  return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
351
- const paused = withUpdatedRun(existing, {
352
- status: 'paused',
353
- pause: {
354
- code: 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED',
355
- message: 'legacy receipts do not contain actual reasoning-effort evidence',
356
- recoverable: false
357
- }
358
- });
359
- saveRun(resolved.taskDir, paused);
360
- return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
256
+ }
257
+ const now = (options.now ?? (() => new Date().toISOString()))();
258
+ const recovery = {
259
+ code: 'CLIENT_CAPABILITY_ENABLED',
260
+ recoveredAt: now,
261
+ previousStatus: 'paused',
262
+ previousPause: existing.pause,
263
+ client: 'claude-code',
264
+ guards: {
265
+ stepCount: 0,
266
+ nextStage: null,
267
+ baselineEmpty: true,
268
+ receiptCount: 0,
269
+ pendingDelegation: false,
270
+ commitAuthorizationUnused: true,
271
+ completionEvidenceAbsent: true
272
+ },
273
+ resultingStatus: 'running'
274
+ };
275
+ const resumed = withUpdatedRun(existing, {
276
+ status: 'running',
277
+ pause: null,
278
+ recoveryHistory: Object.freeze([...existing.recoveryHistory, recovery])
279
+ }, () => now);
280
+ saveRun(resolved.taskDir, resumed);
281
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
361
282
  }
362
- if (existing.modelPolicy
363
- && typeof existing.modelPolicy.executor === 'string'
364
- && (existing.modelPolicy.executor !== policy.executor.model
365
- || existing.modelPolicy.reviewer !== policy.reviewer.model)) {
366
- return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
283
+ if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
284
+ const resumed = withUpdatedRun(existing, { status: 'running', pause: null }, options.now);
285
+ saveRun(resolved.taskDir, resumed);
286
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
367
287
  }
368
- const now = (options.now ?? (() => new Date().toISOString()))();
369
- const source = { kind: sourceKind, client: options.client, resolvedAt: now };
370
- const clearsModelPause = existing.pause?.code === 'ORCHESTRATION_MODEL_EVIDENCE_MISSING';
371
- const resultingStatus = clearsModelPause ? 'running' : existing.status;
372
- const recovery = {
373
- code: 'MODEL_POLICY_SUPPLEMENTED',
374
- recoveredAt: now,
375
- previousSchemaVersion: 1,
376
- previousStatus: existing.status,
377
- previousPause: existing.pause,
378
- policySource: source,
379
- receiptCount: 0,
380
- pendingDelegation: false,
381
- resultingStatus
382
- };
383
- const recovered = Object.freeze({
384
- ...existing,
385
- schemaVersion: 3,
386
- status: resultingStatus,
387
- modelPolicy: policy,
388
- modelPolicySource: source,
389
- recoveryHistory: Object.freeze([recovery]),
390
- pause: clearsModelPause ? null : existing.pause,
391
- updatedAt: now
392
- });
393
- saveRun(resolved.taskDir, recovered);
394
- return { status: resultingStatus, changed: true, taskId: resolved.taskId, run: recovered, next: null, error: null };
288
+ return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
395
289
  }
396
290
  let policy = options.modelPolicy;
397
291
  let sourceKind = 'explicit';
@@ -414,7 +308,6 @@ function beginOrResumeOrchestration(taskRef, options = {}) {
414
308
  const now = (options.now ?? (() => new Date().toISOString()))();
415
309
  const source = { kind: sourceKind, client: options.client, resolvedAt: now };
416
310
  const run = {
417
- schemaVersion: 3,
418
311
  taskId: resolved.taskId,
419
312
  runId: (options.id ?? randomUUID)(),
420
313
  status: 'running',
@@ -656,401 +549,6 @@ function routeOrchestration(taskRef, options = {}) {
656
549
  }
657
550
  return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
658
551
  }
659
- function appendCommitActivity(resolved, step, agent, note) {
660
- const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
661
- const activity = locateActivityLog(taskContent);
662
- if (!activity)
663
- return { code: 'ORCHESTRATION_TASK_INVALID', message: 'task activity log is missing or ambiguous' };
664
- const metadata = captureTaskWriteMetadata();
665
- const written = writeTask({
666
- taskRef: resolved.taskId,
667
- expectedState: 'active',
668
- mutations: [{
669
- kind: 'section',
670
- aliases: ['活动日志', 'Activity Log'],
671
- heading: activity.heading,
672
- body: appendActivityEntry(activity, { time: metadata.timestamp, step, agent, note })
673
- }]
674
- }, { repoRoot: resolved.repoRoot, metadataProvider: () => metadata });
675
- return written.status === 'failed' ? written.error : null;
676
- }
677
- function startCommitAttempt(taskRef, input, options = {}) {
678
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
679
- if (!resolved.ok)
680
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
681
- try {
682
- const gitRoot = gitRootFor(resolved.repoRoot, options);
683
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.start', () => {
684
- if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
685
- return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
686
- }
687
- const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
688
- const activity = locateActivityLog(taskContent);
689
- if (!activity)
690
- return commitIntentFailed('ORCHESTRATION_TASK_INVALID', 'task activity log is missing or ambiguous', resolved.taskId);
691
- const open = pairEntries(activity.entries).filter((row) => row.step === 'Commit' && row.started !== '' && row.done === '');
692
- if (open.length > 0) {
693
- return commitIntentFailed('ORCHESTRATION_COMMIT_ATTEMPT_BUSY', 'an open Commit activity entry already exists', resolved.taskId);
694
- }
695
- const currentHead = repositoryHead(gitRoot);
696
- const attempt = (options.id ?? randomUUID)();
697
- const error = appendCommitActivity(resolved, 'Commit [started]', input.agent, commitAttemptStartedNote({
698
- attempt,
699
- baseline: currentHead,
700
- agent: input.agent
701
- }));
702
- if (error)
703
- return commitIntentFailed(error.code, error.message, resolved.taskId);
704
- const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
705
- return {
706
- status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
707
- finalization: commitFinalizationView(inspection), error: null
708
- };
709
- });
710
- }
711
- catch (error) {
712
- return mapCommitIntentError(error, resolved.taskId);
713
- }
714
- }
715
- function terminateCommitAttempt(taskRef, input, options = {}) {
716
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
717
- if (!resolved.ok)
718
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
719
- try {
720
- const gitRoot = gitRootFor(resolved.repoRoot, options);
721
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.terminate', () => {
722
- if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
723
- return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'an active commit intent must be aborted or recovered first', resolved.taskId);
724
- }
725
- const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
726
- if (inspection.disposition !== 'retryable-start'
727
- || inspection.attempt?.attempt !== input.attempt
728
- || inspection.attempt.agent !== input.agent
729
- || inspection.attempt.baseline !== inspection.currentHead) {
730
- return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit attempt identity or repository HEAD has drifted', resolved.taskId);
731
- }
732
- const error = appendCommitActivity(resolved, 'Commit [aborted]', input.agent, `aborted; attempt=${input.attempt}; code=${input.code}`);
733
- if (error)
734
- return commitIntentFailed(error.code, error.message, resolved.taskId);
735
- return {
736
- status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
737
- finalization: commitFinalizationView(inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId)),
738
- error: null
739
- };
740
- });
741
- }
742
- catch (error) {
743
- return mapCommitIntentError(error, resolved.taskId);
744
- }
745
- }
746
- function beginCommitIntent(taskRef, input, options = {}) {
747
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
748
- if (!resolved.ok)
749
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
750
- try {
751
- const gitRoot = gitRootFor(resolved.repoRoot, options);
752
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
753
- const currentHead = repositoryHead(gitRoot);
754
- if (currentHead !== input.baselineHead) {
755
- return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
756
- }
757
- if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
758
- try {
759
- readCommitIntent(resolved.taskDir, resolved.taskId);
760
- }
761
- catch (error) {
762
- return mapCommitIntentError(error, resolved.taskId);
763
- }
764
- return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
765
- }
766
- const attemptInspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
767
- if (attemptInspection.disposition !== 'retryable-start'
768
- || attemptInspection.attempt?.attempt !== input.attempt
769
- || attemptInspection.attempt.agent !== input.agent
770
- || attemptInspection.attempt.baseline !== input.baselineHead) {
771
- return commitIntentFailed('ORCHESTRATION_COMMIT_ATTEMPT_MISMATCH', 'commit begin requires one matching retryable Commit attempt', resolved.taskId);
772
- }
773
- const runFile = orchestrationPath(resolved.taskDir);
774
- let run = null;
775
- let sourceRunBytes = null;
776
- if (fs.existsSync(runFile)) {
777
- sourceRunBytes = fs.readFileSync(runFile, 'utf8');
778
- try {
779
- run = readRun(resolved.taskDir);
780
- }
781
- catch (error) {
782
- return commitIntentFailed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
783
- }
784
- if (!validCommitRun(run, resolved.taskId)) {
785
- return commitIntentFailed('ORCHESTRATION_STATE_INVALID', 'orchestration state has an invalid schema', resolved.taskId);
786
- }
787
- }
788
- const now = (options.now ?? (() => new Date().toISOString()))();
789
- const failAndPause = (code, message) => {
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 = null;
798
- if (!input.orchestrated) {
799
- if (run?.pendingDelegation) {
800
- return commitIntentFailed('ORCHESTRATION_STANDALONE_BUSY', 'standalone commit is blocked by a pending orchestration delegation', resolved.taskId);
801
- }
802
- }
803
- else {
804
- if (!run || run.status !== 'running') {
805
- return commitIntentFailed('ORCHESTRATION_RUN_NOT_RUNNING', 'orchestrated commit requires a running orchestration', resolved.taskId);
806
- }
807
- const receipt = run.pendingDelegation;
808
- if (!receipt
809
- || receipt.status !== 'activated'
810
- || receipt.stage !== 'commit'
811
- || receipt.round !== 1
812
- || receipt.artifact !== 'commit'
813
- || receipt.role !== 'executor') {
814
- return failAndPause('ORCHESTRATION_PROVENANCE_MISMATCH', 'orchestrated commit requires one matching activated commit delegation');
815
- }
816
- if (!run.commitAuthorization.issuedAt || run.commitAuthorization.consumedAt) {
817
- return failAndPause('ORCHESTRATION_COMMIT_AUTHORIZATION_INVALID', 'orchestrated commit requires an unconsumed one-use authorization');
818
- }
819
- const planned = planOrchestrationStageCompletion(taskRef, {
820
- stage: 'commit', round: 1, artifact: 'commit', role: 'executor', agent: input.agent
821
- }, { ...options, now: () => now });
822
- if (planned.result.status === 'failed' || !planned.plan?.updatedRun.pendingDelegation) {
823
- return failAndPause(planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH', planned.result.error?.message ?? 'commit delegation could not be completed');
824
- }
825
- const plannedRunBytes = serialize(planned.plan.updatedRun);
826
- orchestration = {
827
- runId: run.runId,
828
- receiptId: receipt.id,
829
- authorizationIssuedAt: run.commitAuthorization.issuedAt,
830
- sourceRunDigest: digest(sourceRunBytes),
831
- plannedRunDigest: digest(plannedRunBytes),
832
- completionUpdatedAt: planned.plan.updatedRun.updatedAt,
833
- plannedReceipt: planned.plan.updatedRun.pendingDelegation
834
- };
835
- }
836
- const created = createCommitIntent(resolved.taskDir, {
837
- taskId: resolved.taskId,
838
- mode: input.orchestrated ? 'orchestrated' : 'standalone',
839
- phase: 'prepared',
840
- baselineHead: input.baselineHead,
841
- committedHead: null,
842
- pushEvidence: null,
843
- orchestration,
844
- createdAt: now,
845
- updatedAt: now
846
- }, { token: options.token });
847
- return {
848
- status: 'ready', changed: true, taskId: resolved.taskId,
849
- intent: commitIntentView(created.intent, currentHead), token: created.token, error: null
850
- };
851
- });
852
- }
853
- catch (error) {
854
- return mapCommitIntentError(error, resolved.taskId);
855
- }
856
- }
857
- function checkpointCommitIntent(taskRef, input, options = {}) {
858
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
859
- if (!resolved.ok)
860
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
861
- try {
862
- const gitRoot = gitRootFor(resolved.repoRoot, options);
863
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
864
- const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
865
- const currentHead = repositoryHead(gitRoot);
866
- if (currentHead !== input.head) {
867
- return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
868
- }
869
- if (input.kind === 'committed' && !isAncestor(gitRoot, intent.baselineHead, input.head)) {
870
- return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
871
- }
872
- if (input.kind === 'pushed') {
873
- const expectedHead = intent.committedHead ?? intent.baselineHead;
874
- if (input.head !== expectedHead || !input.remote || !input.ref) {
875
- return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', 'pushed checkpoint requires matching remote, ref and HEAD', resolved.taskId);
876
- }
877
- }
878
- const now = (options.now ?? (() => new Date().toISOString()))();
879
- const updated = updateCommitIntent(resolved.taskDir, resolved.taskId, input.token, input.kind === 'committed'
880
- ? { phase: 'committed', committedHead: input.head, updatedAt: now }
881
- : {
882
- phase: 'pushed', committedHead: intent.committedHead ?? intent.baselineHead, updatedAt: now,
883
- pushEvidence: { remote: input.remote, ref: input.ref, head: input.head }
884
- });
885
- return {
886
- status: 'ready', changed: true, taskId: resolved.taskId,
887
- intent: commitIntentView(updated, currentHead), error: null
888
- };
889
- });
890
- }
891
- catch (error) {
892
- return mapCommitIntentError(error, resolved.taskId);
893
- }
894
- }
895
- function completeCommitOrchestration(resolved, intent, agent) {
896
- if (intent.orchestration === null)
897
- return null;
898
- if (intent.orchestration.plannedReceipt.agent !== agent) {
899
- return { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'completion agent does not match the planned receipt' };
900
- }
901
- const runFile = orchestrationPath(resolved.taskDir);
902
- if (!fs.existsSync(runFile)) {
903
- return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
904
- }
905
- const currentBytes = fs.readFileSync(runFile, 'utf8');
906
- const currentDigest = digest(currentBytes);
907
- if (currentDigest === intent.orchestration.sourceRunDigest) {
908
- const run = readRun(resolved.taskDir);
909
- if (!run)
910
- return { code: 'ORCHESTRATION_RUN_MISSING', message: 'orchestration run disappeared' };
911
- const plannedRun = Object.freeze({
912
- ...run,
913
- pendingDelegation: intent.orchestration.plannedReceipt,
914
- updatedAt: intent.orchestration.completionUpdatedAt
915
- });
916
- if (digest(serialize(plannedRun)) !== intent.orchestration.plannedRunDigest) {
917
- return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'planned orchestration bytes no longer match the intent' };
918
- }
919
- atomicWrite(runFile, plannedRun);
920
- if (digest(fs.readFileSync(runFile)) !== intent.orchestration.plannedRunDigest) {
921
- return { code: 'ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', message: 'orchestration completion could not be verified' };
922
- }
923
- }
924
- else if (currentDigest !== intent.orchestration.plannedRunDigest) {
925
- return { code: 'ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', message: 'orchestration state changed after commit begin' };
926
- }
927
- return null;
928
- }
929
- function finalizeCommitIntentUnlocked(resolved, inspection, agent, remove) {
930
- if (inspection.disposition !== 'recoverable' || !inspection.intent) {
931
- return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_PENDING', inspection.message, resolved.taskId);
932
- }
933
- if (inspection.intent.orchestration !== null
934
- && inspection.intent.orchestration.plannedReceipt.agent !== agent) {
935
- return commitIntentFailed('ORCHESTRATION_PROVENANCE_MISMATCH', 'completion agent does not match the planned receipt', resolved.taskId);
936
- }
937
- const metadata = captureTaskWriteMetadata();
938
- const taskPlan = planCommitTaskFinalization(resolved.taskDir, inspection, agent, metadata.timestamp);
939
- const written = writeTask({
940
- taskRef: resolved.taskId,
941
- expectedState: 'active',
942
- mutations: taskPlan.mutations
943
- }, {
944
- repoRoot: resolved.repoRoot,
945
- metadataProvider: () => metadata
946
- });
947
- if (written.status === 'failed') {
948
- return commitIntentFailed(written.error.code, written.error.message, resolved.taskId);
949
- }
950
- const orchestrationError = completeCommitOrchestration(resolved, inspection.intent, agent);
951
- if (orchestrationError) {
952
- return commitIntentFailed(orchestrationError.code, orchestrationError.message, resolved.taskId);
953
- }
954
- try {
955
- remove();
956
- }
957
- catch (error) {
958
- return commitIntentFailed('ORCHESTRATION_COMMIT_COMPLETE_PARTIAL', error instanceof Error ? error.message : String(error), resolved.taskId);
959
- }
960
- return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
961
- }
962
- function completeCommitIntent(taskRef, input, options = {}) {
963
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
964
- if (!resolved.ok)
965
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
966
- try {
967
- const gitRoot = gitRootFor(resolved.repoRoot, options);
968
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
969
- readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
970
- const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
971
- return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
972
- removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
973
- });
974
- });
975
- }
976
- catch (error) {
977
- return mapCommitIntentError(error, resolved.taskId);
978
- }
979
- }
980
- function recoverCommitIntent(taskRef, input, options = {}) {
981
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
982
- if (!resolved.ok)
983
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
984
- try {
985
- const gitRoot = gitRootFor(resolved.repoRoot, options);
986
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
987
- const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
988
- if (inspection.disposition === 'retryable-start') {
989
- return {
990
- status: 'ready', changed: false, taskId: resolved.taskId, intent: null,
991
- finalization: commitFinalizationView(inspection), error: null
992
- };
993
- }
994
- if (inspection.disposition === 'prepared' && inspection.intentDigest) {
995
- removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
996
- return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
997
- }
998
- if (!inspection.intentDigest) {
999
- return commitIntentFailed(inspection.code ?? 'COMMIT_FINALIZATION_EVIDENCE_MISSING', inspection.message, resolved.taskId);
1000
- }
1001
- return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
1002
- removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
1003
- });
1004
- });
1005
- }
1006
- catch (error) {
1007
- return mapCommitIntentError(error, resolved.taskId);
1008
- }
1009
- }
1010
- function abortCommitIntent(taskRef, input, options = {}) {
1011
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1012
- if (!resolved.ok)
1013
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
1014
- try {
1015
- const gitRoot = gitRootFor(resolved.repoRoot, options);
1016
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
1017
- const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
1018
- const currentHead = repositoryHead(gitRoot);
1019
- if (intent.phase !== 'prepared'
1020
- || intent.pushEvidence !== null
1021
- || input.expectedHead !== intent.baselineHead
1022
- || currentHead !== intent.baselineHead) {
1023
- return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent has side effects or repository drift', resolved.taskId);
1024
- }
1025
- removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
1026
- return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
1027
- });
1028
- }
1029
- catch (error) {
1030
- return mapCommitIntentError(error, resolved.taskId);
1031
- }
1032
- }
1033
- function statusCommitIntent(taskRef, options = {}) {
1034
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1035
- if (!resolved.ok)
1036
- return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
1037
- let gitRoot;
1038
- try {
1039
- gitRoot = gitRootFor(resolved.repoRoot, options);
1040
- }
1041
- catch (error) {
1042
- return commitIntentFailed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
1043
- }
1044
- const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
1045
- return {
1046
- status: 'ready',
1047
- changed: false,
1048
- taskId: resolved.taskId,
1049
- intent: inspection.intent ? commitIntentView(inspection.intent, inspection.currentHead) : null,
1050
- finalization: commitFinalizationView(inspection),
1051
- error: null
1052
- };
1053
- }
1054
552
  function statusOrchestration(taskRef, options = {}) {
1055
553
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1056
554
  if (!resolved.ok)
@@ -1064,26 +562,12 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1064
562
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1065
563
  if (!resolved.ok)
1066
564
  return failed(resolved.code, resolved.message, resolved.taskId);
1067
- if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
1068
- try {
1069
- readCommitIntent(resolved.taskDir, resolved.taskId);
1070
- }
1071
- catch (error) {
1072
- return failed('ORCHESTRATION_COMMIT_INTENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
1073
- }
1074
- return failed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent blocks delegation preparation', resolved.taskId);
1075
- }
1076
565
  if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
1077
566
  return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
1078
567
  }
1079
568
  const run = readRun(resolved.taskDir);
1080
569
  if (!run || run.status !== 'running')
1081
570
  return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
1082
- if (run.schemaVersion !== 3) {
1083
- return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
1084
- }
1085
- if (!isV2Policy(run.modelPolicy))
1086
- return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
1087
571
  if (run.pendingDelegation)
1088
572
  return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
1089
573
  const repositoryPending = matchingDelegations(() => true, options);
@@ -1112,6 +596,10 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1112
596
  if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
1113
597
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
1114
598
  }
599
+ const capabilityValidationError = options.validateLifecycleCapability?.();
600
+ if (capabilityValidationError) {
601
+ return failed(capabilityValidationError.code, capabilityValidationError.message, resolved.taskId, capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {});
602
+ }
1115
603
  let beforeFingerprint;
1116
604
  try {
1117
605
  beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
@@ -1137,6 +625,10 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
1137
625
  lifecycleProvenance: input.lifecycleProvenance ?? null,
1138
626
  beforeFingerprint
1139
627
  }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
628
+ const capabilityError = options.consumeLifecycleCapability?.();
629
+ if (capabilityError) {
630
+ return failed(capabilityError.code, capabilityError.message, resolved.taskId, capabilityError.detail ? { detail: capabilityError.detail } : {});
631
+ }
1140
632
  const updated = withUpdatedRun(run, {
1141
633
  nextStage: next.stage,
1142
634
  pendingDelegation: receipt,
@@ -1167,8 +659,8 @@ function dispatchOrchestrationDelegationUnlocked(taskRef, options = {}) {
1167
659
  const run = readRun(resolved.taskDir);
1168
660
  if (!run?.pendingDelegation)
1169
661
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1170
- if (run.schemaVersion !== 3 || run.status !== 'running') {
1171
- return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
662
+ if (run.status !== 'running') {
663
+ return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running orchestration', resolved.taskId);
1172
664
  }
1173
665
  const result = dispatchDelegation(run.pendingDelegation, {
1174
666
  now: options.now,
@@ -1202,7 +694,7 @@ function matchingDelegations(predicate, options) {
1202
694
  .filter((entry) => entry.isDirectory())
1203
695
  .flatMap((entry) => {
1204
696
  const run = readRun(path.join(activeRoot, entry.name));
1205
- if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation)
697
+ if (!run || run.status !== 'running' || !run.pendingDelegation)
1206
698
  return [];
1207
699
  const receipt = run.pendingDelegation;
1208
700
  return predicate(receipt)
@@ -1240,9 +732,9 @@ function activateMatchingOrchestrationDelegation(client, event, options = {}) {
1240
732
  return matched;
1241
733
  const receipt = replay.run.pendingDelegation;
1242
734
  const sameEvidence = receipt.parentId === event.parentId
1243
- && receipt.spawnMode === event.spawnMode
1244
- && receipt.actualModel === event.actualModel
1245
- && receipt.actualReasoningEffort === event.actualReasoningEffort
735
+ && (receipt.spawnMode ?? null) === (event.spawnMode ?? null)
736
+ && (receipt.actualModel ?? null) === foldBlankToNull(event.actualModel)
737
+ && (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
1246
738
  && receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
1247
739
  && receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
1248
740
  && receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
@@ -1287,7 +779,11 @@ function sealMatchingOrchestrationDelegation(client, event, options = {}) {
1287
779
  childId: event.childId,
1288
780
  exitCode: 0,
1289
781
  afterFingerprint,
1290
- changedPaths
782
+ changedPaths,
783
+ actualModel: event.actualModel,
784
+ actualReasoningEffort: event.actualReasoningEffort,
785
+ modelFallbackReason: event.modelFallbackReason,
786
+ reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
1291
787
  }, options);
1292
788
  }
1293
789
  catch (error) {
@@ -1358,8 +854,6 @@ function activateOrchestrationDelegation(taskRef, event, options = {}) {
1358
854
  const run = readRun(resolved.taskDir);
1359
855
  if (!run?.pendingDelegation)
1360
856
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1361
- if (run.schemaVersion !== 3)
1362
- return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
1363
857
  if (run.status !== 'running') {
1364
858
  return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1365
859
  }
@@ -1387,9 +881,6 @@ async function awaitOrchestrationDelegationActivation(taskRef, event, options =
1387
881
  if (!run?.pendingDelegation) {
1388
882
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1389
883
  }
1390
- if (run.schemaVersion !== 3) {
1391
- return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
1392
- }
1393
884
  const receipt = run.pendingDelegation;
1394
885
  const matches = receipt.stage === event.stage
1395
886
  && receipt.round === event.round
@@ -1440,16 +931,13 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1440
931
  const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
1441
932
  ?? (receipt.client === 'codex'
1442
933
  && receipt.lifecycleProvenance
1443
- && hasActiveCodexLifecycleEvidence(path.join(resolved.repoRoot, '.agents', 'workspace', '.runtime', 'codex-lifecycle'), {
934
+ && hasActiveCodexLifecycleEvidence(resolveAgentRuntimeStoreRoot({ repoRoot: resolved.repoRoot, store: 'lifecycle' }), {
1444
935
  nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
1445
936
  hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
1446
937
  }));
1447
938
  if (activeLifecycleEvidence) {
1448
939
  return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active Codex lifecycle evidence blocks prepared receipt recovery', resolved.taskId);
1449
940
  }
1450
- if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
1451
- return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
1452
- }
1453
941
  const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
1454
942
  gitRoot: gitRootFor(resolved.repoRoot, options),
1455
943
  stateRoot: resolved.repoRoot,
@@ -1485,8 +973,6 @@ function completeOrchestrationStage(taskRef, event, options = {}) {
1485
973
  const run = readRun(resolved.taskDir);
1486
974
  if (!run)
1487
975
  return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
1488
- if (run.schemaVersion !== 3)
1489
- return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
1490
976
  if (!run.pendingDelegation)
1491
977
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
1492
978
  const result = completeDelegationStage(run.pendingDelegation, event);
@@ -1509,8 +995,6 @@ function inspectOrchestrationStage(taskRef, event, options = {}) {
1509
995
  }
1510
996
  if (!run)
1511
997
  return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
1512
- if (run.schemaVersion !== 3)
1513
- return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
1514
998
  const receipt = run.pendingDelegation;
1515
999
  if (!receipt
1516
1000
  || receipt.status !== 'activated'
@@ -1555,8 +1039,6 @@ function sealOrchestrationDelegation(taskRef, event, options = {}) {
1555
1039
  const run = readRun(resolved.taskDir);
1556
1040
  if (!run?.pendingDelegation)
1557
1041
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1558
- if (run.schemaVersion !== 3)
1559
- return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
1560
1042
  const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
1561
1043
  if (!result.ok)
1562
1044
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1571,8 +1053,6 @@ function advanceOrchestration(taskRef, options = {}) {
1571
1053
  const run = readRun(resolved.taskDir);
1572
1054
  if (!run?.pendingDelegation)
1573
1055
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
1574
- if (run.schemaVersion !== 3)
1575
- return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
1576
1056
  const result = consumeDelegation(run.pendingDelegation, { now: options.now });
1577
1057
  if (!result.ok)
1578
1058
  return pauseOrchestration(taskRef, result.code, result.message, true, options);
@@ -1601,5 +1081,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1601
1081
  saveRun(resolved.taskDir, updated);
1602
1082
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1603
1083
  }
1604
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, abortCommitIntent, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, commitOrchestrationStageCompletion, completeCommitIntent, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverCommitIntent, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, startCommitAttempt, statusCommitIntent, statusOrchestration, terminateCommitAttempt };
1084
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, statusOrchestration, };
1605
1085
  //# sourceMappingURL=orchestration.js.map