@fitlab-ai/agent-infra 0.11.1 → 0.11.3

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 (131) hide show
  1. package/dist/bin/internal-cli.js +7 -2
  2. package/dist/lib/agent-clients/adapter.js +3 -0
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  6. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  7. package/dist/lib/internal/cli-route-inventory.js +1 -1
  8. package/dist/lib/internal/manual-validation.js +22 -27
  9. package/dist/lib/internal/platform-comment.js +59 -0
  10. package/dist/lib/internal/sandbox-control.js +2 -171
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +7 -2
  14. package/dist/lib/internal/task-review.js +5 -5
  15. package/dist/lib/internal/task-validate.js +0 -6
  16. package/dist/lib/platform/comment-safety.js +7 -1
  17. package/dist/lib/platform/github-provider.js +13 -3
  18. package/dist/lib/platform/issue-comments.js +93 -15
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/platform/provider-contract.d.ts +2 -0
  21. package/dist/lib/platform/provider-validation.js +7 -2
  22. package/dist/lib/platform/verification-sync.js +19 -2
  23. package/dist/lib/sandbox/control/client.js +3 -44
  24. package/dist/lib/sandbox/control/executor.js +14 -117
  25. package/dist/lib/sandbox/control/finalization-response.js +8 -2
  26. package/dist/lib/sandbox/control/protocol.js +5 -64
  27. package/dist/lib/sandbox/control/server.js +29 -206
  28. package/dist/lib/sandbox/control/state.js +1 -7
  29. package/dist/lib/sandbox/control/task-view.js +2 -1
  30. package/dist/lib/task/artifact-command.js +6 -6
  31. package/dist/lib/task/codex-orchestration.js +10 -208
  32. package/dist/lib/task/commit-operation.js +3 -18
  33. package/dist/lib/task/control-authority.js +35 -44
  34. package/dist/lib/task/control-recovery.js +3 -42
  35. package/dist/lib/task/delegation-receipts.js +16 -78
  36. package/dist/lib/task/events.js +89 -237
  37. package/dist/lib/task/finalization-artifacts.js +1 -3
  38. package/dist/lib/task/finalization-state.js +2 -1
  39. package/dist/lib/task/finalization.js +208 -57
  40. package/dist/lib/task/lifecycle-execution.js +2 -11
  41. package/dist/lib/task/lifecycle.js +1 -1
  42. package/dist/lib/task/local-artifact-finalization.js +25 -242
  43. package/dist/lib/task/manual-validation-receipt.js +8 -3
  44. package/dist/lib/task/orchestration.js +12 -24
  45. package/dist/lib/task/review-command.js +0 -1
  46. package/dist/lib/task/review-finalization.js +46 -211
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapter.ts +46 -0
  49. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  51. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  52. package/lib/agent-clients/adapters/codex.ts +10 -0
  53. package/lib/internal/cli-route-inventory.ts +1 -1
  54. package/lib/internal/manual-validation.ts +22 -26
  55. package/lib/internal/platform-comment.ts +38 -0
  56. package/lib/internal/sandbox-control.ts +1 -185
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +2 -2
  59. package/lib/internal/task-operation-registry.ts +8 -4
  60. package/lib/internal/task-review.ts +5 -5
  61. package/lib/internal/task-validate.ts +0 -5
  62. package/lib/platform/comment-safety.ts +8 -0
  63. package/lib/platform/github-provider.ts +11 -2
  64. package/lib/platform/issue-comments.ts +102 -17
  65. package/lib/platform/pr-summary.ts +10 -2
  66. package/lib/platform/provider-contract.ts +2 -0
  67. package/lib/platform/provider-validation.ts +6 -2
  68. package/lib/platform/verification-sync.ts +19 -2
  69. package/lib/sandbox/control/client.ts +5 -68
  70. package/lib/sandbox/control/executor.ts +14 -125
  71. package/lib/sandbox/control/finalization-response.ts +8 -2
  72. package/lib/sandbox/control/protocol.ts +7 -83
  73. package/lib/sandbox/control/server.ts +30 -220
  74. package/lib/sandbox/control/state.ts +1 -9
  75. package/lib/sandbox/control/task-view.ts +2 -1
  76. package/lib/task/artifact-command.ts +6 -7
  77. package/lib/task/codex-orchestration.ts +8 -241
  78. package/lib/task/commit-operation.ts +3 -23
  79. package/lib/task/control-authority.ts +39 -55
  80. package/lib/task/control-recovery.ts +3 -43
  81. package/lib/task/delegation-receipts.ts +14 -81
  82. package/lib/task/events.ts +106 -255
  83. package/lib/task/finalization-artifacts.ts +1 -3
  84. package/lib/task/finalization-state.ts +2 -1
  85. package/lib/task/finalization.ts +221 -65
  86. package/lib/task/lifecycle-execution.ts +10 -21
  87. package/lib/task/lifecycle.ts +2 -2
  88. package/lib/task/local-artifact-finalization.ts +23 -281
  89. package/lib/task/manual-validation-receipt.ts +8 -2
  90. package/lib/task/orchestration.ts +30 -53
  91. package/lib/task/review-command.ts +0 -1
  92. package/lib/task/review-finalization.ts +45 -193
  93. package/package.json +2 -2
  94. package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
  95. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
  96. package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
  97. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
  98. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  101. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  102. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  103. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  104. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  105. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  106. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  107. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  108. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  109. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  110. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  111. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  112. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  113. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  114. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  115. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  116. package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
  117. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
  118. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  119. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  120. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  121. package/dist/lib/sandbox/control/workflow-executor.js +0 -157
  122. package/dist/lib/task/lifecycle-recovery.js +0 -549
  123. package/dist/lib/task/recovery-warning.js +0 -19
  124. package/dist/lib/task/workflow-command.js +0 -18
  125. package/dist/lib/task/workflow-dispatch.js +0 -161
  126. package/lib/sandbox/control/task-workflow.ts +0 -95
  127. package/lib/sandbox/control/workflow-executor.ts +0 -183
  128. package/lib/task/lifecycle-recovery.ts +0 -691
  129. package/lib/task/recovery-warning.ts +0 -21
  130. package/lib/task/workflow-command.ts +0 -19
  131. package/lib/task/workflow-dispatch.ts +0 -171
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
2
4
  import { parseArtifactName } from './artifact-name.js';
