@fitlab-ai/agent-infra 0.11.9 → 0.11.11

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 (219) hide show
  1. package/dist/bin/internal-cli.js +0 -6
  2. package/dist/lib/agent-clients/adapter.js +20 -0
  3. package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
  4. package/dist/lib/agent-clients/adapters/codex.js +4 -0
  5. package/dist/lib/git/workflow.js +1 -1
  6. package/dist/lib/internal/cli-route-inventory.js +0 -3
  7. package/dist/lib/internal/manual-validation.js +2 -2
  8. package/dist/lib/internal/platform-pr.js +4 -5
  9. package/dist/lib/internal/sandbox-control.js +30 -5
  10. package/dist/lib/internal/task-artifact.js +1 -1
  11. package/dist/lib/internal/task-create.js +15 -1
  12. package/dist/lib/internal/task-finalization.js +14 -25
  13. package/dist/lib/internal/task-operation-registry.js +6 -141
  14. package/dist/lib/internal/task-platform-recovery.js +9 -4
  15. package/dist/lib/platform/completion-backfill.js +2 -1
  16. package/dist/lib/platform/context.js +4 -2
  17. package/dist/lib/platform/github-client.js +20 -17
  18. package/dist/lib/platform/github-provider.js +42 -22
  19. package/dist/lib/platform/github-release-notes.js +4 -2
  20. package/dist/lib/platform/in-label-sync.js +2 -1
  21. package/dist/lib/platform/issue-comments.js +13 -9
  22. package/dist/lib/platform/operation-coordinator.js +1 -1
  23. package/dist/lib/platform/pr-review.js +2 -1
  24. package/dist/lib/platform/pr-summary.js +45 -8
  25. package/dist/lib/platform/provider-bridge.js +4 -5
  26. package/dist/lib/platform/provider-contract.d.ts +3 -6
  27. package/dist/lib/platform/provider-contract.js +3 -15
  28. package/dist/lib/platform/provider-validation.js +9 -4
  29. package/dist/lib/platform/pull-requests.js +83 -16
  30. package/dist/lib/platform/verification-sync.js +17 -14
  31. package/dist/lib/sandbox/commands/create.js +18 -2
  32. package/dist/lib/sandbox/commands/enter.js +47 -33
  33. package/dist/lib/sandbox/control/client.js +99 -17
  34. package/dist/lib/sandbox/control/executor.js +12 -14
  35. package/dist/lib/sandbox/control/host-recovery.js +218 -0
  36. package/dist/lib/sandbox/control/protocol.js +2 -3
  37. package/dist/lib/sandbox/control/server.js +42 -2
  38. package/dist/lib/sandbox/environment.js +150 -0
  39. package/dist/lib/sandbox/recovery.js +8 -0
  40. package/dist/lib/sandbox/removal.js +0 -1
  41. package/dist/lib/sandbox/workspace-identity.js +0 -14
  42. package/dist/lib/sandbox/workspace-view.js +33 -6
  43. package/dist/lib/task/artifact-command.js +18 -3
  44. package/dist/lib/task/artifact-lifecycle.js +121 -63
  45. package/dist/lib/task/artifact-selection.js +1 -1
  46. package/dist/lib/task/capabilities.js +59 -22
  47. package/dist/lib/task/commands/status.js +0 -27
  48. package/dist/lib/task/commit-identity.js +22 -7
  49. package/dist/lib/task/commit-intent.js +67 -26
  50. package/dist/lib/task/commit-operation.js +19 -8
  51. package/dist/lib/task/completion-facts.js +32 -10
  52. package/dist/lib/task/control-authority.js +2 -6
  53. package/dist/lib/task/current-contract.js +1 -5
  54. package/dist/lib/task/events.js +9 -275
  55. package/dist/lib/task/execution-inputs.js +14 -0
  56. package/dist/lib/task/finalization-envelope.js +4 -0
  57. package/dist/lib/task/finalization.js +43 -86
  58. package/dist/lib/task/frontmatter.js +25 -2
  59. package/dist/lib/task/ledger-intents.js +6 -8
  60. package/dist/lib/task/orchestration.js +11 -10
  61. package/dist/lib/task/platform-operation-journal.js +29 -10
  62. package/dist/lib/task/platform-operation-recovery.js +109 -35
  63. package/dist/lib/task/resolve-ref.js +8 -3
  64. package/dist/lib/task/review-fingerprint.js +4 -26
  65. package/dist/lib/task/verification-engine.js +95 -6
  66. package/dist/lib/task/write.js +0 -11
  67. package/dist/lifecycle-build-manifest.json +3 -3
  68. package/lib/agent-clients/adapter.ts +22 -0
  69. package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
  70. package/lib/agent-clients/adapters/codex.ts +4 -0
  71. package/lib/git/workflow.ts +1 -1
  72. package/lib/internal/cli-route-inventory.ts +0 -2
  73. package/lib/internal/manual-validation.ts +2 -2
  74. package/lib/internal/platform-pr.ts +3 -4
  75. package/lib/internal/sandbox-control.ts +39 -6
  76. package/lib/internal/task-artifact.ts +1 -1
  77. package/lib/internal/task-create.ts +15 -2
  78. package/lib/internal/task-finalization.ts +16 -26
  79. package/lib/internal/task-operation-registry.ts +6 -153
  80. package/lib/internal/task-platform-recovery.ts +9 -4
  81. package/lib/platform/completion-backfill.ts +2 -1
  82. package/lib/platform/context.ts +8 -4
  83. package/lib/platform/github-client.ts +24 -20
  84. package/lib/platform/github-provider.ts +47 -26
  85. package/lib/platform/github-release-notes.ts +5 -3
  86. package/lib/platform/in-label-sync.ts +2 -1
  87. package/lib/platform/issue-comments.ts +14 -9
  88. package/lib/platform/operation-coordinator.ts +1 -1
  89. package/lib/platform/pr-review.ts +2 -1
  90. package/lib/platform/pr-summary.ts +43 -7
  91. package/lib/platform/provider-bridge.ts +10 -9
  92. package/lib/platform/provider-contract.ts +6 -21
  93. package/lib/platform/provider-loader.ts +7 -6
  94. package/lib/platform/provider-validation.ts +30 -8
  95. package/lib/platform/pull-requests.ts +78 -15
  96. package/lib/platform/repository-metadata.ts +3 -2
  97. package/lib/platform/security-alerts.ts +1 -1
  98. package/lib/platform/types.ts +2 -0
  99. package/lib/platform/verification-sync.ts +18 -14
  100. package/lib/sandbox/commands/create.ts +24 -2
  101. package/lib/sandbox/commands/enter.ts +42 -39
  102. package/lib/sandbox/control/client.ts +107 -19
  103. package/lib/sandbox/control/executor.ts +13 -15
  104. package/lib/sandbox/control/host-recovery.ts +195 -0
  105. package/lib/sandbox/control/protocol.ts +4 -5
  106. package/lib/sandbox/control/server.ts +42 -1
  107. package/lib/sandbox/environment.ts +162 -0
  108. package/lib/sandbox/recovery.ts +10 -0
  109. package/lib/sandbox/removal.ts +2 -10
  110. package/lib/sandbox/tool-types.ts +1 -0
  111. package/lib/sandbox/workspace-identity.ts +0 -26
  112. package/lib/sandbox/workspace-view.ts +33 -8
  113. package/lib/task/artifact-command.ts +19 -3
  114. package/lib/task/artifact-lifecycle.ts +102 -53
  115. package/lib/task/artifact-selection.ts +1 -1
  116. package/lib/task/capabilities.ts +52 -26
  117. package/lib/task/commands/status.ts +0 -31
  118. package/lib/task/commit-identity.ts +24 -7
  119. package/lib/task/commit-intent.ts +58 -24
  120. package/lib/task/commit-operation.ts +17 -8
  121. package/lib/task/completion-facts.ts +31 -9
  122. package/lib/task/control-authority.ts +1 -8
  123. package/lib/task/current-contract.ts +1 -6
  124. package/lib/task/events.ts +9 -248
  125. package/lib/task/execution-inputs.ts +25 -0
  126. package/lib/task/finalization-envelope.ts +21 -0
  127. package/lib/task/finalization.ts +40 -82
  128. package/lib/task/frontmatter.ts +26 -2
  129. package/lib/task/ledger-intents.ts +6 -8
  130. package/lib/task/orchestration.ts +11 -17
  131. package/lib/task/platform-operation-journal.ts +47 -13
  132. package/lib/task/platform-operation-recovery.ts +102 -37
  133. package/lib/task/resolve-ref.ts +8 -3
  134. package/lib/task/review-fingerprint.ts +4 -26
  135. package/lib/task/verification-engine.ts +103 -9
  136. package/lib/task/write.ts +0 -19
  137. package/package.json +1 -1
  138. package/templates/.agents/rules/README.en.md +1 -1
  139. package/templates/.agents/rules/README.zh-CN.md +1 -1
  140. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  141. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  142. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  143. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  144. package/templates/.agents/rules/issue-sync.en.md +1 -1
  145. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  146. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  147. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  148. package/templates/.agents/rules/review-handshake.en.md +3 -0
  149. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
  150. package/templates/.agents/rules/task-management.en.md +1 -1
  151. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  152. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  153. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  158. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
  159. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
  160. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
  161. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
  162. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  163. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +27 -10
  164. package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
  165. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
  166. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  167. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  168. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
  169. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
  170. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  171. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  172. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  173. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  174. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  175. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  176. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  177. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  178. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  179. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  180. package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
  181. package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
  182. package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
  183. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
  184. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  189. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  190. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  191. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  192. package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
  193. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
  194. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
  195. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
  196. package/templates/.agents/skills/test/SKILL.en.md +4 -1
  197. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
  198. package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
  199. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
  200. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -2
  201. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -2
  202. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
  203. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
  204. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  205. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  206. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  207. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  208. package/templates/.agents/templates/task.zh-CN.md +0 -12
  209. package/templates/.github/workflows/pr-label.yml +1 -2
  210. package/dist/lib/internal/task-invalidation.js +0 -63
  211. package/dist/lib/task/finalization-handoff.js +0 -93
  212. package/dist/lib/task/intermediate-cleanup.js +0 -327
  213. package/dist/lib/task/invalidation-command.js +0 -88
  214. package/dist/lib/task/invalidation.js +0 -242
  215. package/lib/internal/task-invalidation.ts +0 -41
  216. package/lib/task/finalization-handoff.ts +0 -105
  217. package/lib/task/intermediate-cleanup.ts +0 -393
  218. package/lib/task/invalidation-command.ts +0 -88
  219. package/lib/task/invalidation.ts +0 -313
