@fitlab-ai/agent-infra 0.9.15 → 0.11.0

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 (224) hide show
  1. package/README.md +2 -6
  2. package/README.zh-CN.md +2 -6
  3. package/bin/internal-cli.sh +0 -13
  4. package/dist/bin/internal-cli.js +11 -49
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  7. package/dist/lib/fs/durable-write.js +2 -0
  8. package/dist/lib/internal/cli-route-inventory.js +3 -4
  9. package/dist/lib/internal/manual-validation.js +17 -8
  10. package/dist/lib/internal/platform-comment.js +60 -2
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-finalization.js +2 -2
  13. package/dist/lib/internal/task-lifecycle.js +4 -4
  14. package/dist/lib/internal/task-operation-registry.js +22 -13
  15. package/dist/lib/internal/task-orchestration.js +2 -2
  16. package/dist/lib/internal/task-review.js +12 -7
  17. package/dist/lib/platform/activity-recovery.js +159 -0
  18. package/dist/lib/platform/issue-comments.js +89 -31
  19. package/dist/lib/platform/issues.js +1 -1
  20. package/dist/lib/platform/pr-change-report.js +5 -4
  21. package/dist/lib/platform/pr-summary.js +64 -32
  22. package/dist/lib/platform/pull-requests.js +3 -3
  23. package/dist/lib/platform/task-comment-projection.js +70 -0
  24. package/dist/lib/platform/task-identities.js +2 -9
  25. package/dist/lib/platform/task-recovery.js +55 -0
  26. package/dist/lib/platform/verification-sync.js +45 -7
  27. package/dist/lib/pr-review/evidence-grading.js +4 -8
  28. package/dist/lib/sandbox/commands/rm.js +21 -2
  29. package/dist/lib/sandbox/control/executor.js +4 -2
  30. package/dist/lib/sandbox/control/server.js +29 -9
  31. package/dist/lib/sandbox/control/state.js +7 -1
  32. package/dist/lib/sandbox/control/task-workflow.js +1 -1
  33. package/dist/lib/sandbox/control/workflow-executor.js +87 -35
  34. package/dist/lib/sandbox/index.js +1 -2
  35. package/dist/lib/sandbox/removal.js +116 -1011
  36. package/dist/lib/sandbox/workspace-identity.js +17 -1
  37. package/dist/lib/server/process-state.js +17 -5
  38. package/dist/lib/task/artifact-command.js +11 -30
  39. package/dist/lib/task/artifact-operations.js +17 -231
  40. package/dist/lib/task/artifact-recovery.js +476 -0
  41. package/dist/lib/task/artifact-repair-intent.js +90 -39
  42. package/dist/lib/task/commit-operation.js +31 -4
  43. package/dist/lib/task/control-authority.js +39 -8
  44. package/dist/lib/task/control-recovery.js +14 -2
  45. package/dist/lib/task/create-service.js +1 -8
  46. package/dist/lib/task/delegation-receipts.js +64 -3
  47. package/dist/lib/task/events.js +165 -23
  48. package/dist/lib/task/human-override.js +5 -8
  49. package/dist/lib/task/intermediate-cleanup.js +91 -8
  50. package/dist/lib/task/lifecycle-recovery.js +549 -0
  51. package/dist/lib/task/lifecycle.js +12 -3
  52. package/dist/lib/task/local-artifact-finalization.js +212 -123
  53. package/dist/lib/task/manual-validation-receipt.js +5 -1
  54. package/dist/lib/task/orchestration.js +52 -2
  55. package/dist/lib/task/pr-delivery-fact.js +12 -56
  56. package/dist/lib/task/recovery-warning.js +19 -0
  57. package/dist/lib/task/review-command.js +3 -2
  58. package/dist/lib/task/review-finalization.js +294 -74
  59. package/dist/lib/{host-control → task}/secure-fs.js +8 -19
  60. package/dist/lib/task/short-id.js +15 -7
  61. package/dist/lib/task/task-execution-lock.js +1 -1
  62. package/dist/lib/task/verification-engine.js +3 -14
  63. package/dist/lib/task/workflow-command.js +2 -1
  64. package/dist/lib/task/workflow-dispatch.js +161 -0
  65. package/dist/lib/version.js +1 -11
  66. package/dist/lifecycle-build-manifest.json +3 -3
  67. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  68. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  69. package/lib/fs/durable-write.ts +1 -0
  70. package/lib/internal/cli-route-inventory.ts +3 -4
  71. package/lib/internal/manual-validation.ts +17 -8
  72. package/lib/internal/platform-comment.ts +53 -2
  73. package/lib/internal/task-artifact.ts +1 -1
  74. package/lib/internal/task-finalization.ts +1 -1
  75. package/lib/internal/task-lifecycle.ts +5 -4
  76. package/lib/internal/task-operation-registry.ts +22 -13
  77. package/lib/internal/task-orchestration.ts +1 -1
  78. package/lib/internal/task-review.ts +11 -7
  79. package/lib/platform/activity-recovery.ts +179 -0
  80. package/lib/platform/issue-comments.ts +105 -30
  81. package/lib/platform/issues.ts +1 -1
  82. package/lib/platform/pr-change-report.ts +7 -5
  83. package/lib/platform/pr-summary.ts +64 -32
  84. package/lib/platform/pull-requests.ts +3 -3
  85. package/lib/platform/task-comment-projection.ts +79 -0
  86. package/lib/platform/task-identities.ts +3 -14
  87. package/lib/platform/task-recovery.ts +54 -0
  88. package/lib/platform/verification-sync.ts +48 -11
  89. package/lib/pr-review/evidence-grading.ts +4 -9
  90. package/lib/sandbox/commands/rm.ts +34 -2
  91. package/lib/sandbox/control/executor.ts +4 -2
  92. package/lib/sandbox/control/protocol.ts +6 -1
  93. package/lib/sandbox/control/server.ts +30 -10
  94. package/lib/sandbox/control/state.ts +9 -1
  95. package/lib/sandbox/control/task-workflow.ts +1 -1
  96. package/lib/sandbox/control/workflow-executor.ts +106 -35
  97. package/lib/sandbox/index.ts +1 -2
  98. package/lib/sandbox/removal.ts +126 -1128
  99. package/lib/sandbox/workspace-identity.ts +19 -3
  100. package/lib/server/process-state.ts +26 -5
  101. package/lib/task/artifact-command.ts +12 -29
  102. package/lib/task/artifact-operations.ts +22 -252
  103. package/lib/task/artifact-recovery.ts +572 -0
  104. package/lib/task/artifact-repair-intent.ts +126 -51
  105. package/lib/task/commit-operation.ts +35 -4
  106. package/lib/task/control-authority.ts +48 -15
  107. package/lib/task/control-recovery.ts +13 -1
  108. package/lib/task/create-service.ts +1 -10
  109. package/lib/task/delegation-receipts.ts +73 -2
  110. package/lib/task/events.ts +188 -24
  111. package/lib/task/human-override.ts +5 -8
  112. package/lib/task/intermediate-cleanup.ts +66 -12
  113. package/lib/task/lifecycle-recovery.ts +691 -0
  114. package/lib/task/lifecycle.ts +13 -4
  115. package/lib/task/local-artifact-finalization.ts +249 -131
  116. package/lib/task/manual-validation-receipt.ts +5 -1
  117. package/lib/task/orchestration.ts +65 -0
  118. package/lib/task/pr-delivery-fact.ts +15 -62
  119. package/lib/task/recovery-warning.ts +21 -0
  120. package/lib/task/review-command.ts +2 -1
  121. package/lib/task/review-finalization.ts +298 -84
  122. package/lib/{host-control → task}/secure-fs.ts +10 -32
  123. package/lib/task/short-id.ts +27 -5
  124. package/lib/task/task-execution-lock.ts +1 -1
  125. package/lib/task/verification-engine.ts +3 -17
  126. package/lib/task/verification-types.ts +0 -2
  127. package/lib/task/workflow-command.ts +2 -1
  128. package/lib/task/workflow-dispatch.ts +171 -0
  129. package/lib/version.ts +1 -15
  130. package/package.json +1 -1
  131. package/templates/.agents/rules/create-issue.en.md +1 -1
  132. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  133. package/templates/.agents/rules/issue-sync.en.md +12 -2
  134. package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
  135. package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
  136. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
  137. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  138. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  139. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  140. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  141. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  142. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  143. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  144. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  145. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  146. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  147. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  148. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
  149. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  150. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  151. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  152. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  153. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  154. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  155. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  157. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  158. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  159. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  160. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  161. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  162. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  163. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  164. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  165. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  166. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  167. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  168. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  169. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  170. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  171. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  172. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  173. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  174. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  175. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  176. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
  177. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
  178. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  179. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  180. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  181. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  182. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  183. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  184. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  185. package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
  186. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
  187. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  188. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  189. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  190. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  191. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  192. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  193. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  194. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  195. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  196. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  197. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  198. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  199. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  200. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  201. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  202. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  203. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  204. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  205. package/templates/.agents/templates/task.en.md +1 -1
  206. package/templates/.agents/templates/task.zh-CN.md +1 -1
  207. package/templates/.claude/commands/import-issue.en.md +1 -1
  208. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  209. package/templates/.claude/commands/restore-task.en.md +1 -1
  210. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  211. package/dist/lib/host-control/audit.js +0 -19
  212. package/dist/lib/host-control/client.js +0 -142
  213. package/dist/lib/host-control/command.js +0 -49
  214. package/dist/lib/host-control/path.js +0 -160
  215. package/dist/lib/host-control/server.js +0 -159
  216. package/dist/lib/host-control/service.js +0 -112
  217. package/dist/lib/internal/host-control.js +0 -28
  218. package/lib/host-control/audit.ts +0 -22
  219. package/lib/host-control/client.ts +0 -172
  220. package/lib/host-control/command.ts +0 -60
  221. package/lib/host-control/path.ts +0 -167
  222. package/lib/host-control/server.ts +0 -180
  223. package/lib/host-control/service.ts +0 -133
  224. package/lib/internal/host-control.ts +0 -28
