@fitlab-ai/agent-infra 0.11.4 → 0.11.6

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 (149) hide show
  1. package/dist/bin/internal-cli.js +0 -5
  2. package/dist/lib/decide.js +4 -41
  3. package/dist/lib/git/branch-worktree.js +45 -0
  4. package/dist/lib/internal/cli-route-inventory.js +0 -3
  5. package/dist/lib/internal/task-activity.js +7 -40
  6. package/dist/lib/internal/task-event.js +4 -37
  7. package/dist/lib/internal/task-finalization.js +9 -2
  8. package/dist/lib/internal/task-ledger.js +5 -43
  9. package/dist/lib/internal/task-lifecycle.js +1 -1
  10. package/dist/lib/internal/task-operation-registry.js +1 -4
  11. package/dist/lib/internal/task-review.js +5 -37
  12. package/dist/lib/internal/task-verify.js +3 -47
  13. package/dist/lib/internal/task-warning.js +4 -40
  14. package/dist/lib/merge.js +62 -10
  15. package/dist/lib/platform/github-provider.js +9 -14
  16. package/dist/lib/platform/github-release-notes.js +80 -11
  17. package/dist/lib/platform/none-provider.js +1 -1
  18. package/dist/lib/platform/pr-summary.js +4 -23
  19. package/dist/lib/platform/provider-contract.d.ts +39 -16
  20. package/dist/lib/platform/provider-contract.js +1 -1
  21. package/dist/lib/platform/provider-validation.js +83 -13
  22. package/dist/lib/platform/release-notes.js +7 -8
  23. package/dist/lib/platform/verification-sync.js +12 -49
  24. package/dist/lib/sandbox/capture.js +1 -1
  25. package/dist/lib/sandbox/commands/create.js +46 -1
  26. package/dist/lib/sandbox/commands/enter.js +1 -1
  27. package/dist/lib/sandbox/commands/start.js +1 -1
  28. package/dist/lib/sandbox/config.js +10 -0
  29. package/dist/lib/sandbox/control/client.js +4 -1
  30. package/dist/lib/sandbox/control/executor.js +12 -4
  31. package/dist/lib/sandbox/control/lifecycle.js +18 -13
  32. package/dist/lib/sandbox/control/protocol.js +2 -1
  33. package/dist/lib/sandbox/removal.js +15 -8
  34. package/dist/lib/sandbox/task-resolver.js +62 -2
  35. package/dist/lib/task/activity-intent.js +1 -2
  36. package/dist/lib/task/artifact-operations.js +36 -2
  37. package/dist/lib/task/artifact-schema.js +7 -7
  38. package/dist/lib/task/control-authority.js +9 -166
  39. package/dist/lib/task/decision-intents.js +2 -4
  40. package/dist/lib/task/events.js +7 -28
  41. package/dist/lib/task/finalization-handoff.js +92 -0
  42. package/dist/lib/task/finalization.js +123 -12
  43. package/dist/lib/task/intermediate-cleanup.js +9 -148
  44. package/dist/lib/task/ledger-intents.js +3 -6
  45. package/dist/lib/task/lifecycle.js +50 -11
  46. package/dist/lib/task/local-artifact-finalization.js +13 -28
  47. package/dist/lib/task/resolve-ref.js +3 -3
  48. package/dist/lib/task/review-command.js +1 -2
  49. package/dist/lib/task/review-finalization.js +1 -2
  50. package/dist/lib/task/snapshot.js +6 -6
  51. package/dist/lib/task/verification-engine.js +16 -11
  52. package/dist/lib/task/verification.js +1 -13
  53. package/dist/lib/task/workflow-warning-intents.js +2 -4
  54. package/dist/lib/task/write.js +122 -3
  55. package/dist/lifecycle-build-manifest.json +3 -3
  56. package/lib/decide.ts +5 -35
  57. package/lib/git/branch-worktree.ts +41 -0
  58. package/lib/internal/cli-route-inventory.ts +0 -2
  59. package/lib/internal/task-activity.ts +6 -34
  60. package/lib/internal/task-event.ts +4 -37
  61. package/lib/internal/task-finalization.ts +9 -2
  62. package/lib/internal/task-ledger.ts +5 -38
  63. package/lib/internal/task-lifecycle.ts +2 -2
  64. package/lib/internal/task-operation-registry.ts +1 -4
  65. package/lib/internal/task-review.ts +5 -31
  66. package/lib/internal/task-verify.ts +3 -39
  67. package/lib/internal/task-warning.ts +3 -32
  68. package/lib/merge.ts +59 -10
  69. package/lib/platform/github-provider.ts +11 -18
  70. package/lib/platform/github-release-notes.ts +89 -14
  71. package/lib/platform/none-provider.ts +1 -1
  72. package/lib/platform/pr-summary.ts +4 -25
  73. package/lib/platform/provider-contract.ts +38 -12
  74. package/lib/platform/provider-validation.ts +83 -12
  75. package/lib/platform/release-notes.ts +11 -12
  76. package/lib/platform/verification-sync.ts +13 -53
  77. package/lib/sandbox/capture.ts +1 -1
  78. package/lib/sandbox/commands/create.ts +52 -1
  79. package/lib/sandbox/commands/enter.ts +1 -1
  80. package/lib/sandbox/commands/start.ts +1 -1
  81. package/lib/sandbox/config.ts +12 -0
  82. package/lib/sandbox/control/client.ts +5 -1
  83. package/lib/sandbox/control/executor.ts +13 -4
  84. package/lib/sandbox/control/lifecycle.ts +22 -13
  85. package/lib/sandbox/control/protocol.ts +3 -2
  86. package/lib/sandbox/removal.ts +23 -7
  87. package/lib/sandbox/task-resolver.ts +51 -2
  88. package/lib/task/activity-intent.ts +1 -2
  89. package/lib/task/artifact-operations.ts +58 -2
  90. package/lib/task/artifact-schema.ts +9 -7
  91. package/lib/task/control-authority.ts +14 -188
  92. package/lib/task/decision-intents.ts +2 -4
  93. package/lib/task/events.ts +8 -27
  94. package/lib/task/finalization-handoff.ts +104 -0
  95. package/lib/task/finalization.ts +120 -12
  96. package/lib/task/intermediate-cleanup.ts +9 -125
  97. package/lib/task/ledger-intents.ts +3 -6
  98. package/lib/task/lifecycle.ts +45 -13
  99. package/lib/task/local-artifact-finalization.ts +16 -35
  100. package/lib/task/resolve-ref.ts +3 -3
  101. package/lib/task/review-command.ts +2 -5
  102. package/lib/task/review-finalization.ts +1 -4
  103. package/lib/task/snapshot.ts +6 -6
  104. package/lib/task/verification-engine.ts +20 -10
  105. package/lib/task/verification.ts +1 -15
  106. package/lib/task/workflow-warning-intents.ts +2 -4
  107. package/lib/task/write.ts +116 -5
  108. package/package.json +1 -1
  109. package/templates/.agents/rules/evidence-reporting.en.md +1 -2
  110. package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
  111. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
  112. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
  113. package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
  114. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
  115. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
  116. package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
  117. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
  118. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  119. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  120. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  121. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  122. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  123. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  124. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
  125. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
  126. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  127. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  128. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  129. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  130. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  131. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  132. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  133. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  134. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  135. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  136. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  137. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  138. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  139. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  140. package/dist/lib/internal/task-override.js +0 -138
  141. package/dist/lib/task/artifact-recovery.js +0 -476
  142. package/dist/lib/task/artifact-repair-intent.js +0 -181
  143. package/dist/lib/task/guard-override.js +0 -31
  144. package/dist/lib/task/human-override.js +0 -1259
  145. package/lib/internal/task-override.ts +0 -144
  146. package/lib/task/artifact-recovery.ts +0 -572
  147. package/lib/task/artifact-repair-intent.ts +0 -227
  148. package/lib/task/guard-override.ts +0 -50
  149. package/lib/task/human-override.ts +0 -1522