@@ -19,7 +19,7 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
19
19
  import { parseImplementationInputs } from "./implementation-inputs.js";
20
20
  import { scanVisibleMarkdown } from "./markdown.js";
21
21
  import { loadVerificationConfig } from "./verification-config.js";
22
- import { inspectActivityLog } from "./activity-log.js";
22
+ import { inspectActivityLog, pairEntries } from "./activity-log.js";
23
23
  import { parseWorkflowWarnings } from "./workflow-warnings.js";
24
24
  import { OrchestrationStateError, readRun } from "./orchestration.js";
25
25
  import { readPrDeliveryFact } from "./pr-delivery-fact.js";
@@ -30,9 +30,11 @@ import { normalizeVerificationRecord } from './gate-policy.js';
30
30
  import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
31
31
  import { readManualValidationCompletion } from "./manual-validation-completion.js";
32
32
  import { sha256File } from "./artifact-receipts.js";
33
+ import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
34
+ import { parseArtifactName } from "./artifact-name.js";
33
35
  import { summaryCommentState } from "../platform/pr-summary.js";
34
36
  import { taskIssueIdentity } from "../platform/task-identities.js";
35
- import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
37
+ import { hasArtifactCompletionFact, hasArtifactCompletionLog, parseCompletionFacts } from './completion-facts.js';
36
38
  import { inspectReviewIdentity } from './review-identity.js';
