@fitlab-ai/agent-infra 0.11.2 → 0.11.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 (162) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +7 -2
  4. package/dist/lib/agent-clients/adapter.js +3 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  8. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  9. package/dist/lib/internal/cli-route-inventory.js +1 -1
  10. package/dist/lib/internal/manual-validation.js +22 -27
  11. package/dist/lib/internal/sandbox-control.js +1 -43
  12. package/dist/lib/internal/task-artifact.js +1 -1
  13. package/dist/lib/internal/task-finalization.js +41 -1
  14. package/dist/lib/internal/task-ledger.js +13 -5
  15. package/dist/lib/internal/task-lifecycle.js +1 -1
  16. package/dist/lib/internal/task-operation-registry.js +7 -2
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/issue-comments.js +2 -31
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/sandbox/control/client.js +3 -44
  21. package/dist/lib/sandbox/control/executor.js +14 -79
  22. package/dist/lib/sandbox/control/protocol.js +5 -64
  23. package/dist/lib/sandbox/control/server.js +49 -343
  24. package/dist/lib/sandbox/control/state.js +1 -7
  25. package/dist/lib/sandbox/control/task-view.js +1 -2
  26. package/dist/lib/sandbox/task-cleanup.js +13 -20
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +1 -26
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +1 -0
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +37 -38
  36. package/dist/lib/task/control-recovery.js +3 -42
  37. package/dist/lib/task/decision-intents.js +12 -0
  38. package/dist/lib/task/delegation-receipts.js +2 -2
  39. package/dist/lib/task/delivery.js +1 -43
  40. package/dist/lib/task/events.js +80 -64
  41. package/dist/lib/task/finalization-artifacts.js +1 -3
  42. package/dist/lib/task/finalization-state.js +1 -1
  43. package/dist/lib/task/finalization.js +319 -234
  44. package/dist/lib/task/ledger-intents.js +157 -15
  45. package/dist/lib/task/lifecycle-path.js +75 -0
  46. package/dist/lib/task/lifecycle.js +11 -1
  47. package/dist/lib/task/local-artifact-finalization.js +1 -20
  48. package/dist/lib/task/manual-validation-receipt.js +8 -3
  49. package/dist/lib/task/orchestration.js +29 -13
  50. package/dist/lib/task/review-finalization.js +3 -16
  51. package/dist/lib/task/rework-intent.js +70 -4
  52. package/dist/lib/task/verification-engine.js +20 -30
  53. package/dist/lib/task/verification.js +1 -0
  54. package/dist/lib/task/workflow-warning-intents.js +21 -6
  55. package/dist/lib/task/workspace-snapshot.js +7 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/agent-clients/adapter.ts +46 -0
  58. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  60. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  61. package/lib/agent-clients/adapters/codex.ts +10 -0
  62. package/lib/internal/cli-route-inventory.ts +1 -1
  63. package/lib/internal/manual-validation.ts +22 -26
  64. package/lib/internal/sandbox-control.ts +0 -42
  65. package/lib/internal/task-artifact.ts +1 -1
  66. package/lib/internal/task-finalization.ts +40 -1
  67. package/lib/internal/task-ledger.ts +12 -5
  68. package/lib/internal/task-lifecycle.ts +2 -2
  69. package/lib/internal/task-operation-registry.ts +8 -4
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/issue-comments.ts +1 -35
  72. package/lib/platform/pr-summary.ts +10 -2
  73. package/lib/sandbox/control/client.ts +5 -68
  74. package/lib/sandbox/control/executor.ts +14 -81
  75. package/lib/sandbox/control/protocol.ts +7 -83
  76. package/lib/sandbox/control/server.ts +47 -362
  77. package/lib/sandbox/control/state.ts +1 -9
  78. package/lib/sandbox/control/task-view.ts +1 -2
  79. package/lib/sandbox/task-cleanup.ts +12 -20
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +1 -26
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +1 -0
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +41 -48
  89. package/lib/task/control-recovery.ts +3 -43
  90. package/lib/task/decision-intents.ts +9 -0
  91. package/lib/task/delegation-receipts.ts +2 -2
  92. package/lib/task/delivery.ts +1 -40
  93. package/lib/task/events.ts +82 -64
  94. package/lib/task/finalization-artifacts.ts +1 -3
  95. package/lib/task/finalization-state.ts +1 -1
  96. package/lib/task/finalization.ts +310 -216
  97. package/lib/task/ledger-intents.ts +155 -15
  98. package/lib/task/lifecycle-path.ts +98 -0
  99. package/lib/task/lifecycle.ts +15 -2
  100. package/lib/task/local-artifact-finalization.ts +2 -25
  101. package/lib/task/manual-validation-receipt.ts +8 -2
  102. package/lib/task/orchestration.ts +56 -27
  103. package/lib/task/review-finalization.ts +3 -16
  104. package/lib/task/rework-intent.ts +75 -5
  105. package/lib/task/verification-engine.ts +19 -30
  106. package/lib/task/verification.ts +2 -1
  107. package/lib/task/workflow-warning-intents.ts +18 -6
  108. package/lib/task/workspace-snapshot.ts +3 -2
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  111. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  112. package/templates/.agents/rules/review-handshake.en.md +4 -4
  113. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  114. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  115. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  116. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  117. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  118. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  119. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  120. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  121. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  122. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  123. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  124. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  125. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  128. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  129. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  130. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  131. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  132. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  135. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  136. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  137. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  138. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  139. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  140. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  141. package/templates/.agents/templates/task.en.md +2 -2
  142. package/templates/.agents/templates/task.zh-CN.md +2 -2
  143. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  144. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  145. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  146. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  147. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  148. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  149. package/dist/lib/sandbox/control/finalization-response.js +0 -35
  150. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  151. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  152. package/dist/lib/task/lifecycle-recovery.js +0 -549
  153. package/dist/lib/task/recovery-warning.js +0 -19
  154. package/dist/lib/task/workflow-command.js +0 -21
  155. package/dist/lib/task/workflow-dispatch.js +0 -213
  156. package/lib/sandbox/control/finalization-response.ts +0 -37
  157. package/lib/sandbox/control/task-workflow.ts +0 -95
  158. package/lib/sandbox/control/workflow-executor.ts +0 -156
  159. package/lib/task/lifecycle-recovery.ts +0 -691
  160. package/lib/task/recovery-warning.ts +0 -21
  161. package/lib/task/workflow-command.ts +0 -22
  162. package/lib/task/workflow-dispatch.ts +0 -214
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { parseArtifactName } from './artifact-name.js';
4
4
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
5
- import { receiptForOutput } from './artifact-receipts.js';
5
+ import { parseArtifactReceipts, receiptForOutput } from './artifact-receipts.js';
6
6
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
7
7
  import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