3
- import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
5
+ import { validateCompletedArtifact, validateArtifactPublication } from './artifact-lifecycle.js';
4
6
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
5
7
  import { finalizeReviewSummaryContent } from './review-artifacts.js';
6
8
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
@@ -11,8 +13,6 @@ import { allowsManualOverride } from './guard-override.js';
11
13
  import { getArtifactSchema } from './artifact-schema.js';
12
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
13
15
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
14
- import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
15
- import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
16
16
  const STAGES = {
17
17
  analysis: { family: 'review-analysis' },
18
18
  plan: { family: 'review-plan' },
@@ -47,7 +47,7 @@ function preflightFailed(request, code, message, taskId = null, extra = {}) {
47
47
  stageStatus: null
48
48
  };
49
49
  }
50
- /** Validate and seal a review draft before any finding-ledger write. */
50
+ /** Validate the current review draft before any finding-ledger write. */
51
51
  function preflightReviewSummaryUnlocked(request, options = {}) {
52
52
  const stage = request.stage;
53
53
  const spec = STAGES[stage];
@@ -68,37 +68,16 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
68
68
  return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
69
69
  let taskContent;
70
70
  let content;
71
- let recovery;
72
71
  try {
73
72
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
74
- if (request.recoveryId) {
75
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
76
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
77
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal', resolved.taskId);
78
- if (!['awaiting-preflight-recovery', 'preflight-ready'].includes(intent.state))
79
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`, resolved.taskId);
80
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
81
- content = fs.existsSync(recovery.stagingPath)
82
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
83
- : fs.readFileSync(recovery.formalPath, 'utf8');
84
- }
85
- else {
86
- const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
87
- if (!validated.ok)
88
- return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
89
- content = fs.readFileSync(validated.artifact.path, 'utf8');
90
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
91
- if (intent?.state === 'preflight-ready')
92
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
93
- if (intent?.state === 'consumed')
94
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'review artifact was already finalized', resolved.taskId);
95
- }
73
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
74
+ if (!validated.ok)
75
+ return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
76
+ content = fs.readFileSync(validated.artifact.path, 'utf8');
96
77
  }
97
78
  catch (error) {
98
79
  return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
99
80
  }
100
- if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round))
101
- return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
102
81
  const execution = validateLifecycleExecution(request.taskRef, {
103
82
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
104
83
  identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
@@ -120,17 +99,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
120
99
  });
121
100
  const qualificationError = qualification.ok ? null : qualification;
122
101
  if (!detail.ok || !structure.ok || qualificationError) {
123
- if (!recovery && !request.dryRun) {
124
- try {
125
- recovery = beginArtifactRecovery({
126
- taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
127
- requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
128
- }, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
129
- }
130
- catch (error) {
131
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId);
132
- }
133
- }
134
102
  const diagnostics = [
135
103
  ...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
136
104
  ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
@@ -138,30 +106,18 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
138
106
  ].join('; ');
139
107
  return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
140
108
  artifactSha256, semanticDigest,
141
- ...(recovery ? { recovery: recoveryInfo(recovery) } : {})
142
109
  });
143
110
  }
144
- if (request.dryRun || recovery && readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact)?.state === 'preflight-ready') {
111
+ if (request.dryRun) {
145
112
  return {
146
- ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, ...(recovery ? { recovery: recoveryInfo(recovery) } : {}) }),
113
+ ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
147
114
  status: 'passed', changed: false, error: null
148
115
  };
149
116
  }
150
- try {
151
- recovery ??= beginArtifactRecovery({
152
- taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
153
- requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
154
- }, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
155
- const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: options.lockAlreadyHeld });
156
- prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: options.lockAlreadyHeld });
157
- return {
158
- ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, recovery: recoveryInfo(recovery) }),
159
- status: 'passed', changed: false, error: null
160
- };
161
- }
162
- catch (error) {
163
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, { artifactSha256, semanticDigest });
164
- }
117
+ return {
118
+ ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
119
+ status: 'passed', changed: false, error: null
120
+ };
165
121
  }
166
122
  function preflightReviewSummary(request, options = {}) {
167
123
  if (request.dryRun || options.lockAlreadyHeld)
@@ -178,14 +134,6 @@ function preflightReviewSummary(request, options = {}) {
178
134
  return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
179
135
  }
180
136
  }
181
- function recoveryInfo(recovery) {
182
- return {
183
- recoveryId: recovery.recoveryId,
184
- candidatePath: recovery.stagingPath,
185
- baselineSha256: recovery.baselineSha256,
186
- baselineSemanticDigest: recovery.baselineSemanticDigest
187
- };
188
- }
189
137
  /** Prepare domain validation, summary bytes and provenance before any publication. */
190
138
  function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
191
139
  let taskId = null;
@@ -219,45 +167,9 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
219
167
  catch (error) {
220
168
  return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
221
169
  }
222
- let recovery;
223
- if (request.recoveryId) {
224
- try {
225
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
226
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
227
- return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
228
- if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
229
- return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
230
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
231
- artifactContent = fs.existsSync(recovery.stagingPath)
232
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
233
- : fs.readFileSync(recovery.formalPath, 'utf8');
234
- }
235
- catch (error) {
236
- return reject('REVIEW_PROVENANCE_INVALID', String(error));
237
- }
238
- }
239
170
  const artifactSha256 = sha256Content(artifactContent);
240
171
  const semanticDigest = canonicalSemanticDigest(artifactContent);
241
172
  const digests = { artifactSha256, semanticDigest };
242
- let repairIntent;
243
- try {
244
- repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
245
- }
246
- catch (error) {
247
- return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
248
- }
249
- if (repairIntent?.state === 'consumed') {
250
- if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
251
- return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
252
- }
253
- }
254
- if (repairIntent?.state === 'passed'
255
- && (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
256
- repairIntent = undefined;
257
- }
258
- if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
259
- return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
260
- }
261
173
  const execution = validateLifecycleExecution(request.taskRef, {
262
174
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
263
175
  identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
@@ -284,67 +196,16 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
284
196
  const schema = getArtifactSchema(spec.family);
285
197
  const structure = inspectArtifactContract(artifactContent, schema);
286
198
  if (!structure.ok) {
287
- if (!recovery && !request.dryRun && options.startRecovery) {
288
- try {
289
- recovery = beginArtifactRecovery({
290
- taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
291
- requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
292
- }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
293
- }
294
- catch (error) {
295
- return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
296
- }
297
- }
298
- return {
299
- ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
300
- artifactSha256, semanticDigest: structure.semanticDigest,
301
- ...(recovery ? { recovery: {
302
- recoveryId: recovery.recoveryId,
303
- candidatePath: recovery.stagingPath,
304
- baselineSha256: recovery.baselineSha256,
305
- baselineSemanticDigest: recovery.baselineSemanticDigest
306
- } } : {})
307
- }),
308
- ...(recovery ? { recovery } : {})
309
- };
199
+ return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
200
+ artifactSha256, semanticDigest: structure.semanticDigest
201
+ });
310
202
  }
311
203
  const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
312
- refreshNumericCounts: Boolean(request.recoveryId && recovery)
204
+ refreshNumericCounts: true
313
205
  });
314
206
  if (!transformed.ok)
315
207
  return reject(transformed.code, transformed.message);
316
208
  const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
317
- if (!recovery && repairIntent?.state === 'commit-started') {
318
- if (repairIntent.finalArtifactSha256 !== finalDigests.artifactSha256 || repairIntent.finalSemanticDigest !== finalDigests.semanticDigest) {
319
- return reject('REVIEW_PROVENANCE_INVALID', 'formal review artifact does not match the interrupted recovery journal', finalDigests);
320
- }
321
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, repairIntent);
322
- }
323
- if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
324
- if (transformed.changed)
325
- return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
326
- return {
327
- content: transformed.content,
328
- result: {
329
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
330
- status: 'no-op',
331
- changed: false,
332
- error: null
333
- },
334
- lockAlreadyHeld: options.lockAlreadyHeld
335
- };
336
- }
337
- if (!recovery && !request.dryRun && options.startRecovery) {
338
- try {
339
- recovery = beginArtifactRecovery({
340
- taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
341
- requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
342
- }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
343
- }
344
- catch (error) {
345
- return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
346
- }
347
- }
348
209
  return {
349
210
  content: transformed.content,
350
211
  result: {
@@ -354,65 +215,26 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
354
215
  operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
355
216
  error: null
356
217
  },
357
- ...(recovery ? { recovery } : {}),
358
218
  lockAlreadyHeld: options.lockAlreadyHeld
359
219
  };
360
220
  }
361
221
  function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
362
- if (prepared.result.status === 'failed' || !prepared.recovery)
363
- return prepared.result;
222
+ return prepared.result;
223
+ }
224
+ function replaceCurrentArtifactAtomically(file, content) {
225
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
364
226
  try {
365
- const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
366
- if (!intent)
367
- throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
368
- let committed;
369
- if (intent.state === 'passed' || intent.state === 'consumed') {
370
- return {
371
- ...prepared.result,
372
- status: 'no-op',
373
- changed: false,
374
- artifactSha256: intent.finalArtifactSha256,
375
- semanticDigest: intent.finalSemanticDigest,
376
- error: null
377
- };
378
- }
379
- else if (intent.state === 'commit-started') {
380
- const reconciled = reconcileArtifactRecovery(prepared.recovery, {
381
- lockAlreadyHeld: prepared.lockAlreadyHeld,
382
- afterPublish: options.afterPublish
383
- });
384
- committed = reconciled.intent;
385
- }
386
- else if (intent.state === 'awaiting-preflight-recovery') {
387
- const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
388
- prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
389
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
390
- }
391
- else if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
392
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
393
- }
394
- if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
395
- prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
396
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
227
+ fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
228
+ fs.renameSync(temporary, file);
229
+ }
230
+ finally {
231
+ try {
232
+ fs.unlinkSync(temporary);
397
233
  }
398
- else if (intent.state === 'full-finalizer-ready') {
399
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
234
+ catch (error) {
235
+ if (error.code !== 'ENOENT')
236
+ throw error;
400
237
  }
401
- if (!committed || committed.state !== 'passed')
402
- throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
403
- return {
404
- ...prepared.result,
405
- artifactSha256: committed.finalArtifactSha256,
406
- semanticDigest: committed.finalSemanticDigest,
407
- error: null
408
- };
409
- }
410
- catch (error) {
411
- if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
412
- throw error;
413
- return { ...prepared.result, status: 'failed', error: {
414
- code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
415
- } };
416
238
  }
417
239
  }
418
240
  function finalizeReviewSummaryUnlocked(request, options = {}) {
@@ -437,10 +259,23 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
437
259
  const prepared = prepareReviewSummaryCandidate(request, content, options);
438
260
  if (request.dryRun)
439
261
  return prepared.result;
440
- return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
262
+ if (prepared.result.status === 'failed' || !prepared.result.changed)
263
+ return prepared.result;
264
+ try {
265
+ replaceCurrentArtifactAtomically(validated.artifact.path, prepared.content);
266
+ return {
267
+ ...prepared.result,
268
+ status: 'applied',
269
+ artifactSha256: sha256Content(prepared.content),
270
+ semanticDigest: canonicalSemanticDigest(prepared.content)
271
+ };
272
+ }
273
+ catch (error) {
274
+ return failed(request, 'REVIEW_RECOVERY_COMMIT_FAILED', `cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`, resolved.taskId);
275
+ }
441
276
  }
442
277
  function finalizeReviewSummary(request, options = {}) {
443
- const effectiveOptions = { ...options, startRecovery: !request.dryRun };
278
+ const effectiveOptions = options;
444
279
  if (request.dryRun || options.lockAlreadyHeld)
445
280
  return finalizeReviewSummaryUnlocked(request, effectiveOptions);
446
281
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.1",
4
- "sourceInputHash": "0d66fb32bbdc0982d38a04b86b0278768bb6091eb945ebd90a432f9bae363712",
5
- "internalExecutableBuildHash": "4b1edd20ed87d3d742e5e7975fd49b1ac1fb0ff14f837d52ee9ba3ab11318365"
3
+ "packageVersion": "0.11.3",
4
+ "sourceInputHash": "99efbc575d475879773bf1676cc2de5ea628b9c193f69a9812a723313ba5ae7f",
5
+ "internalExecutableBuildHash": "6912a66863d013c476699b88cc1d4154082971164cbae3f98ea79dda39a46e7a"
6
6
  }
@@ -7,6 +7,10 @@ import type {
7
7
  AgentClientCapabilityMap,
8
8
  AgentClientId
9
9
  } from './types.ts';
10
+ import type {
11
+ OrchestrationOptions,
12
+ OrchestrationResult
13
+ } from '../task/orchestration.ts';
10
14
  import {
11
15
  SANDBOX_HOOK_PHASES
12
16
  } from '../sandbox/tool-types.ts';
@@ -43,6 +47,41 @@ type AgentClientDelegationEvidence = Readonly<{
43
47
  actualReasoningEffort: 'host-event' | 'app-server' | 'spawn-ack' | 'unavailable';
44
48
  }>;
45
49
 
50
+ type AgentClientLifecycleRecoveryRequest = Readonly<{
51
+ taskRef: string;
52
+ intent: 'recover-started';
53
+ agent: string;
54
+ auto: true;
55
+ }>;
56
+
57
+ type AgentClientLifecycleRecoveryResult = Readonly<{
58
+ status: 'applied' | 'no-op' | 'owner-unknown' | 'conflict';
59
+ changed: boolean;
60
+ targetState: 'active';
61
+ requestRef: string;
62
+ intent: 'recover-started';
63
+ taskId: string | null;
64
+ receiptId: string | null;
65
+ childId: string | null;
66
+ error: Readonly<{ code: string; message: string }> | null;
67
+ }>;
68
+
69
+ type AgentClientOrchestrationAdapter = Readonly<{
70
+ prepareDelegation?: (
71
+ taskRef: string,
72
+ input: Readonly<{
73
+ client: AgentClientId;
74
+ requestedModel?: string;
75
+ requestedReasoningEffort?: string;
76
+ }>,
77
+ options?: Readonly<{ repoRoot?: string; orchestrationOptions?: OrchestrationOptions }>
78
+ ) => Promise<OrchestrationResult>;
79
+ recoverStarted?: (
80
+ request: AgentClientLifecycleRecoveryRequest,
81
+ options: Readonly<{ repoRoot: string }>
82
+ ) => AgentClientLifecycleRecoveryResult | Promise<AgentClientLifecycleRecoveryResult>;
83
+ }>;
84
+
46
85
  type AgentClientSeedCommand = Readonly<{
47
86
  templates: Readonly<{
48
87
  en: string;
@@ -75,6 +114,7 @@ type AgentClientAdapter = Readonly<{
75
114
  capabilities: AgentClientCapabilities;
76
115
  modelSelection: AgentClientModelSelectionContext;
77
116
  delegationEvidence: AgentClientDelegationEvidence;
117
+ orchestrationAdapter?: AgentClientOrchestrationAdapter;
78
118
  project: AgentClientProjectDescriptor;
79
119
  sandbox: AgentClientSandboxDescriptor;
80
120
  }>;
@@ -628,6 +668,9 @@ function defineAgentClientAdapter(
628
668
  capabilities: Object.freeze(capabilities),
629
669
  modelSelection: frozenModelSelection,
630
670
  delegationEvidence: Object.freeze({ ...evidence }),
671
+ ...(candidate.orchestrationAdapter === undefined
672
+ ? {}
673
+ : { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
631
674
  project: Object.freeze({
632
675
  ownedPathPrefixes: Object.freeze(paths),
633
676
  ...projectAssets,
@@ -650,6 +693,9 @@ export type {
650
693
  AgentClientCapabilities,
651
694
  AgentClientCustomCommandDescriptor,
652
695
  AgentClientDelegationEvidence,
696
+ AgentClientLifecycleRecoveryRequest,
697
+ AgentClientLifecycleRecoveryResult,
698
+ AgentClientOrchestrationAdapter,
653
699
  AgentClientManifestEntry,
654
700
  AgentClientModelSelectionContext,
655
701
  AgentClientProjectDescriptor,