@@ -36,7 +36,9 @@ function stripQuotes(value: string): string {
36
36
 
37
37
  function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
38
38
  for (const state of TASK_WORKSPACE_STATES) {
39
- const taskPath = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
39
+ const taskPath = state === 'archive'
40
+ ? path.join(repoRoot, '.agents', 'workspace', state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
41
+ : path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
40
42
  if (fs.existsSync(taskPath)) {
41
43
  return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
42
44
  }
@@ -45,7 +47,7 @@ function readTaskContent(repoRoot: string, taskId: string): { content: string; s
45
47
  }
46
48
 
47
49
  function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
48
- return TASK_WORKSPACE_STATES.flatMap((state) => {
50
+ const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
49
51
  const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
50
52
  if (!fs.existsSync(taskMd)) return [];
51
53
  const content = fs.readFileSync(taskMd, 'utf8');
@@ -56,6 +58,32 @@ function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspac
56
58
  taskMd
57
59
  }];
58
60
  });
61
+ const parts = findArchiveTaskParts(repoRoot, taskId);
62
+ if (parts.length === 0) return hot;
63
+ const taskMd = path.join(repoRoot, '.agents', 'workspace', 'archive', ...parts, 'local', 'task.md');
64
+ if (!fs.existsSync(taskMd)) return hot;
65
+ const content = fs.readFileSync(taskMd, 'utf8');
66
+ return [...hot, {
67
+ taskId,
68
+ branch: resolveBranchFromTaskContent(content, taskId),
69
+ state: 'archive',
70
+ taskMd
71
+ }];
72
+ }
73
+
74
+ function findArchiveTaskParts(repoRoot: string, taskId: string): string[] {
75
+ const archiveRoot = path.join(repoRoot, '.agents', 'workspace', 'archive');
76
+ if (!fs.existsSync(archiveRoot)) return [];
77
+ for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
78
+ if (!/^\d{4}$/.test(year)) continue;
79
+ for (const month of fs.readdirSync(path.join(archiveRoot, year)).sort().reverse()) {
80
+ if (!/^\d{2}$/.test(month)) continue;
81
+ for (const day of fs.readdirSync(path.join(archiveRoot, year, month)).sort().reverse()) {
82
+ if (/^\d{2}$/.test(day) && fs.existsSync(path.join(archiveRoot, year, month, day, taskId, 'local', 'task.md'))) return [year, month, day, taskId];
83
+ }
84
+ }
85
+ }
86
+ return [];
59
87
  }