8
8
  import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
@@ -10,6 +10,8 @@ import { parseReworkIntentDocument } from './rework-intent.js';
10
10
  import { sha256File } from './artifact-receipts.js';
11
11
  import { hasOpenLifecycleExecution } from './activity-log.js';
12
12
  import { parseQualificationAudit, parseTaskQualification } from './qualification-audit.js';
13
+ import { parseLifecyclePathDecision, pathIncludes } from './lifecycle-path.js';
14
+ import { parseImplementationInputs } from './implementation-inputs.js';
13
15
  const ARTIFACT_AUDIT_FAMILIES = new Set(['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']);
14
16
  const REASONS = new Set([
15
17
  'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
@@ -23,6 +25,11 @@ function allow(...evidence) {
23
25
  function hasArtifact(facts, action) {
24
26
  return (facts.artifacts[action]?.length ?? 0) > 0;
25
27
  }
28
+ function effectiveReworkTarget(target, pathState) {
29
+ if (target === 'plan' && pathState?.status === 'valid' && !pathIncludes(pathState, 'plan'))
30
+ return 'analysis';
31
+ return target;
32
+ }
26
33
  function canStart(action, facts, trigger) {
27
34
  if (!trigger || trigger.requestedAction !== action)
28
35
  return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
@@ -45,11 +52,30 @@ function canStart(action, facts, trigger) {
45
52
  return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
46
53
  if (invalidationBlocks(facts.invalidation))
47
54
  return deny('INVALIDATION_INCOMPLETE');
48
- if (facts.qualificationStale && facts.recommendedAction !== action) {
49
- return deny('QUALIFICATION_STALE', ...(facts.qualificationStaleArtifacts ?? ['qualification audit is stale']));
50
- }
51
55
  if (facts.executionBusy)
52
56
  return deny('EXECUTION_BUSY');
57
+ const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
58
+ if (pendingIntent) {
59
+ const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
60
+ const requirementRestart = action === 'analysis' && trigger.reasonCode === 'new-requirement';
61
+ if (!requirementRestart && (target === 'pause' || target !== action)) {
62
+ return deny(target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_TARGET_MISMATCH', pendingIntent.intentId);
63
+ }
64
+ }
65
+ if (facts.pathState && facts.pathState.status !== 'valid') {
66
+ return action === 'analysis'
67
+ ? allow(`lifecycle-path-${facts.pathState.status}`)
68
+ : deny('LIFECYCLE_PATH_INVALID', facts.pathState.message);
69
+ }
70
+ if (facts.pathState?.status === 'valid'
71
+ && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(action)
72
+ && !pathIncludes(facts.pathState, action)) {
73
+ return deny('ARTIFACT_STAGE_NOT_SELECTED', `path=${facts.pathState.decision.path}`, `stage=${action}`);
74
+ }
75
+ if (facts.reworkClassificationRequired?.includes(action)
76
+ && !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
77
+ return deny('REWORK_CLASSIFICATION_REQUIRED', `stage=${action}`);
78
+ }
53
79
  const implicit = trigger.explicitRequest === false;
54
80
  if (action === 'analysis') {
55
81
  if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis')
@@ -60,6 +86,9 @@ function canStart(action, facts, trigger) {
60
86
  return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
61
87
  }
62
88
  if (action === 'plan') {
89
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
90
+ return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
91
+ }
63
92
  if (!hasArtifact(facts, 'review-analysis'))
64
93
  return deny('ANALYSIS_REVIEW_REQUIRED');
65
94
  if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis'))
@@ -75,6 +104,8 @@ function canStart(action, facts, trigger) {
75
104
  }
76
105
  if (action === 'code') {
77
106
  if (trigger.implementationInput) {
107
+ if (facts.resolvedHumanDecisions?.code === 'implementation')
108
+ return allow('human-decision-implementation-input');
78
109
  if (!hasArtifact(facts, 'review-code'))
79
110
  return deny('CODE_REVIEW_REQUIRED');
80
111
  if (!reviewMatchesLatest(facts, 'code', 'review-code'))
@@ -83,6 +114,20 @@ function canStart(action, facts, trigger) {
83
114
  return deny('CODE_REVIEW_NOT_APPROVED');
84
115
  return allow('implementation-input');
85
116
  }
117
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'streamlined') {
118
+ if (!hasArtifact(facts, 'analysis'))
119
+ return deny('ANALYSIS_ARTIFACT_REQUIRED');
120
+ if (facts.unresolvedLedger.analysis > 0)
121
+ return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
122
+ return allow('analysis-path-approved');
123
+ }
124
+ if (facts.pathState?.status === 'valid' && facts.pathState.decision.path === 'standard') {
125
+ if (!hasArtifact(facts, 'plan'))
126
+ return deny('PLAN_ARTIFACT_REQUIRED');
127
+ if (facts.unresolvedLedger.plan > 0)
128
+ return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
129
+ return allow('plan-path-approved');
130
+ }
86
131
  if (!hasArtifact(facts, 'review-plan'))
87
132
  return deny('PLAN_REVIEW_REQUIRED');
88
133
  if (!reviewMatchesLatest(facts, 'plan', 'review-plan'))
@@ -113,14 +158,6 @@ function latestArtifact(names) {
113
158
  return names.map(parseArtifactName).filter((identity) => identity !== null)
114
159
  .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
115
160
  }
116
- function reviewedInputName(content, expectedFamily) {
117
- const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
118
- if (header < 0)
119
- return null;
120
- const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
121
- const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
122
- return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
123
- }
124
161
  function reviewMatchesLatest(facts, source, review) {
125
162
  const latestSource = latestArtifact(facts.artifacts[source] ?? []);
126
163
  return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
@@ -133,30 +170,56 @@ function qualificationCandidateSnapshotMatches(audit, qualification) {
133
170
  && candidate.evidence === row.evidence && candidate.constraintIds.join(',') === row.constraintIds.join(','));
134
171
  });
135
172
  }
136
- const QUALIFICATION_RECOVERY_ORDER = [
137
- { action: 'analysis', pattern: /^analysis(?:-r\d+)?\.md$/ },
138
- { action: 'review-analysis', pattern: /^review-analysis(?:-r\d+)?\.md$/ },
139
- { action: 'plan', pattern: /^plan(?:-r\d+)?\.md$/ },
140
- { action: 'review-plan', pattern: /^review-plan(?:-r\d+)?\.md$/ },
141
- { action: 'code', pattern: /^code(?:-r\d+)?\.md$/ },
142
- { action: 'review-code', pattern: /^review-code(?:-r\d+)?\.md$/ }
143
- ];
144
- function qualificationRecoveryAction(facts) {
145
- const stale = facts.qualificationStaleArtifacts ?? [];
146
- return QUALIFICATION_RECOVERY_ORDER.find(({ pattern }) => stale.some((name) => pattern.test(name)))?.action ?? null;
147
- }
148
173
  function recommendNext(facts) {
149
- if (facts.qualificationStale) {
150
- const action = qualificationRecoveryAction(facts);
151
- return {
152
- action,
153
- reasonCode: 'QUALIFICATION_RECOVERY_REQUIRED',
154
- evidence: facts.qualificationStaleArtifacts ?? ['qualification audit is stale']
155
- };
174
+ if ((facts.reworkClassificationRequired?.length ?? 0) > 0
175
+ && !(facts.reworkIntents ?? []).some((intent) => intent.status === 'pending')) {
176
+ return { action: null, reasonCode: 'REWORK_CLASSIFICATION_REQUIRED', evidence: facts.reworkClassificationRequired ?? [] };
156
177
  }
157
178
  const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
158
- if (pendingIntent)
159
- return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
179
+ if (pendingIntent) {
180
+ const target = effectiveReworkTarget(pendingIntent.target, facts.pathState);
181
+ return { action: target === 'pause' ? null : target, reasonCode: target === 'pause' ? 'REWORK_PAUSED' : 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
182
+ }
183
+ if (facts.pathState && facts.pathState.status !== 'valid') {
184
+ return { action: 'analysis', reasonCode: facts.pathState.status === 'missing' ? 'LIFECYCLE_PATH_MISSING' : 'LIFECYCLE_PATH_INVALID', evidence: [facts.pathState.message] };
185
+ }
186
+ if (facts.pathState?.status === 'valid') {
187
+ for (const stage of facts.pathState.decision.stages) {
188
+ if (stage === 'analysis' && !hasArtifact(facts, stage))
189
+ return { action: stage, reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
190
+ if (stage === 'review-analysis' && (!reviewMatchesLatest(facts, 'analysis', stage) || facts.reviews[stage] !== 'approved')) {
191
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.analysis === 'review') {
192
+ return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['analysis decision was resolved'] };
193
+ }
194
+ return facts.reviews[stage] === 'changes-requested'
195
+ ? { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review is not approved'] }
196
+ : { action: stage, reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
197
+ }
198
+ if (stage === 'plan' && !hasArtifact(facts, stage))
199
+ return { action: stage, reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
200
+ if (stage === 'review-plan' && (!reviewMatchesLatest(facts, 'plan', stage) || facts.reviews[stage] !== 'approved')) {
201
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.plan === 'review') {
202
+ return { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['plan decision was resolved'] };
203
+ }
204
+ return facts.reviews[stage] === 'changes-requested'
205
+ ? { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review is not approved'] }
206
+ : { action: stage, reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
207
+ }
208
+ if (stage === 'code' && !hasArtifact(facts, stage))
209
+ return { action: stage, reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
210
+ if (stage === 'review-code' && (!reviewMatchesLatest(facts, 'code', stage) || facts.reviews[stage] !== 'approved')) {
211
+ if (facts.reviews[stage] === 'changes-requested' && facts.resolvedHumanDecisions?.code) {
212
+ return facts.resolvedHumanDecisions.code === 'implementation'
213
+ ? { action: 'code', reasonCode: 'HUMAN_DECISION_IMPLEMENTATION_REQUIRED', evidence: ['code decision requires implementation'] }
214
+ : { action: stage, reasonCode: 'HUMAN_DECISION_REVIEW_REQUIRED', evidence: ['code decision requires no implementation'] };
215
+ }
216
+ return facts.reviews[stage] === 'changes-requested'
217
+ ? { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review is not approved'] }
218
+ : { action: stage, reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
219
+ }
220
+ }
221
+ return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['selected lifecycle path is complete'] };
222
+ }
160
223
  if (!hasArtifact(facts, 'analysis'))
161
224
  return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
162
225
  if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
@@ -213,9 +276,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
213
276
  const family = familyFor(name);
214
277
  return !family || !isArtifactInvalidated(invalidation.document, family, name);
215
278
  });
279
+ const allArtifactHashes = {};
280
+ for (const name of files)
281
+ allArtifactHashes[name] = sha256File(path.join(taskDir, name));
216
282
  const artifactHashes = {};
217
283
  for (const name of activeFiles)
218
- artifactHashes[name] = sha256File(path.join(taskDir, name));
284
+ artifactHashes[name] = allArtifactHashes[name];
219
285
  const artifacts = Object.fromEntries(artifactFamilies.map((family) => [
220
286
  family, activeFiles.filter((name) => familyFor(name) === family)
221
287
  ]));
@@ -264,9 +330,12 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
264
330
  continue;
265
331
  const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
266
332
  const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
267
- const input = reviewedInputName(reviewContent, expectedFamily);
268
- const receipt = input ? receiptForOutput(content, latest) : null;
269
- if (input && receipt?.input === input && artifactHashes[input] === receipt.inputSha256)
333
+ const receipt = receiptForOutput(content, latest);
334
+ const input = receipt?.input;
335
+ if (input
336
+ && parseArtifactName(input)?.family === expectedFamily
337
+ && (artifacts[expectedFamily] ?? []).includes(input)
338
+ && artifactHashes[input] === receipt.inputSha256)
270
339
  reviewedInputs[family] = input;
271
340
  const parsed = parseReviewSummary(reviewContent);
272
341
  if (!parsed.ok)
@@ -275,11 +344,58 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
275
344
  if (verdict.ok)
276
345
  reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
277
346
  }
347
+ const reworkClassificationRequired = [];
348
+ const receipts = parseArtifactReceipts(content).rows;
349
+ for (const [reviewFamily, stage] of [['review-analysis', 'analysis'], ['review-plan', 'plan'], ['review-code', 'code']]) {
350
+ const cycles = receipts.filter((receipt) => receipt.event === `${reviewFamily}.completed` && files.includes(receipt.output)
351
+ && allArtifactHashes[receipt.input] === receipt.inputSha256).map((receipt) => {
352
+ const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, receipt.output), 'utf8'));
353
+ const verdict = parsed.ok ? resolveCanonicalVerdict(parsed.summary) : null;
354
+ return {
355
+ input: receipt.input,
356
+ output: receipt.output,
357
+ outputRound: parseArtifactName(receipt.output)?.round ?? 0,
358
+ inputRound: parseArtifactName(receipt.input)?.round ?? 0,
359
+ verdict: verdict?.ok ? verdict.verdict : null
360
+ };
361
+ }).sort((left, right) => left.inputRound - right.inputRound || left.outputRound - right.outputRound || left.output.localeCompare(right.output));
362
+ const latest = cycles.at(-1);
363
+ const trailingInputRounds = new Set();
364
+ for (let index = cycles.length - 1; index >= 0 && cycles[index].verdict === 'Changes Requested'; index -= 1) {
365
+ trailingInputRounds.add(cycles[index].inputRound);
366
+ }
367
+ const handled = latest && (rework.intents ?? []).some((intent) => intent.sourceArtifact === latest.output
368
+ && intent.sourceSha256 === allArtifactHashes[latest.output]);
369
+ const latestActiveReview = latestArtifact(artifacts[reviewFamily] ?? []);
370
+ const latestActiveInput = latestArtifact(artifacts[stage] ?? []);
371
+ const latestCycleIsActive = latest?.output === latestActiveReview && latest.input === latestActiveInput;
372
+ if (latestCycleIsActive && latest.verdict === 'Changes Requested' && trailingInputRounds.size >= 2 && !handled)
373
+ reworkClassificationRequired.push(stage);
374
+ }
278
375
  const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
279
376
  if (ledger.present) {
280
377
  for (const stage of ['analysis', 'plan', 'code'])
281
378
  unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
282
379
  }
380
+ const latestAnalysis = latestArtifact(artifacts.analysis ?? []);
381
+ const pathState = latestAnalysis
382
+ ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latestAnalysis), 'utf8'), latestAnalysis, artifactHashes[latestAnalysis] ?? '')
383
+ : { status: 'missing', decision: null, message: 'analysis artifact is missing' };
384
+ const resolvedHumanDecisions = {};
385
+ const implementationInputs = parseImplementationInputs(content).rows;
386
+ for (const stage of ['analysis', 'plan', 'code']) {
387
+ const reviewFamily = `review-${stage}`;
388
+ const latestReview = latestArtifact(artifacts[reviewFamily] ?? []);
389
+ if (!latestReview || reviews[reviewFamily] !== 'changes-requested')
390
+ continue;
391
+ const latestHash = artifactHashes[latestReview];
392
+ const decision = rework.intents.find((intent) => intent.status === 'consumed'
393
+ && intent.classification === 'human-decision' && intent.sourceArtifact === latestReview && intent.sourceSha256 === latestHash);
394
+ if (!decision)
395
+ continue;
396
+ const implementationInput = implementationInputs.find((input) => input.ledgerId === decision.findingId);
397
+ resolvedHumanDecisions[stage] = stage === 'code' && implementationInput?.status === 'pending' ? 'implementation' : 'review';
398
+ }
283
399
  const facts = {
284
400
  taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
285
401
  staleArtifacts, reviewedInputs, artifactHashes,
@@ -288,7 +404,10 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
288
404
  executionBusy: executionBusy || hasOpenLifecycleExecution(content),
289
405
  recommendedAction: null,
290
406
  qualificationStale: qualificationStaleArtifacts.length > 0,
291
- qualificationStaleArtifacts
407
+ qualificationStaleArtifacts,
408
+ pathState,
409
+ reworkClassificationRequired,
410
+ resolvedHumanDecisions
292
411
  };
293
412
  facts.recommendedAction = recommendNext(facts).action;
294
413
  return { ok: true, facts };
@@ -297,5 +416,5 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
297
416
  return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
298
417
  }
299
418
  }
300
- export { buildLifecycleFacts, canStart, recommendNext };
419
+ export { buildLifecycleFacts, canStart, effectiveReworkTarget, recommendNext };
301
420
  //# sourceMappingURL=capabilities.js.map
@@ -325,7 +325,11 @@ function renderStatus(model) {
325
325
  ['processed', `${model.invalidation?.processed ?? 0}/${model.invalidation?.total ?? 0}`],
326
326
  ['pending_intents', String(model.invalidation?.pendingIntents ?? 0)]
327
327
  ]));
328
- lines.push('', 'Recommendation', ...renderPairs([['action', model.recommendation ?? DASH]]));
328
+ lines.push('', 'Recommendation', ...renderPairs([
329
+ ['path', model.lifecyclePath ?? DASH],
330
+ ['action', model.recommendation ?? DASH],
331
+ ['reason', model.recommendationReason ?? DASH]
332
+ ]));
329
333
  lines.push('', 'Orchestration', ...renderPairs([
330
334
  ['status', model.orchestration.status],
331
335
  ['run_id', model.orchestration.runId],
@@ -411,7 +415,8 @@ function buildFromResolved(input) {
411
415
  pendingIntents
412
416
  };
413
417
  const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
414
- const recommendation = facts.ok ? (recommendNext(facts.facts).action ?? DASH) : DASH;
418
+ const next = facts.ok ? recommendNext(facts.facts) : null;
419
+ const recommendation = next?.action ?? DASH;
415
420
  return {
416
421
  taskId: input.taskId,
417
422
  shortId: input.shortId ?? loadShortIdByTaskId(input.repoRoot).get(input.taskId) ?? DASH,
@@ -424,6 +429,8 @@ function buildFromResolved(input) {
424
429
  orchestration: collectOrchestration(input.taskDir),
425
430
  invalidation: invalidationInfo,
426
431
  recommendation,
432
+ recommendationReason: next?.reasonCode ?? 'TASK_CAPABILITY_FACTS_INVALID',
433
+ lifecyclePath: facts.ok && facts.facts.pathState?.status === 'valid' ? facts.facts.pathState.decision.path : facts.ok ? facts.facts.pathState?.status ?? DASH : DASH,
427
434
  git: collectGit(fm.branch ?? '', run)
428
435
  };
429
436
  }
@@ -3,12 +3,11 @@ import crypto from 'node:crypto';
3
3
  import fs from 'node:fs';
4
4
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
5
5
  import { isAgentClientId } from '../agent-clients/types.js';
6
+ import { getAgentClientAdapter } from '../agent-clients/registry.js';
6
7
  import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
7
8
  import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
8
- import { prepareCodexOrchestrationDelegation } from './codex-orchestration.js';
9
- import { applyTaskFinalization } from './finalization.js';
9
+ import { applyTaskFinalization, commitPreparedTaskFinalization } from './finalization.js';
10
10
  import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
11
- import { recoverStartedLifecycleUnderLock, recoveryFailure } from './lifecycle-recovery.js';
12
11
  import { locateActivityLog } from './activity-log.js';
13
12
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
14
13
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
@@ -407,7 +406,6 @@ export function createSandboxExecutorExecutionContext(params) {
407
406
  generation: params.generation,
408
407
  manifestPath: absolute('manifestPath', params.manifestPath),
409
408
  requestId: params.requestId,
410
- ...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
411
409
  ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
412
410
  ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
413
411
  lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
@@ -450,11 +448,7 @@ function operationTaskId(context, taskRef) {
450
448
  function domainOptions(context, options) {
451
449
  return {
452
450
  ...options,
453
- repoRoot: context.repoRoot,
454
- ...(context.source === 'sandbox-executor' && context.diagnosticLog !== undefined
455
- ? { diagnosticLog: context.diagnosticLog }
456
- : {}),
457
- ...(context.source === 'sandbox-executor' ? { gitWorktreeRoot: context.worktreeRoot } : {})
451
+ repoRoot: context.repoRoot
458
452
  };
459
453
  }
460
454
  function lifecycleTaskId(request, repoRoot) {
@@ -475,7 +469,17 @@ async function applyLifecycleWithAuthority(context, request) {
475
469
  }
476
470
  const execute = async () => {
477
471
  if (request.intent === 'recover-started') {
478
- return recoverStartedLifecycleUnderLock(request, { repoRoot: context.repoRoot });
472
+ const adapter = isAgentClientId(request.agent)
473
+ ? getAgentClientAdapter(request.agent).orchestrationAdapter
474
+ : undefined;
475
+ if (!adapter?.recoverStarted) {
476
+ return {
477
+ status: 'no-op', changed: false, targetState: 'active',
478
+ requestRef: request.taskRef, intent: 'recover-started', taskId: null,
479
+ receiptId: null, childId: null, error: null
480
+ };
481
+ }
482
+ return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
479
483
  }
480
484
  const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
481
485
  if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
@@ -511,7 +515,12 @@ async function applyLifecycleWithAuthority(context, request) {
511
515
  if (!(error instanceof TaskExecutionLockError))
512
516
  throw error;
513
517
  if (request.intent === 'recover-started') {
514
- return recoveryFailure(request, 'owner-unknown', error.code, error.message);
518
+ return {
519
+ status: 'owner-unknown', changed: false, targetState: 'active',
520
+ requestRef: request.taskRef, intent: 'recover-started', taskId,
521
+ receiptId: null, childId: null,
522
+ error: { code: error.code, message: error.message }
523
+ };
515
524
  }
516
525
  return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
517
526
  }
@@ -536,8 +545,10 @@ function orchestration(context, operation) {
536
545
  requestedModel: input.requestedModel,
537
546
  requestedReasoningEffort: input.requestedReasoningEffort,
538
547
  };
539
- if (prepareInput.client === 'codex') {
540
- return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
548
+ const prepareDelegation = getAgentClientAdapter(prepareInput.client)
549
+ .orchestrationAdapter?.prepareDelegation;
550
+ if (prepareDelegation) {
551
+ return prepareDelegation(operation.taskRef, prepareInput, {
541
552
  repoRoot: context.repoRoot,
542
553
  orchestrationOptions: options,
543
554
  });
@@ -579,7 +590,8 @@ export function dispatchTaskControlOperation(context, operation) {
579
590
  }
580
591
  if (operation.family === 'task-finalization') {
581
592
  operationTaskId(context, operation.request.taskRef);
582
- return applyTaskFinalization(operation.request, {
593
+ const finalize = context.source === 'sandbox-executor' ? commitPreparedTaskFinalization : applyTaskFinalization;
594
+ return finalize(operation.request, {
583
595
  repoRoot: context.repoRoot,
584
596
  ...(context.source === 'sandbox-executor' ? {
585
597
  controlBinding: { generation: context.generation, requestId: context.requestId }
@@ -617,7 +629,7 @@ function operationInvalid(message) {
617
629
  }
618
630
  const LIFECYCLE_FLAGS = new Set([
619
631
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
620
- '--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
632
+ '--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
621
633
  ]);
622
634
  const FINALIZATION_FLAGS = new Set(['--agent']);
623
635
  const ORCHESTRATION_FLAGS = new Set([
@@ -626,13 +638,13 @@ const ORCHESTRATION_FLAGS = new Set([
626
638
  '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
627
639
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
628
640
  '--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
629
- '--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
641
+ '--changed-paths', '--code', '--message', '--recoverable'
630
642
  ]);
631
643
  function parseValues(args, start, flags) {
632
644
  const values = {};
633
645
  for (let index = start; index < args.length; index += 1) {
634
646
  const flag = args[index];
635
- if (flag === '--dry-run') {
647
+ if (flag === '--dry-run' || flag === '--auto') {
636
648
  if (!flags.has(flag))
637
649
  operationInvalid(`unknown option '${flag}'`);
638
650
  if (values[flag] !== undefined)
@@ -693,31 +705,21 @@ export function parseTaskControlOperation(family, args) {
693
705
  ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
694
706
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
695
707
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
696
- ...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
697
- ...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
698
- ...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
708
+ ...(values['--auto'] === true ? { auto: true } : {}),
699
709
  ...(values['--dry-run'] === true ? { dryRun: true } : {})
700
710
  };
701
711
  if (intent === 'recover-started') {
702
- const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
712
+ const recoveryFlags = new Set(['--agent', '--auto']);
703
713
  for (const flag of Object.keys(values)) {
704
714
  if (!recoveryFlags.has(flag))
705
715
  operationInvalid(`option '${flag}' is not supported for recover-started`);
706
716
  }
707
- required(values, ['--stage', '--round', '--artifact', '--reason']);
708
- const stage = value(values, '--stage');
709
- if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
710
- operationInvalid('--stage must be a supported lifecycle stage');
711
- }
712
- const round = Number(value(values, '--round'));
713
- if (!Number.isSafeInteger(round) || round < 1)
714
- operationInvalid('--round must be a positive integer');
715
- if (value(values, '--reason').includes('\n') || value(values, '--reason').includes('\r')) {
716
- operationInvalid('--reason must be a single line');
717
- }
717
+ if (values['--auto'] !== true)
718
+ operationInvalid('--auto is required for recover-started');
719
+ return { family, request: input };
718
720
  }
719
- else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
720
- operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
721
+ else if (values['--auto'] !== undefined) {
722
+ operationInvalid('--auto is only supported for recover-started');
721
723
  }
722
724
  return { family, request: input };
723
725
  }
@@ -820,9 +822,6 @@ export function parseTaskControlOperation(family, args) {
820
822
  input.message = value(values, '--message');
821
823
  input.recoverable = recoverable === 'true';
822
824
  }
823
- const options = value(values, '--git-worktree-root') === undefined
824
- ? undefined
825
- : { gitWorktreeRoot: value(values, '--git-worktree-root') };
826
- return { family, taskRef, intent: parsedIntent, input, ...(options ? { options } : {}) };
825
+ return { family, taskRef, intent: parsedIntent, input };
827
826
  }
828
827
  //# sourceMappingURL=control-authority.js.map
@@ -1,7 +1,4 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { isCompletionEvidence } from './orchestration.js';
3
- import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.js';
4
- import { sameRecoveryWarning } from './recovery-warning.js';
5
2
  /** Normalize the command result once at the control transport boundary. */
6
3
  export function parseControlOutput(output) {
7
4
  if (output === null)
@@ -20,20 +17,10 @@ export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
20
17
  'completed', 'evidence-written', 'publish-authorized'
21
18
  ]);
22
19
  const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
23
- const ORCHESTRATION_INTENTS = [
24
- 'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
25
- 'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
26
- ];
27
20
  export const SANDBOX_CONTROL_RECOVERY_OPERATIONS = Object.freeze([
28
21
  ...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle', intent, class: 'lifecycle-mutation' })),
29
22
  { family: 'task-finalization', intent: 'complete', class: 'finalization' },
30
- ...ORCHESTRATION_INTENTS.map((intent) => ({
31
- family: 'task-orchestration',
32
- intent,
33
- class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
34
- })),
35
23
  { family: 'task-create', intent: 'create', class: 'task-create' },
36
- ...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow', intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
37
24
  ...['open', 'close', 'verify'].map((intent) => ({ family: 'codex-controller', intent, class: 'codex-controller' }))
38
25
  ]);
39
26
  export function digestControlRecoveryIntent(family, intent) {
@@ -53,30 +40,8 @@ function domainEvidenceMatches(operation, result, domain) {
53
40
  return false;
54
41
  if (domain.consistent !== true)
55
42
  return false;
56
- if (operation.class === 'route.clean-completion') {
57
- const completion = domain.completionEvidence;
58
- const snapshot = domain.snapshot;
59
- const lastReviewedCommit = domain.lastReviewedCommit;
60
- return result.status === 'completed'
61
- && domain.status === 'completed'
62
- && domain.pendingDelegation === null
63
- && isCompletionEvidence(completion)
64
- && snapshot?.head === completion.head
65
- && snapshot.headTree === completion.headTree
66
- && snapshot.worktreeTree === completion.worktreeTree
67
- && lastReviewedCommit === completion.lastReviewedCommit;
68
- }
69
- if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
70
- const retryRequired = domain.recoveryState === 'retry-required'
71
- && sameRecoveryWarning(domain.warning, result.warning);
72
- return result.targetState === 'active'
73
- && ['applied', 'no-op'].includes(String(result.status))
74
- && domain.recovery === true
75
- && domain.targetState === 'active'
76
- && (domain.recoveryState === 'released' || retryRequired);
77
- }
78
- if (operation.class === 'read-only')
79
- return result.changed === false && domain.snapshotValid === true;
43
+ if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started')
44
+ return false;
80
45
  return true;
81
46
  }
82
47
  export function classifySandboxControlRecovery(input) {
@@ -127,11 +92,7 @@ export function classifySandboxControlRecovery(input) {
127
92
  return {
128
93
  outcome: 'success',
129
94
  responseReconstructable: true,
130
- reasonCode: input.operation.class === 'route.clean-completion' && result.changed === false
131
- ? 'RECOVERY_ROUTE_COMPLETION_NOOP'
132
- : input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
133
- ? 'RECOVERY_RELEASE_RETRY_REQUIRED'
134
- : 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
95
+ reasonCode: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
135
96
  };
136
97
  }
137
98
  export function operationRecoveryBinding(requestId, generation, taskId, family, intent) {
@@ -7,6 +7,7 @@ import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
8
8
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
9
  import { allowsManualOverride } from './guard-override.js';
10
+ import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
10
11
  const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
11
12
  const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
12
13
  function failed(code, message, taskId = null, ledgerId = null) {
@@ -160,6 +161,17 @@ function applyHumanDecision(request, options = {}) {
160
161
  kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
161
162
  body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
162
163
  });
164
+ try {
165
+ const parsedRework = parseReworkIntentDocument(content);
166
+ if (!parsedRework.ok)
167
+ return failed('DECISION_DOCUMENT_INVALID', parsedRework.message, resolved.taskId, row.id);
168
+ const consumed = consumeHumanDecisionReworkIntents(parsedRework.intents, row.id, metadata.timestamp);
169
+ if (consumed.changed)
170
+ mutations.push(reworkIntentMutation(content, consumed.intents));
171
+ }
172
+ catch (error) {
173
+ return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
174
+ }
163
175
  const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
164
176
  ...options,
165
177
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
@@ -149,7 +149,7 @@ function hasStatusBoundEvidence(receipt) {
149
149
  && host !== undefined
150
150
  && Number.isSafeInteger(host.stopRevision)
151
151
  && host.stopRevision > host.startRevision
152
- && host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
152
+ && host.consumer === receipt.id
153
153
  && exactText(host.consumedAt);
154
154
  }
155
155
  if (!exactText(receipt.agent)
@@ -405,7 +405,7 @@ function abortActivatedDelegation(receipt, event) {
405
405
  if (event.childId !== receipt.childId
406
406
  || !Number.isSafeInteger(event.stopRevision)
407
407
  || event.stopRevision <= receipt.hostEvidence.startRevision
408
- || event.consumer !== `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
408
+ || event.consumer !== receipt.id
409
409
  || !exactText(event.consumedAt)) {
410
410
  return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
411
411
  }