@@ -14,6 +14,7 @@ import { requestAuditFields, resultAuditFields, appendCriticalAudit, appendDiagn
14
14
  import { parseTaskControlOperation } from '../../task/control-authority.js';
15
15
  import { parseControlOutput, classifySandboxControlRecovery, findSandboxControlRecoveryOperation, operationRecoveryBinding, SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES } from '../../task/control-recovery.js';
16
16
  import { readRun } from '../../task/orchestration.js';
17
+ import { readLifecycleRecoveryDomainEvidence } from '../../task/lifecycle-recovery.js';
17
18
  import { captureRepositorySnapshot } from '../../task/workspace-snapshot.js';
18
19
  import { parseTypedTaskFrontmatter } from '../../task/frontmatter.js';
19
20
  import { readLifecycleJournalEvidence } from '../../task/lifecycle.js';
@@ -256,7 +257,7 @@ function outputMatchesEvidence(output, bytes, sha256) {
256
257
  return Buffer.byteLength(output, 'utf8') === bytes
257
258
  && createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
258
259
  }
259
- function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery') {
260
+ export function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery', warning = null) {
260
261
  const outputUnavailable = request.family === 'task-create' && !payload;
261
262
  return {
262
263
  version: 2,
@@ -264,9 +265,11 @@ function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery')
264
265
  phase: 'completed',
265
266
  exitCode,
266
267
  stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
267
- stderr: payload ? '' : cause === 'publish'
268
- ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
269
- : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
268
+ stderr: payload ? '' : warning
269
+ ? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
270
+ : cause === 'publish'
271
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
272
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
270
273
  error: null,
271
274
  outputState: payload ? 'available' : 'unavailable',
272
275
  payload: payload ? payloadReference(payload) : null
@@ -365,6 +368,23 @@ function readRecoveryDomain(manifest, manifestPath, request, operation, terminal
365
368
  return { domain: null };
366
369
  }
367
370
  if (operation.family === 'task-lifecycle') {
371
+ if (operation.intent === 'recover-started') {
372
+ try {
373
+ if (!('args' in request))
374
+ return { domain: null };
375
+ const parsed = parseTaskControlOperation('task-lifecycle', request.args);
376
+ if (parsed.family !== 'task-lifecycle' || parsed.request.intent !== 'recover-started') {
377
+ return { domain: null };
378
+ }
379
+ return {
380
+ domain: readLifecycleRecoveryDomainEvidence(manifest.repoRoot, parsed.request, terminalResult),
381
+ journal: readLifecycleJournalEvidence(manifest.repoRoot, taskRef)
382
+ };
383
+ }
384
+ catch {
385
+ return { domain: null };
386
+ }
387
+ }
368
388
  if (!taskRef || !terminalResult.targetState)
369
389
  return { domain: null };
370
390
  const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
@@ -441,7 +461,7 @@ function readCommittedCriticalPhases(manifest, requestId) {
441
461
  }
442
462
  });
443
463
  }