60
88
 
61
89
  function resolveBranchFromTaskContent(content: string, taskId: string): string {
@@ -102,6 +130,27 @@ export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWork
102
130
 
103
131
  export function listTaskWorkspaces(repoRoot: string, state: TaskWorkspaceState): TaskWorkspace[] {
104
132
  const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
133
+ if (state === 'archive') {
134
+ if (!fs.existsSync(stateRoot)) return [];
135
+ const rows: TaskWorkspace[] = [];
136
+ for (const year of fs.readdirSync(stateRoot).sort()) {
137
+ if (!/^\d{4}$/.test(year)) continue;
138
+ for (const month of fs.readdirSync(path.join(stateRoot, year)).sort()) {
139
+ if (!/^\d{2}$/.test(month)) continue;
140
+ for (const day of fs.readdirSync(path.join(stateRoot, year, month)).sort()) {
141
+ if (!/^\d{2}$/.test(day)) continue;
142
+ for (const taskId of fs.readdirSync(path.join(stateRoot, year, month, day)).sort()) {
143
+ if (!TASK_ID_RE.test(taskId)) continue;
144
+ const taskMd = path.join(stateRoot, year, month, day, taskId, 'local', 'task.md');
145
+ if (!fs.existsSync(taskMd)) continue;
146
+ const content = fs.readFileSync(taskMd, 'utf8');
147
+ rows.push({ taskId, branch: resolveBranchFromTaskContent(content, taskId), state, taskMd });
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return rows;
153
+ }
105
154
  if (!fs.existsSync(stateRoot)) return [];
106
155
  return fs.readdirSync(stateRoot, { withFileTypes: true })
107
156
  .filter((entry) => entry.isDirectory() && TASK_ID_RE.test(entry.name))
@@ -14,7 +14,6 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
14
14
  import type { TaskExecutionLockOptions } from './task-execution-lock.ts';
15
15
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
16
16
  import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
17
- import { allowsManualOverride } from './guard-override.ts';
18
17
 
19
18
  type PrReviewVerdict = 'approved' | 'changes-requested' | 'commented';
20
19
  type PrReviewOutcome = 'aborted' | 'superseded';
@@ -398,7 +397,7 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
398
397
  if ('code' in validated) return failed(intent, validated.code, validated.message);
399
398
  const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
400
399
  if (!resolved.ok) return failed(intent, resolved.code, resolved.message, resolved.taskId);
401
- if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'activity-intent', 'TASK_STATE_MISMATCH')) {
400
+ if (resolved.state !== 'active') {
402
401
  return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
403
402
  }
404
403
  if (options.lockAlreadyHeld) return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
@@ -27,6 +27,7 @@ type ArtifactStructuralDiagnosticCode =
27
27
  | 'ARTIFACT_EMPTY_SECTION'
28
28
  | 'ARTIFACT_SECTION_ORDER_INVALID'
29
29
  | 'ARTIFACT_HEADING_TRAILING_PUNCTUATION'
30
+ | 'ARTIFACT_STATE_CHECK_PATTERN_MISSING'
30
31
  | 'ARTIFACT_REQUIRED_PATTERN_MISSING';
31
32
 
32
33
  type ArtifactStructuralDiagnostic = Readonly<{
@@ -254,13 +255,68 @@ function inspectArtifactPatterns(
254
255
  };
255
256
  }
256
257
 
258
+ function inspectArtifactStateCheck(
259
+ content: string,
260
+ schema: ArtifactSchema,
261
+ scopeToStateCheckSection: boolean
262
+ ): ArtifactStructureResult {
263
+ const stateCheck = schema.sections.find((section) => section.id === 'state-check');
264
+ if (!stateCheck || schema.stateCheckPatterns.length === 0) {
265
+ return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
266
+ }
267
+
268
+ if (!scopeToStateCheckSection) {
269
+ const diagnostics = schema.stateCheckPatterns
270
+ .filter((pattern) => !new RegExp(pattern, 'm').test(content))
271
+ .map((pattern) => diagnostic(
272
+ 'ARTIFACT_STATE_CHECK_PATTERN_MISSING',
273
+ `artifact is missing required state-check command '${pattern}'`,
274
+ stateCheck.id,
275
+ null
276
+ ));
277
+ return {
278
+ ok: diagnostics.length === 0,
279
+ family: schema.family,
280
+ semanticDigest: canonicalSemanticDigest(content),
281
+ diagnostics
282
+ };
283
+ }
284
+
285
+ const scanned = scanVisibleMarkdown(content);
286
+ const headings = scanned.headings.filter((heading) => (
287
+ heading.level === 2 && sectionHeadings(schema, stateCheck).includes(heading.text)
288
+ ));
289
+ if (headings.length !== 1) {
290
+ return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
291
+ }
292
+
293
+ const bodyBounds = sectionBodyBounds(content, headings[0]!, scanned);
294
+ const body = content.slice(bodyBounds.start, bodyBounds.end);
295
+ const diagnostics = schema.stateCheckPatterns
296
+ .filter((pattern) => !new RegExp(pattern, 'm').test(body))
297
+ .map((pattern) => diagnostic(
298
+ 'ARTIFACT_STATE_CHECK_PATTERN_MISSING',
299
+ `state-check section is missing required command '${pattern}'`,
300
+ stateCheck.id,
301
+ lineNumber(content, headings[0]!.start)
302
+ ));
303
+ return {
304
+ ok: diagnostics.length === 0,
305
+ family: schema.family,
306
+ semanticDigest: canonicalSemanticDigest(content),
307
+ diagnostics
308
+ };
309
+ }
310
+
257
311
  function inspectArtifactContract(
258
312
  content: string,
259
- schema: ArtifactSchema
313
+ schema: ArtifactSchema,
314
+ options: { scopeStateCheckSection?: boolean } = {}
260
315
  ): ArtifactStructureResult {
261
316
  const structure = inspectArtifactStructure(content, schema);
317
+ const stateCheck = inspectArtifactStateCheck(content, schema, options.scopeStateCheckSection === true);
262
318
  const patterns = inspectArtifactPatterns(content, schema);
263
- const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
319
+ const diagnostics = [...structure.diagnostics, ...stateCheck.diagnostics, ...patterns.diagnostics];
264
320
  return {
265
321
  ok: diagnostics.length === 0,
266
322
  family: schema.family,
@@ -13,6 +13,7 @@ type ArtifactSchema = Readonly<{
13
13
  family: ArtifactSchemaFamily;
14
14
  title: Readonly<{ zh: string; en: string }>;
15
15
  sections: readonly ArtifactSection[];
16
+ stateCheckPatterns: readonly string[];
16
17
  requiredPatterns: readonly string[];
17
18
  }>;
18
19
  type ArtifactSkeletonInput = Readonly<{
@@ -106,21 +107,22 @@ function buildSections(family: ArtifactSchemaFamily): readonly ArtifactSection[]
106
107
  }));
107
108
  }
108
109
 
110
+ const TASK_SNAPSHOT_PATTERN = 'agent-infra-internal[ \\t]+task-snapshot\\b';
111
+
109
112
  const ARTIFACT_SCHEMAS: readonly ArtifactSchema[] = [
110
- { family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), requiredPatterns: ['^\\$ '] },
111
- { family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$', '^\\$ '] },
112
- { family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), requiredPatterns: ['^\\$ '] },
113
- { family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$', '^\\$ '] },
114
- { family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), requiredPatterns: ['^\\$ '] },
113
+ { family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
114
+ { family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
115
+ { family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
116
+ { family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: ['^### (?:审查决定|Approval Decision)$'] },
117
+ { family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), stateCheckPatterns: [TASK_SNAPSHOT_PATTERN], requiredPatterns: [] },
115
118
  { family: 'review-code', title: { zh: '代码审查报告', en: 'Code Review Report' }, sections: buildSections('review-code'), requiredPatterns: [
116
119
  '^### (?:审查决定|Approval Decision)$',
117
- '^\\$ ',
118
120
  '^- \\*\\*(?:总体结论|Overall Verdict)\\*\\*[::]\\s*(?:通过|需要修改|拒绝|Approved|Changes Requested|Rejected)\\s*$',
119
121
  '^- \\*\\*(?:审查基线提交|Review Baseline Commit)\\*\\*[::]\\s*\\S',
120
122
  '^- \\*\\*(?:审查差异基线|Reviewed Diff Base)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$',
121
123
  '^- \\*\\*(?:审查差异指纹|Reviewed Diff Fingerprint)\\*\\*[::]\\s*sha256:[0-9a-f]{64}\\s*$',
122
124
  '^- \\*\\*(?:审查快照树|Reviewed Snapshot Tree)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$'
123
- ] }
125
+ ], stateCheckPatterns: [TASK_SNAPSHOT_PATTERN] }
124
126
  ];
125
127
 
126
128
  function getArtifactSchema(family: string): ArtifactSchema | null {
@@ -1,12 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import crypto from 'node:crypto';
3
- import fs from 'node:fs';
4
3
 
5
4
  import { normalizeAgentToken } from '../agent-clients/tokens.ts';
6
5
  import { isAgentClientId } from '../agent-clients/types.ts';
7
6
  import type { AgentClientId } from '../agent-clients/types.ts';
8
7
  import { getAgentClientAdapter } from '../agent-clients/registry.ts';
9
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.ts';
10
8
  import {
11
9
  activateMatchingOrchestrationDelegation,
12
10
  activateOrchestrationDelegation,
@@ -40,19 +38,12 @@ import type {
40
38
  AgentClientLifecycleRecoveryRequest as LifecycleRecoveryRequest,
41
39
  AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult
42
40
  } from '../agent-clients/adapter.ts';
43
- import { locateActivityLog } from './activity-log.ts';
44
41
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
45
42
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
46
43
  import { verifyTaskEvent } from './verification.ts';
47
44
  import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
48
- import {
49
- findArtifactRecoveryIntentsByOperation,
50
- writeArtifactRecoveryIntent,
51
- type ArtifactRecoveryIntent
52
- } from './artifact-repair-intent.ts';
53
45
  import {
54
46
  computeLifecycleBuildIdentity,
55
- verifyLifecycleBuildIdentity,
56
47
  type LifecycleBuildIdentity
57
48
  } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
58
49
 
@@ -324,7 +315,6 @@ export function consumeLifecycleRecoveryAttestation(
324
315
  export function queryLifecycleRecoveryOperation(
325
316
  operationId: string,
326
317
  options: Readonly<{
327
- repoRoot?: string;
328
318
  capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
329
319
  }> = {}
330
320
  ): LifecycleRecoveryOperationQueryV1 {
@@ -334,7 +324,6 @@ export function queryLifecycleRecoveryOperation(
334
324
  const store = options.capabilityStore ?? createCodexCapabilityStore();
335
325
  const registry = authorityOperationMap.get(store);
336
326
  const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
337
- const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
338
327
  const capabilities = store.findByRecoveryOperation(operationId);
339
328
  const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
340
329
  for (const capability of capabilities) {
@@ -358,21 +347,10 @@ export function queryLifecycleRecoveryOperation(
358
347
  ? 'consumed' : 'issued'
359
348
  });
360
349
  }
361
- for (const intent of intents) {
362
- if (!intent.phase) continue;
363
- const key = `${intent.phase}\0${intent.requestId}`;
364
- if (phaseMap.has(key)) continue;
365
- phaseMap.set(key, {
366
- phase: intent.phase,
367
- requestId: intent.requestId,
368
- attestationId: null,
369
- state: intent.state === 'consumed' ? 'consumed' : 'observed'
370
- });
371
- }
372
350
  const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
373
351
  ? 'consumed'
374
352
  : capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
375
- const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
353
+ const committed = capabilityState === 'consumed';
376
354
  return {
377
355
  version: 1,
378
356
  operationId,
@@ -382,133 +360,6 @@ export function queryLifecycleRecoveryOperation(
382
360
  };
383
361
  }
384
362
 
385
- type LifecycleRecoveryCompensationOptions = Readonly<{
386
- repoRoot: string;
387
- controllerBinding: TaskControlControllerBinding;
388
- capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
389
- buildIdentity?: LifecycleBuildIdentity;
390
- now?: () => number;
391
- }>;
392
-
393
- function lifecycleRecoveryCompensationError(code: string, message: string): Error {
394
- const error = new Error(`${code}: ${message}`);
395
- error.name = code;
396
- return error;
397
- }
398
-
399
- function lifecycleRecoveryEventCommitted(
400
- selector: LifecycleAuthorityRequestV1,
401
- repoRoot: string
402
- ): boolean {
403
- const resolved = resolveTaskRef(selector.taskId, { repoRoot });
404
- if (!resolved.ok || resolved.taskId !== selector.taskId) return false;
405
- let content: string;
406
- try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
407
- catch { return false; }
408
- const section = locateActivityLog(content);
409
- if (!section) return false;
410
- const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' } as const;
411
- const prefix = `${labels[selector.family]} (Round ${selector.round}`;
412
- return section.entries.some((entry) => {
413
- if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix)) return false;
414
- const delimiter = entry.step[prefix.length];
415
- return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
416
- });
417
- }
418
-
419
- function lifecycleRecoveryIntentMatches(
420
- intent: ArtifactRecoveryIntent,
421
- selector: LifecycleAuthorityRequestV1
422
- ): boolean {
423
- return intent.taskId === selector.taskId
424
- && intent.family === selector.family
425
- && intent.artifact === selector.artifact
426
- && (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
427
- }
428
-
429
- /**
430
- * Completes only the durable tail of a committed task event after a process
431
- * restart. The task event itself is the commit boundary and is never replayed.
432
- */
433
- export function recoverLifecycleRecoveryOperation(
434
- input: LifecycleAuthorityRequestV1,
435
- options: LifecycleRecoveryCompensationOptions
436
- ): LifecycleRecoveryOperationQueryV1 {
437
- const selector = validateLifecycleAuthorityRequest(input);
438
- if (selector.phase !== 'task-event.completed') {
439
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
440
- }
441
- const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
442
- if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
443
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
444
- }
445
- if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
446
- || selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
447
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
448
- }
449
- const store = options.capabilityStore ?? createCodexCapabilityStore();
450
- const expected = {
451
- taskId: selector.taskId,
452
- hookDefinitionHash: selector.expectedHookDefinitionHash,
453
- buildIdentity,
454
- controller: options.controllerBinding
455
- };
456
- const capabilities = store.findByRecoveryOperation(selector.operationId);
457
- if (capabilities.length !== 1 || capabilities[0]!.capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
458
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
459
- }
460
- const capability = capabilities[0]!;
461
- const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
462
- const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
463
- if (!build.ok
464
- || capability.taskId !== selector.taskId
465
- || capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
466
- || capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
467
- || capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
468
- || capability.recoveryState === 'unreserved'
469
- || capability.recoveryOperationId !== selector.operationId
470
- || (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
471
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
472
- }
473
- const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
474
- if (!phase) {
475
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
476
- }
477
- const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
478
- .filter((intent) => intent.taskId === selector.taskId
479
- && intent.family === selector.family
480
- && intent.artifact === selector.artifact);
481
- if (intents.length !== 1) {
482
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
483
- }
484
- const intent = intents[0]!;
485
- if (!lifecycleRecoveryIntentMatches(intent, selector)
486
- || intent.phase !== selector.phase
487
- || intent.requestId !== selector.lifecycleRequestId
488
- || intent.authorityDigest === null) {
489
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
490
- }
491
- if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
492
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
493
- }
494
- if (capability.recoveryState === 'reserved') {
495
- if (phase.state === 'issued') {
496
- store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
497
- }
498
- store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
499
- } else if (phase.state !== 'consumed') {
500
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
501
- }
502
- if (intent.state !== 'consumed') {
503
- writeArtifactRecoveryIntent(options.repoRoot, {
504
- ...intent,
505
- state: 'consumed',
506
- updatedAt: (options.now ?? Date.now)()
507
- }, { expected: intent });
508
- }
509
- return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
510
- }
511
-
512
363
  function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
513
364
  return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
514
365
  }
@@ -537,6 +388,7 @@ export type TaskControlExecutionContext =
537
388
  generation: string;
538
389
  manifestPath: string;
539
390
  requestId: string;
391
+ handoffDirectory: string;
540
392
  controllerBinding?: TaskControlControllerBinding | null;
541
393
  lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
542
394
  }>;
@@ -575,11 +427,7 @@ export function taskControlOperationKey(operation: TaskControlOperation): string
575
427
  : operation.intent;
576
428
  }
577
429
 
578
- export type TaskLifecycleControlRequest = TaskLifecycleRequest & Readonly<{
579
- overrideTicket?: string;
580
- overrideTarget?: string;
581
- overrideScope?: string;
582
- }>;
430
+ export type TaskLifecycleControlRequest = TaskLifecycleRequest;
583
431
 
584
432
  function invalidContext(message: string): never {
585
433
  throw new Error(`TASK_CONTROL_CONTEXT_INVALID: ${message}`);
@@ -625,6 +473,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
625
473
  generation: string;
626
474
  manifestPath: string;
627
475
  requestId: string;
476
+ handoffDirectory?: string;
628
477
  controllerBinding?: TaskControlControllerBinding | null;
629
478
  lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
630
479
  }>): TaskControlExecutionContext {
@@ -645,6 +494,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
645
494
  generation: params.generation,
646
495
  manifestPath: absolute('manifestPath', params.manifestPath),
647
496
  requestId: params.requestId,
497
+ handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
648
498
  ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
649
499
  ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
650
500
  lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
@@ -670,6 +520,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
670
520
  absolute('worktreeRoot', context.worktreeRoot);
671
521
  absolute('runtimeDir', context.runtimeDir);
672
522
  absolute('manifestPath', context.manifestPath);
523
+ absolute('handoffDirectory', context.handoffDirectory);
673
524
  if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
674
525
  invalidContext('runtimeDir is not bound to the manifest control root');
675
526
  }