37
39
  const TASK_ENUMS = {
38
40
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
@@ -264,7 +266,7 @@ function checkOrchestrationEvidence({ taskDir }) {
264
266
  return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
265
267
  }
266
268
  // === Check Functions ===
267
- function checkTaskMeta({ taskDir, config, repositoryRoot }) {
269
+ function checkTaskMeta({ taskDir, config, repositoryRoot, skillName, artifactFile }) {
268
270
  const task = loadTask(taskDir);
269
271
  if (!task.ok) {
270
272
  return failResult("task-meta", task.message);
@@ -306,7 +308,22 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
306
308
  }
307
309
  const expectedStep = config.expected_step;
308
310
  if (expectedStep && metadata.current_step !== expectedStep) {
309
- return failResult("task-meta", `Expected current_step '${expectedStep}', got '${metadata.current_step || "(empty)"}'`);
311
+ let validLifecycleArtifact = false;
312
+ if (currentTask) {
313
+ const activityConfig = loadVerificationConfig(repositoryRoot, skillName).checks?.["activity-log"];
314
+ const { section, invalidEntries } = inspectActivityLog(task.content);
315
+ if (activityConfig && section && invalidEntries.length === 0) {
316
+ let latestAction = "";
317
+ for (const entry of section.entries) {
318
+ if (!ACTIVITY_LOG_STARTED_RE.test(entry.step))
319
+ latestAction = entry.step;
320
+ }
321
+ validLifecycleArtifact = checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, activityConfig, task, section.entries, latestAction)?.status === "pass";
322
+ }
323
+ }
324
+ if (!validLifecycleArtifact) {
325
+ return failResult("task-meta", `Expected current_step '${expectedStep}', got '${metadata.current_step || "(empty)"}'`);
326
+ }
310
327
  }
311
328
  const expectedStatus = config.expected_status;
312
329
  if (expectedStatus && metadata.status !== expectedStatus) {
@@ -604,7 +621,73 @@ function checkImplementationInput({ taskDir, artifactFile }) {
604
621
  }
605
622
  return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
606
623
  }
607
- function checkActivityLog({ taskDir, config }) {
624
+ const ARTIFACT_ACTIVITY_STAGES = {
625
+ "analyze-task": { family: "analysis", event: "analyze.completed" },
626
+ "plan-task": { family: "plan", event: "plan.completed" },
627
+ "review-analysis": { family: "review-analysis", event: "review-analysis.completed" },
628
+ "review-plan": { family: "review-plan", event: "review-plan.completed" },
629
+ "code-task": { family: "code", event: "code.completed" },
630
+ "review-code": { family: "review-code", event: "review-code.completed" }
631
+ };
632
+ function checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, config, task, entries, latestAction) {
633
+ const stage = ARTIFACT_ACTIVITY_STAGES[skillName];
634
+ if (!stage)
635
+ return null;
636
+ const inventory = inspectArtifactDirectory(taskDir, stage.family);
637
+ if (inventory.status !== "ready" || !inventory.latest) {
638
+ return failResult("activity-log", `Current ${stage.family} artifact is unavailable: ${inventory.error?.message ?? "no artifact exists"}`);
639
+ }
640
+ const output = artifactFile || inventory.latest.name;
641
+ if (output !== inventory.latest.name) {
642
+ return failResult("activity-log", `Artifact '${output}' is not the current ${stage.family} artifact; rerun ${skillName} for '${inventory.latest.name}'`);
643
+ }
644
+ const identity = parseArtifactName(output);
645
+ if (!identity || identity.family !== stage.family) {
646
+ return failResult("activity-log", `Artifact '${output}' does not belong to ${stage.family}`);
647
+ }
648
+ if (!hasArtifactCompletionFact(task.content, path.join(taskDir, output), stage.event)) {
649
+ return failResult("activity-log", `Completion fact for '${output}' is missing or stale; rerun ${skillName} to record a current completion`);
650
+ }
651
+ const frontmatter = parseTypedTaskFrontmatter(task.content);
652
+ const fact = parseCompletionFacts(frontmatter.completion_facts).find((item) => item.event === stage.event && item.output === output);
653
+ if (!fact)
654
+ return failResult("activity-log", `Completion fact for '${output}' is missing; rerun ${skillName}`);
655
+ if (!Array.isArray(fact.lifecycleInputs)) {
656
+ return failResult("activity-log", `Completion fact for '${output}' has no input snapshot; rerun ${skillName}`);
657
+ }
658
+ for (const input of fact.lifecycleInputs) {
659
+ if (!input || typeof input.name !== "string" || !/^[a-f0-9]{64}$/u.test(input.sha256)) {
660
+ return failResult("activity-log", `Completion fact for '${output}' has an invalid input snapshot; rerun ${skillName}`);
661
+ }
662
+ const inputIdentity = parseArtifactName(input.name);
663
+ if (!inputIdentity)
664
+ return failResult("activity-log", `Completion fact for '${output}' has invalid input '${input.name}'; rerun ${skillName}`);
665
+ const inputInventory = inspectArtifactDirectory(taskDir, inputIdentity.family);
666
+ if (inputInventory.status !== "ready" || inputInventory.latest?.name !== input.name
667
+ || sha256File(path.join(taskDir, input.name)) !== input.sha256) {
668
+ return failResult("activity-log", `Input '${input.name}' changed or is no longer current; rerun ${skillName} to refresh '${output}'`);
669
+ }
670
+ }
671
+ const expectedAction = new RegExp(config.expected_action_pattern);
672
+ const roundAction = entries.find((entry) => {
673
+ if (ACTIVITY_LOG_STARTED_RE.test(entry.step))
674
+ return false;
675
+ if (!expectedAction.test(entry.step))
676
+ return false;
677
+ expectedAction.lastIndex = 0;
678
+ const actionRound = /\(Round (\d+)/.exec(entry.step)?.[1];
679
+ return actionRound === String(identity.round) && entry.note.includes(`→ ${output}`);
680
+ });
681
+ if (!roundAction) {
682
+ return failResult("activity-log", `No completed Activity Log row records '${output}' for ${skillName} Round ${identity.round}`);
683
+ }
684
+ if (skillName === "plan-task" && /^(?:Human Decision|Decision)\b/.test(latestAction)
685
+ && expectedAction.test(latestAction)) {
686
+ return passResult("activity-log", `Plan completion '${output}' is current; latest action '${latestAction}' is a human decision`);
687
+ }
688
+ return passResult("activity-log", `Current ${stage.family} completion '${output}' is valid for Round ${identity.round}`);
689
+ }
690
+ function checkActivityLog({ taskDir, config, skillName, artifactFile }) {
608
691
  const task = loadTask(taskDir);
609
692
  if (!task.ok) {
610
693
  return failResult("activity-log", task.message);
@@ -638,6 +721,9 @@ function checkActivityLog({ taskDir, config }) {
638
721
  doneActions.push(action);
639
722
  }
640
723
  }
724
+ const artifactLifecycleResult = checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, config, task, entries, latestAction);
725
+ if (artifactLifecycleResult)
726
+ return artifactLifecycleResult;
641
727
  if (config.expected_action_pattern && !new RegExp(config.expected_action_pattern).test(latestAction)) {
642
728
  const expected = new RegExp(config.expected_action_pattern);
643
729
  let index = doneActions.length - 1;
@@ -799,7 +885,10 @@ async function checkManualValidation({ taskDir, repositoryRoot }) {
799
885
  const inspectedLog = inspectActivityLog(task.content);
800
886
  if (!inspectedLog.section)
801
887
  return failResult("manual-validation", "Activity Log is missing or invalid");
802
- const reviewDoneIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === `Review Code (Round ${review.round})`);
888
+ const reviewRow = pairEntries(inspectedLog.section.entries).find((row) => row.step === `Review Code (Round ${review.round})` && row.started !== '' && row.done !== '');
889
+ const reviewDoneIndex = reviewRow
890
+ ? inspectedLog.section.entries.findIndex((entry) => entry.step === reviewRow.step && entry.time === reviewRow.done)
891
+ : -1;
803
892
  const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
804
893
  && entry.note.includes(`Manual validation passed → ${artifactName};`)
805
894
  && entry.note.includes(`transaction=${receipt.transactionId};`)
@@ -6,7 +6,6 @@ import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
7
  import { mutateTableRow, upsertSection } from './sections.js';
8
8
  import { validateCurrentTaskContract } from './current-contract.js';
9
- import { invalidationBlocks, parseInvalidationDocument } from './invalidation.js';
10
9
  const DEFAULT_FILE_SYSTEM = {
11
10
  readFileSync: (file) => fs.readFileSync(file, 'utf8'),
12
11
  statModeSync: (file) => fs.statSync(file).mode,
@@ -91,16 +90,6 @@ function writeTaskCore(request, options = {}) {
91
90
  if (!contract.ok) {
92
91
  return failure(request, identity, contract.code, contract.message);
93
92
  }
94
- if (invalidationBlocks(contract.invalidation)
95
- && options.invalidationContext !== 'source-completion'
96
- && options.invalidationContext !== 'reconcile') {
97
- return failure(request, identity, 'TASK_INVALIDATION_BLOCKED', 'task has an incomplete artifact invalidation operation; reconcile it before writing downstream state');
98
- }
99
- }
100
- else {
101
- const invalidation = parseInvalidationDocument(original);
102
- if (!invalidation.ok)
103
- return failure(request, identity, 'TASK_INVALIDATION_INVALID', invalidation.message);
104
93
  }
105
94
  let candidate = original;
106
95
  const operations = [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.9",
4
- "sourceInputHash": "17fe6c91408c93e3aa5185e816025d590ded7db7e6a305fdce00751e78723abc",
5
- "internalExecutableBuildHash": "8de9870e4ece1050564d7a3e77ee211a789dd975e3f94593300b7362ed815a00"
3
+ "packageVersion": "0.11.11",
4
+ "sourceInputHash": "576098e3f88ceca11a0a0f94573a3ceca2b14c97b29a6b934ae07849e214c889",
5
+ "internalExecutableBuildHash": "065375c31d3564a65236fc43bad9947395ea0873ae2531d9b05f42956ef30f1e"
6
6
  }
@@ -1,3 +1,4 @@
1
+ import path from 'node:path';
1
2
  import {
2
3
  AGENT_CLIENT_CAPABILITY_IDS,
3
4
  AGENT_CLIENT_SUPPORT_LEVELS,
@@ -216,6 +217,26 @@ function freezeTool(tool: SandboxTool): SandboxTool {
216
217
  if (!tool.containerMount.startsWith('/')) {
217
218
  throw new Error(`Sandbox tool '${tool.id}' containerMount must be absolute`);
218
219
  }
220
+ if (tool.hostStateMounts !== undefined && !Array.isArray(tool.hostStateMounts)) {
221
+ throw new Error(`Agent Client '${tool.id}' hostStateMounts must be an array`);
222
+ }
223
+ const hostStateMounts = tool.hostStateMounts?.map((entry) => {
224
+ if (!entry || typeof entry.hostSubdir !== 'string' || typeof entry.containerSubpath !== 'string') {
225
+ throw new Error(`Agent Client '${tool.id}' hostStateMounts must contain relative path pairs`);
226
+ }
227
+ for (const value of [entry.hostSubdir, entry.containerSubpath]) {
228
+ const normalized = path.posix.normalize(value);
229
+ if (
230
+ value === ''
231
+ || path.posix.isAbsolute(value)
232
+ || normalized === '..'
233
+ || normalized.startsWith('../')
234
+ ) {
235
+ throw new Error(`Agent Client '${tool.id}' hostStateMounts must stay within the tool directories`);
236
+ }
237
+ }
238
+ return Object.freeze({ ...entry });
239
+ });
219
240
  if (!tool.versionCmd) {
220
241
  throw new Error(`Sandbox tool '${tool.id}' requires versionCmd`);
221
242
  }
@@ -233,6 +254,7 @@ function freezeTool(tool: SandboxTool): SandboxTool {
233
254
  ...(tool.hostLiveMounts ? {
234
255
  hostLiveMounts: Object.freeze(tool.hostLiveMounts.map((entry) => Object.freeze({ ...entry })))
235
256
  } : {}),
257
+ ...(hostStateMounts ? { hostStateMounts: Object.freeze(hostStateMounts) } : {}),
236
258
  ...(tool.postSetupCmds ? { postSetupCmds: Object.freeze([...tool.postSetupCmds]) } : {}),
237
259
  ...(tool.tmpfs ? {
238
260
  tmpfs: Object.freeze({
@@ -285,6 +285,19 @@ const codexRecoveryChecks: readonly AgentClientSandboxRecoveryCheck[] = [
285
285
  path: '/home/devuser/.codex'
286
286
  }
287
287
  },
288
+ {
289
+ id: 'app-server-package-writable',
290
+ probe: {
291
+ script: 'probe="$1/.agent-infra-app-server-package-$$"; trap \'rm -f -- "$probe"\' EXIT; : > "$probe"',
292
+ args: ['/home/devuser/.codex/packages/app-server-daemon'],
293
+ user: 'devuser'
294
+ },
295
+ finding: {
296
+ repairKind: 'hard-failure',
297
+ message: 'Codex app-server package directory is not writable by devuser.',
298
+ path: '/home/devuser/.codex/packages/app-server-daemon'
299
+ }
300
+ },
288
301
  {
289
302
  id: 'prompts-link',
290
303
  when: {
@@ -55,6 +55,10 @@ const codexAdapter = defineAgentClientAdapter({
55
55
  versionCmd: 'codex --version',
56
56
  setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
57
57
  tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
58
+ hostStateMounts: [{
59
+ hostSubdir: 'packages/app-server-daemon',
60
+ containerSubpath: 'packages/app-server-daemon'
61
+ }],
58
62
  hostLiveMounts: [
59
63
  {
60
64
  hostPath: hostJoin(home, '.codex', 'auth.json'),
@@ -100,7 +100,7 @@ function commitExplicitPaths(input: { cwd: string; paths: readonly string[]; mes
100
100
  const tree = value(runner, input.cwd, ['write-tree']);
101
101
  if (tree !== input.expectedTree) return { status: 'failed' as const, changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'applied' as const }], error: { code: 'GIT_TREE_MISMATCH', message: `Expected staged tree ${input.expectedTree}, received ${tree ?? 'unavailable'}` } };
102
102
  }
103
- const commit = run(runner, input.cwd, ['commit', '-m', input.message]);
103
+ const commit = run(runner, input.cwd, ['commit', '--cleanup=verbatim', '-m', input.message]);
104
104
  const after = inspectGitWorkflow(input.cwd, runner);
105
105
  if (commit.status !== 0) return { status: 'failed' as const, changed: false, snapshot: after.snapshot, operations: [{ name: 'commit', status: 'failed' as const, message: commit.stderr.trim() }], error: { code: 'GIT_COMMIT_FAILED', message: commit.stderr.trim() } };
106
106
  return { status: 'applied' as const, changed: true, snapshot: after.snapshot, operations: [{ name: 'stage', status: 'applied' as const }, { name: 'commit', status: 'applied' as const }], error: null };
@@ -60,7 +60,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
60
60
  'task-orchestration': ['status', 'progress'],
61
61
  'task-review': ['preflight', 'finalize-summary'],
62
62
  'task-event': ['event'],
63
- 'task-invalidation': ['reconcile'],
64
63
  'task-lifecycle': ['intent'],
65
64
  'task-finalization': ['complete'],
66
65
  'task-platform-recovery': ['inspect', 'recover'],
@@ -98,7 +97,6 @@ export function internalRouteSelector(command: string, args: readonly string[]):
98
97
  if (command === 'task-short-id') return first(args) === 'list' && args.includes('--verify') ? 'list-verify' : first(args);
99
98
  if (command === 'task-orchestration') return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
100
99
  if (command === 'task-review') return args[1] ?? '';
101
- if (command === 'task-invalidation') return args[1] ?? '';
102
100
  if (command === 'task-artifact') return args[1] ?? '';
103
101
  if (command === 'platform-pr-review' && first(args) === 'publish') {
104
102
  const scope = optionValue(args, '--scope') ?? '';
@@ -206,7 +206,7 @@ async function executeManualValidationTransactionLocked(
206
206
  if (transaction.phase === 'prepared') {
207
207
  const staged = await syncPullRequestSummary(taskRef, {
208
208
  cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
209
- manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
209
+ manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
210
210
  });
211
211
  if (!['applied', 'no-op'].includes(staged.status)) transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
212
212
  transitionAndPersist('summary-staged');
@@ -264,7 +264,7 @@ async function executeManualValidationTransactionLocked(
264
264
  try {
265
265
  await syncPullRequestSummary(taskRef, {
266
266
  cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile!), primaryResult: primaryResult as 'pr_created' | 'pr_reused' | 'no_op', strict: true,
267
- manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
267
+ manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
268
268
  });
269
269
  } catch {
270
270
  // Preserve recovery-required when pending compensation cannot be confirmed.
@@ -22,7 +22,7 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
22
22
  agent-infra-internal platform-pr bind <task-ref> --pr <token> --agent <agent> [--dry-run] [--cwd <path>]
23
23
  agent-infra-internal platform-pr skip <task-ref> --agent <agent> [--dry-run] [--cwd <path>]
24
24
  agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
25
- agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
25
+ agent-infra-internal platform-pr sync-in-labels --pr <token> [--dry-run] [--cwd <path>]
26
26
  agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
27
27
  agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
28
28
  agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
@@ -89,10 +89,9 @@ async function platformPr(args: string[] = []): Promise<void> {
89
89
  const values = parsed.values;
90
90
  const unexpected = Object.keys(values).find((name) => !['cwd', 'pr', 'dryRun'].includes(name));
91
91
  if (unexpected) { fail(`sync-in-labels does not accept --${unexpected}`); return; }
92
- const pr = Number(values.pr);
93
- if (!Number.isInteger(pr) || pr <= 0) { fail('sync-in-labels requires a positive --pr'); return; }
92
+ if (typeof values.pr !== 'string' || !values.pr || values.pr.trim() !== values.pr) { fail('sync-in-labels requires a non-empty --pr token'); return; }
94
93
  const cwd = path.resolve(typeof values.cwd === 'string' ? values.cwd : process.cwd());
95
- finish(await syncPlatformPullRequestInLabels(pr, { cwd, dryRun: values.dryRun === true }));
94
+ finish(await syncPlatformPullRequestInLabels(values.pr, { cwd, dryRun: values.dryRun === true }));
96
95
  return;
97
96
  }
98
97
  const taskRef = args[1];
@@ -9,6 +9,7 @@ import { serveSandboxControl } from '../sandbox/control/server.ts';
9
9
  import { runSandboxControlExecutor } from '../sandbox/control/executor.ts';
10
10
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
11
11
  import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.ts';
12
+ import { serializeTaskFinalizationEnvelope, type TaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
12
13
 
13
14
  type FinalizationStatus = 'completed' | 'failed' | 'blocked' | 'unknown';
14
15
 
@@ -16,10 +17,11 @@ function writeFinalizationEnvelope(
16
17
  status: FinalizationStatus,
17
18
  accepted: boolean,
18
19
  error: { code: string; message: string; retryable: boolean },
19
- changed = false,
20
- result: unknown = null
20
+ changed: boolean | null = false,
21
+ result: unknown = null,
22
+ requestId: string | null = null
21
23
  ): void {
22
- process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
24
+ process.stdout.write(serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error }));
23
25
  }
24
26
 
25
27
  function finalizationErrorStatus(error: { retryable: boolean; code: string }): FinalizationStatus {
@@ -43,6 +45,28 @@ function recoveredExitCode(response: Awaited<ReturnType<typeof recoverSandboxCon
43
45
  return response.exitCode ?? 1;
44
46
  }
45
47
 
48
+ function finalizationResponseOutput(response: Awaited<ReturnType<typeof requestSandboxTaskFinalization>>): string {
49
+ try {
50
+ const value = JSON.parse(response.stdout) as Partial<TaskFinalizationEnvelope>;
51
+ if (value.version === 2
52
+ && value.status
53
+ && ['completed', 'failed', 'blocked', 'unknown'].includes(value.status)
54
+ && (typeof value.changed === 'boolean' || value.changed === null)) {
55
+ return serializeTaskFinalizationEnvelope({
56
+ status: value.status,
57
+ changed: value.changed,
58
+ accepted: true,
59
+ requestId: response.id,
60
+ result: value.result ?? null,
61
+ error: value.error ?? null
62
+ });
63
+ }
64
+ } catch {
65
+ // Preserve non-envelope output for diagnostics from the executor.
66
+ }
67
+ return response.stdout;
68
+ }
69
+
46
70
  function sandboxFinalizationClient(args: string[]): void {
47
71
  if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
48
72
  const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
@@ -63,7 +87,8 @@ function sandboxFinalizationClient(args: string[]): void {
63
87
  response = requestSandboxTaskFinalization({ agent });
64
88
  } catch (error) {
65
89
  if (!(error instanceof SandboxControlClientError)) throw error;
66
- writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
90
+ const status = finalizationErrorStatus(error.detail);
91
+ writeFinalizationEnvelope(status, error.accepted, error.detail, status === 'unknown' ? null : false, null, error.requestId);
67
92
  writeClientError(error);
68
93
  process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
69
94
  return;
@@ -74,12 +99,20 @@ function sandboxFinalizationClient(args: string[]): void {
74
99
  message: 'sandbox control rejected the finalization request',
75
100
  retryable: false
76
101
  };
77
- writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
102
+ const status = finalizationErrorStatus(error);
103
+ writeFinalizationEnvelope(
104
+ status,
105
+ error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN',
106
+ error,
107
+ status === 'unknown' ? null : false,
108
+ null,
109
+ response.id
110
+ );
78
111
  process.stderr.write(`${error.message}\n`);
79
112
  process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
80
113
  return;
81
114
  }
82
- process.stdout.write(response.stdout);
115
+ process.stdout.write(finalizationResponseOutput(response));
83
116
  process.stderr.write(response.stderr);
84
117
  process.exitCode = response.exitCode ?? 1;
85
118
  }
@@ -1,7 +1,7 @@
1
1
  import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.ts';
2
2
  import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
3
3
 
4
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
4
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--reason-code <reason>] [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
5
5
 
6
6
 
7
7
  function taskArtifact(args: string[] = []): void {
@@ -7,7 +7,7 @@ import {
7
7
  SandboxControlClientError
8
8
  } from '../sandbox/control/client.ts';
9
9
  import { SANDBOX_CONTROL_MAX_BYTES } from '../sandbox/control/protocol.ts';
10
- import { validateTaskCreateCandidate } from '../task/create.ts';
10
+ import { validateTaskCreateCandidate, type TaskCreateCandidateV1 } from '../task/create.ts';
11
11
  import {
12
12
  createTask,
13
13
  parseTaskCreateResult,
@@ -82,7 +82,20 @@ async function taskCreate(args: string[]): Promise<void> {
82
82
  const stat = fs.lstatSync(inputPath);
83
83
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('TASK_CREATE_INPUT_INVALID: input must be a regular file');
84
84
  if (stat.size > SANDBOX_CONTROL_MAX_BYTES) throw new Error('TASK_CREATE_INPUT_TOO_LARGE: input exceeds the control limit');
85
- const candidate = validateTaskCreateCandidate(JSON.parse(fs.readFileSync(inputPath, 'utf8')));
85
+ const inputContent = fs.readFileSync(inputPath, 'utf8');
86
+ let candidate: TaskCreateCandidateV1;
87
+ try {
88
+ candidate = validateTaskCreateCandidate(JSON.parse(inputContent));
89
+ } catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_INPUT_INVALID';
92
+ output(failed(code, message, false, {
93
+ requestId: null,
94
+ accepted: false,
95
+ recovery: 'new-request-id'
96
+ }));
97
+ return;
98
+ }
86
99
  const environment = resolveSandboxControlTransport();
87
100
  if (environment.kind === 'fail-closed') {
88
101
  output(failed(environment.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID', 'sandbox client control configuration is invalid'));
@@ -3,12 +3,11 @@ import {
3
3
  dispatchTaskControlOperation,
4
4
  parseTaskControlOperation
5
5
  } from '../task/control-authority.ts';
6
- import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.ts';
7
- import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.ts';
6
+ import { applyTaskFinalization } from '../task/finalization.ts';
8
7
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
9
- import { verifyTaskEvent } from '../task/verification.ts';
10
8
  import { resolveSandboxControlTransport } from './task-operation-registry.ts';
11
9
  import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.ts';
10
+ import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.ts';
12
11
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
13
12
 
14
13
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
@@ -17,12 +16,13 @@ type FinalizationError = Readonly<{ code: string; message: string; retryable: bo
17
16
 
18
17
  function envelope(
19
18
  status: 'completed' | 'failed' | 'blocked' | 'unknown',
20
- changed: boolean,
19
+ changed: boolean | null,
21
20
  accepted: boolean,
22
21
  result: Awaited<ReturnType<typeof applyTaskFinalization>> | null,
23
- error: FinalizationError | null
22
+ error: FinalizationError | null,
23
+ requestId: string | null = null
24
24
  ): string {
25
- return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
25
+ return serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error });
26
26
  }
27
27
 
28
28
  function exitCode(status: 'completed' | 'failed' | 'blocked' | 'unknown'): number {
@@ -79,33 +79,23 @@ async function taskFinalization(args: string[] = []): Promise<void> {
79
79
  };
80
80
  const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
81
81
  if (transport.kind === 'sandbox-local') {
82
- const prepared = await prepareTaskFinalization(boundOperation.request, {
83
- repoRoot,
84
- preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
85
- });
86
- if (prepared.status !== 'prepared') {
87
- process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
88
- process.exitCode = exitCode(prepared.status);
89
- return;
90
- }
91
82
  try {
92
- const response = requestSandboxTaskFinalization({
93
- agent: boundOperation.request.agent,
94
- prepareHandoff: (binding) => {
95
- const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
96
- return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
97
- }
98
- });
83
+ const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
99
84
  process.stdout.write(response.stdout);
100
85
  process.stderr.write(response.stderr);
101
86
  process.exitCode = response.exitCode ?? 1;
102
87
  return;
103
88
  } catch (error) {
104
- const detail = error instanceof SandboxControlClientError
105
- ? error.detail
89
+ const clientError = error instanceof SandboxControlClientError ? error : null;
90
+ const detail = clientError
91
+ ? clientError.detail
106
92
  : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
107
- process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
108
- process.exitCode = detail.retryable ? 2 : 1;
93
+ const status = detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
94
+ ? 'unknown'
95
+ : detail.retryable ? 'blocked' : 'failed';
96
+ const changed = status === 'unknown' ? null : false;
97
+ process.stdout.write(envelope(status, changed, clientError?.accepted ?? false, null, detail, clientError?.requestId ?? null));
98
+ process.exitCode = exitCode(status);
109
99
  return;
110
100
  }
111
101
  }