444
- function recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult) {
464
+ export function recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult) {
445
465
  const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
446
466
  const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
447
467
  if (!operation)
@@ -503,7 +523,7 @@ function recoveryResponse(manifest, manifestPath, request, evidence, payload, te
503
523
  return null;
504
524
  return finalization.response ?? null;
505
525
  }
506
- return genericRecoveryResponse(request, evidence.exitCode, payload);
526
+ return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
507
527
  }
508
528
  function terminalMatchesEvidence(manifest, manifestPath, request, response, evidence, payload, payloadInvalid, terminalResult) {
509
529
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
@@ -538,7 +558,7 @@ function terminalMatchesEvidence(manifest, manifestPath, request, response, evid
538
558
  if (response.outputState === 'unavailable') {
539
559
  const causes = ['recovery', 'publish'];
540
560
  const valid = causes.some((cause) => JSON.stringify(response)
541
- === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
561
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
542
562
  return { valid, payloadReferenced: false };
543
563
  }
544
564
  return {
@@ -550,7 +570,7 @@ function terminalMatchesEvidence(manifest, manifestPath, request, response, evid
550
570
  }
551
571
  function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
552
572
  const normalized = sanitizeSandboxControlResult(manifest, result);
553
- writeSandboxControlTerminalResult(manifest, {
573
+ const terminalResult = writeSandboxControlTerminalResult(manifest, {
554
574
  id: request.id,
555
575
  family: request.family,
556
576
  operation: operationKey(request, normalized.stdout)
@@ -590,7 +610,7 @@ function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
590
610
  catch {
591
611
  payload = null;
592
612
  }
593
- terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
613
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
594
614
  }
595
615
  }
596
616
  if (!brokerOwns())
@@ -1,5 +1,6 @@
1
1
  import { isCompletionEvidence } from '../../task/orchestration.js';
2
2
  import { parseControlOutput } from '../../task/control-recovery.js';
3
+ import { isRecoveryWarning } from '../../task/recovery-warning.js';
3
4
  import { execFileSync } from 'node:child_process';
4
5
  import fs from 'node:fs';
5
6
  import path from 'node:path';
@@ -83,6 +84,7 @@ export function createSandboxControlTerminalResult(manifest, request, output) {
83
84
  ? nested.run : null;
84
85
  const rawCompletion = nested.completionEvidence ?? run?.completionEvidence;
85
86
  const operation = request.operation ?? (typeof nested.intent === 'string' ? nested.intent : null) ?? '';
87
+ const warning = isRecoveryWarning(nested.warning) ? nested.warning : null;
86
88
  return {
87
89
  version: 1,
88
90
  requestId: request.id,
@@ -92,7 +94,8 @@ export function createSandboxControlTerminalResult(manifest, request, output) {
92
94
  targetState: typeof nested.targetState === 'string' ? nested.targetState : null,
93
95
  status: typeof nested.status === 'string' ? nested.status : 'completed',
94
96
  changed: typeof nested.changed === 'boolean' ? nested.changed : null,
95
- completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null
97
+ completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null,
98
+ ...(warning ? { warning } : {})
96
99
  };
97
100
  }
98
101
  export function writeSandboxControlTerminalResult(manifest, request, output) {
@@ -123,6 +126,9 @@ export function readSandboxControlTerminalResult(filePath) {
123
126
  if (value.completionEvidence !== null && !isCompletionEvidence(value.completionEvidence)) {
124
127
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
125
128
  }
129
+ if (value.warning !== undefined && value.warning !== null && !isRecoveryWarning(value.warning)) {
130
+ throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
131
+ }
126
132
  if (value.changed === undefined) {
127
133
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
128
134
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { readStableFile, SecureFileError, writeAtomicFile } from '../../host-control/secure-fs.js';
4
+ import { readStableFile, SecureFileError, writeAtomicFile } from '../../task/secure-fs.js';
5
5
  import { TASK_WORKFLOW_COMMANDS, TASK_WORKFLOW_OPERATIONS } from '../../task/workflow-command.js';
6
6
  export function validateTaskWorkflowRequest(value) {
7
7
  if (!value || typeof value !== 'object' || Array.isArray(value))
@@ -1,22 +1,46 @@
1
1
  import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.js';
2
2
  import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.js';
3
3
  import { parseReviewCommand } from '../../task/review-command.js';
4
- import { prepareLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
4
+ import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.js';
5
5
  import { prepareReviewSummaryCandidate, commitReviewSummaryProvenance } from '../../task/review-finalization.js';
6
+ import { readArtifactRecoveryIntent } from '../../task/artifact-repair-intent.js';
6
7
  import { withTaskExecutionLock } from '../../task/task-execution-lock.js';
7
- import { dispatchHostControlCommand } from '../../host-control/command.js';
8
+ import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.js';
8
9
  import { assertSandboxTaskSource } from '../workspace-view.js';
9
10
  import { appendDiagnosticAudit } from './audit.js';
10
- import { readTaskArtifact, writeTaskArtifact } from './task-workflow.js';
11
+ import { readTaskArtifact } from './task-workflow.js';
11
12
  import { applyTaskEvent } from '../../task/events.js';
12
13
  import { parseTaskEventRequest } from '../../internal/task-event.js';
13
14
  function executionResult(result) {
14
15
  return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
15
16
  }
16
17
  /** The broker already authorized this host executor; domain operations own validation. */
17
- export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null) {
18
- let publicationStarted = false;
18
+ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAttestation = null, options = {}) {
19
+ let publicationCommitted = false;
20
+ let recoveryObservation = null;
21
+ const observeRecoveryPublication = () => {
22
+ if (!recoveryObservation || recoveryObservation.wasPassed)
23
+ return;
24
+ const intent = readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, recoveryObservation.family, recoveryObservation.artifact);
25
+ publicationCommitted ||= intent?.state === 'preflight-ready'
26
+ || intent?.state === 'preflight-commit-started'
27
+ || intent?.state === 'preflight-passed'
28
+ || intent?.state === 'passed'
29
+ || intent?.state === 'commit-started';
30
+ };
31
+ let faultTriggered = false;
32
+ const fault = (window) => {
33
+ if (faultTriggered || options.faultWindow !== window)
34
+ return;
35
+ faultTriggered = true;
36
+ throw new Error(`TASK_WORKFLOW_FAULT_INJECTED:${window}`);
37
+ };
38
+ const response = (result) => {
39
+ fault('before-result-return');
40
+ return executionResult(result);
41
+ };
19
42
  try {
43
+ fault('before-call');
20
44
  const taskDir = assertSandboxTaskSource(manifest.repoRoot, request.taskId);
21
45
  const [command] = TASK_WORKFLOW_COMMANDS[request.operation];
22
46
  if (request.operation === 'event') {
@@ -24,76 +48,104 @@ export async function executeTaskWorkflow(manifest, request, lifecycleRecoveryAt
24
48
  const boundEventRequest = lifecycleRecoveryAttestation && eventRequest.requestId === undefined
25
49
  ? { ...eventRequest, requestId: lifecycleRecoveryAttestation.lifecycleRequestId }
26
50
  : eventRequest;
51
+ fault('before-domain-write');
27
52
  const result = applyTaskEvent(boundEventRequest, {
28
53
  repoRoot: manifest.repoRoot,
29
54
  lifecycleRecoveryAttestation,
30
55
  deferLifecycleRecoveryConsumption: true
31
56
  });
32
- return executionResult(result);
57
+ publicationCommitted = result.changed;
58
+ fault('after-atomic-rename');
59
+ return response(result);
33
60
  }
34
61
  if (command !== 'task-artifact' && command !== 'task-review') {
35
- publicationStarted = true;
36
- // Inherit this executor's process group so its existing recovery owns the worker too.
37
- const result = await dispatchHostControlCommand({
38
- operation: command, payload: { workingDirectory: manifest.repoRoot, args: request.args }
39
- });
40
- return result;
62
+ // The broker owns this short-lived executor; no global service or worker
63
+ // credential participates in the command's authority or recovery path.
64
+ fault('before-domain-write');
65
+ const result = dispatchWorkflowCommand(manifest.repoRoot, request.operation, request.args);
66
+ publicationCommitted = result.changed === true;
67
+ fault('after-atomic-rename');
68
+ return response(result);
41
69
  }
42
70
  const input = command === 'task-artifact' ? parseArtifactCommand(request.args) : parseReviewCommand(request.args);
43
71
  if (command === 'task-artifact' && 'operation' in input && input.operation === 'inspect') {
44
- return executionResult(executeArtifactCommand(input, { repoRoot: manifest.repoRoot }));
72
+ fault('before-domain-write');
73
+ const result = executeArtifactCommand(input, { repoRoot: manifest.repoRoot });
74
+ publicationCommitted = result.changed === true;
75
+ fault('after-atomic-rename');
76
+ return response(result);
45
77
  }
46
- if ('operation' in input && (input.operation === 'init' || input.operation === 'repair')) {
47
- return executionResult(executeArtifactCommand(input, { repoRoot: manifest.repoRoot, artifactDir: taskDir }));
78
+ if ('operation' in input && input.operation === 'init') {
79
+ fault('before-domain-write');
80
+ const result = executeArtifactCommand(input, { repoRoot: manifest.repoRoot, artifactDir: taskDir });
81
+ publicationCommitted = result.changed === true;
82
+ fault('after-atomic-rename');
83
+ return response(result);
48
84
  }
49
85
  return await withTaskExecutionLock(manifest.repoRoot, request.taskId, `sandbox-control.${request.operation}`, async () => {
50
86
  const artifact = await readTaskArtifact(taskDir, { artifact: input.artifact });
51
87
  const content = artifact.bytes.toString('utf8');
88
+ fault('before-domain-write');
52
89
  let result;
53
90
  if ('operation' in input) {
54
91
  const { family } = input;
55
92
  if (family !== 'analysis' && family !== 'plan' && family !== 'code')
56
93
  throw new Error('ARTIFACT_IDENTITY_INVALID');
57
- const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot };
94
+ const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, recoveryId: input.recoveryId, lockAlreadyHeld: true };
95
+ if (input.operation === 'preflight') {
96
+ // Preflight only seals a verified generation. It must not publish the
97
+ // formal artifact, set publicationStarted, or append finalizer audit.
98
+ const result = preflightLocalArtifact(local);
99
+ recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
100
+ observeRecoveryPublication();
101
+ return response(result);
102
+ }
103
+ recoveryObservation = {
104
+ family,
105
+ artifact: input.artifact,
106
+ wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
107
+ };
58
108
  const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
109
+ observeRecoveryPublication();
110
+ const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
59
111
  if (prepared.result.status === 'failed')
60
- return executionResult(commitLocalArtifactProvenance(prepared));
61
- publicationStarted = true;
62
- await writeTaskArtifact(taskDir, {
63
- artifact: artifact.artifact,
64
- bytes: artifact.bytes,
65
- expectedSha256: artifact.sha256
66
- });
67
- result = commitLocalArtifactProvenance(prepared);
112
+ return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
113
+ result = commitLocalArtifactProvenance(prepared, commitOptions);
68
114
  }
69
115
  else {
70
116
  if (input.overrideTicket)
71
117
  throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
72
- const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot });
118
+ const family = `review-${input.stage}`;
119
+ recoveryObservation = {
120
+ family,
121
+ artifact: input.artifact,
122
+ wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
123
+ };
124
+ const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
125
+ observeRecoveryPublication();
73
126
  if (input.dryRun || prepared.result.status === 'planned')
74
127
  return executionResult(prepared.result);
128
+ const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
75
129
  if (prepared.result.status === 'failed')
76
- return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot));
77
- publicationStarted = true;
78
- await writeTaskArtifact(taskDir, {
79
- artifact: input.artifact,
80
- bytes: Buffer.from(prepared.content, 'utf8'),
81
- expectedSha256: artifact.sha256
82
- });
83
- result = commitReviewSummaryProvenance(prepared, manifest.repoRoot);
130
+ return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions));
131
+ result = commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions);
84
132
  }
133
+ observeRecoveryPublication();
134
+ publicationCommitted ||= result.changed === true;
85
135
  if (result.status !== 'failed')
86
136
  appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
87
137
  requestId: request.id, sandboxTaskId: request.taskId, workflowOperation: request.operation,
88
138
  artifact: input.artifact, sha256: result.artifactSha256, semanticDigest: result.semanticDigest
89
139
  });
90
- return executionResult({ ...result, changed: true });
140
+ fault('after-atomic-rename');
141
+ return response(result);
91
142
  });
92
143
  }
93
144
  catch (error) {
145
+ observeRecoveryPublication();
94
146
  const message = error instanceof Error ? error.message : String(error);
95
147
  const code = error?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
96
- return executionResult({ status: 'failed', changed: publicationStarted ? null : false, error: { code, message } });
148
+ return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
97
149
  }
98
150
  }
99
151
  //# sourceMappingURL=workflow-executor.js.map
@@ -19,8 +19,7 @@ Commands:
19
19
  rm <branch | TASK-id> | --unbound | --purge
20
20
  Remove one sandbox; use a full TASK-id for task-bound
21
21
  cleanup and a branch for branch-only cleanup. --unbound
22
- removes completed task-bound and branch-only sandboxes
23
- plus verified auxiliary state; active, blocked, and archive tasks are protected.
22
+ removes discovered task-bound and branch-only sandboxes.
24
23
  start [--recreate] <branch | TASK-id | N>
25
24
  Verify or recover an existing sandbox container;
26
25
  optionally replace only the container on failure