@@ -706,12 +557,8 @@ function lifecycleTaskId(request: TaskLifecycleControlRequest, repoRoot: string)
706
557
  async function applyLifecycleWithAuthority(
707
558
  context: TaskControlExecutionContext,
708
559
  request: TaskLifecycleControlRequest
709
- ): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> {
710
- const conflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
711
- if (conflict) {
712
- return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
713
- }
714
- const execute = async (): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> => {
560
+ ): Promise<TaskLifecycleResult | LifecycleRecoveryResult> {
561
+ const execute = async (): Promise<TaskLifecycleResult | LifecycleRecoveryResult> => {
715
562
  if (request.intent === 'recover-started') {
716
563
  const adapter = isAgentClientId(request.agent)
717
564
  ? getAgentClientAdapter(request.agent).orchestrationAdapter
@@ -725,27 +572,7 @@ async function applyLifecycleWithAuthority(
725
572
  }
726
573
  return adapter.recoverStarted(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
727
574
  }
728
- const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
729
- if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
730
- if (!request.overrideTarget || !request.overrideScope) {
731
- return {
732
- ...lifecycleResult,
733
- error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
734
- };
735
- }
736
- const override = await consumeHumanOverride({
737
- taskRef: request.taskRef,
738
- ticketId: request.overrideTicket,
739
- failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
740
- target: request.overrideTarget,
741
- scope: request.overrideScope,
742
- intent: request.intent,
743
- ...('alertNumber' in request && request.alertNumber ? { alertNumber: request.alertNumber } : {}),
744
- ...('issueNumber' in request && request.issueNumber ? { issueNumber: request.issueNumber } : {}),
745
- ...('stagingDir' in request && request.stagingDir ? { stagingDir: request.stagingDir } : {})
746
- }, { repoRoot: context.repoRoot });
747
- if (override.status === 'failed') return { ...lifecycleResult, humanOverride: override };
748
- return { ...lifecycleResult, ...override, humanOverride: override, error: null };
575
+ return applyTaskLifecycle(request, { repoRoot: context.repoRoot });
749
576
  };
750
577
  const taskId = lifecycleTaskId(request, context.repoRoot);
751
578
  if (!taskId) return await execute();
@@ -840,7 +667,7 @@ function orchestrationFailure(error: unknown): OrchestrationResult {
840
667
  export function dispatchTaskControlOperation(
841
668
  context: TaskControlExecutionContext,
842
669
  operation: Extract<TaskControlOperation, { family: 'task-lifecycle' }>
843
- ): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }>;
670
+ ): Promise<TaskLifecycleResult | LifecycleRecoveryResult>;
844
671
  export function dispatchTaskControlOperation(
845
672
  context: TaskControlExecutionContext,
846
673
  operation: Extract<TaskControlOperation, { family: 'task-finalization' }>
@@ -865,7 +692,9 @@ export function dispatchTaskControlOperation(
865
692
  return finalize(operation.request, {
866
693
  repoRoot: context.repoRoot,
867
694
  ...(context.source === 'sandbox-executor' ? {
868
- controlBinding: { generation: context.generation, requestId: context.requestId }
695
+ controlBinding: { generation: context.generation, requestId: context.requestId },
696
+ handoffDirectory: context.handoffDirectory,
697
+ manifestPath: context.manifestPath
869
698
  } : {}),
870
699
  preflight: (request, options) => verifyTaskEvent(
871
700
  { ...request, event: 'complete-task.hard-preflight' }, options
@@ -904,7 +733,7 @@ function operationInvalid(message: string): never {
904
733
 
905
734
  const LIFECYCLE_FLAGS = new Set([
906
735
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
907
- '--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
736
+ '--auto', '--dry-run'
908
737
  ]);
909
738
 
910
739
  const FINALIZATION_FLAGS = new Set(['--agent']);
@@ -979,9 +808,6 @@ export function parseTaskControlOperation(
979
808
  ...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
980
809
  ...(value(values, '--note') ? { note: value(values, '--note') } : {}),
981
810
  ...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
982
- ...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
983
- ...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
984
- ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
985
811
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
986
812
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
987
813
  ...(values['--auto'] === true ? { auto: true } : {}),
@@ -16,7 +16,6 @@ import { resolveTaskContext } from './resolve-ref.ts';
16
16
  import { extractSection, extractSubSection, findSectionHeading } from './sections.ts';
17
17
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
18
18
  import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
19
- import { allowsManualOverride } from './guard-override.ts';
20
19
  import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.ts';
21
20
 
22
21
  type HumanDecisionRequest = {
@@ -87,8 +86,7 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
87
86
  if (!request.selector || !request.decision.trim()) return failed('DECISION_PAYLOAD_INVALID', 'selector and decision are required');
88
87
  const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
89
88
  if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
90
- const stateOverride = allowsManualOverride(options.manualOverride, 'decision-intent', 'TASK_STATE_MISMATCH');
91
- if (resolved.state !== 'active' && !stateOverride) return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
89
+ if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
92
90
  let content: string;
93
91
  let rows: LedgerRow[];
94
92
  try {
@@ -186,7 +184,7 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
186
184
  } catch (error) {
187
185
  return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
188
186
  }
189
- const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
187
+ const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
190
188
  ...options,
191
189
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
192
190
  metadataProvider: () => metadata