@fitlab-ai/agent-infra 0.9.12 → 0.9.14

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 (402) hide show
  1. package/README.md +11 -11
  2. package/README.zh-CN.md +11 -11
  3. package/bin/cli.ts +26 -9
  4. package/dist/bin/cli.js +173 -157
  5. package/dist/bin/internal-cli.js +66 -40
  6. package/dist/lib/agent-client.js +5 -0
  7. package/dist/lib/agent-clients/next-steps.js +6 -1
  8. package/dist/lib/decide.js +3 -3
  9. package/dist/lib/internal/agent-client.js +5 -2
  10. package/dist/lib/internal/cli-route-inventory.js +152 -0
  11. package/dist/lib/internal/codex-lifecycle.js +9 -6
  12. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  13. package/dist/lib/internal/git-workflow.js +10 -6
  14. package/dist/lib/internal/platform-checks.js +19 -7
  15. package/dist/lib/internal/platform-comment.js +25 -13
  16. package/dist/lib/internal/platform-context.js +6 -3
  17. package/dist/lib/internal/platform-issue.js +20 -13
  18. package/dist/lib/internal/platform-metadata.js +88 -0
  19. package/dist/lib/internal/platform-pr-review.js +26 -13
  20. package/dist/lib/internal/platform-pr.js +84 -31
  21. package/dist/lib/internal/platform-release-notes.js +9 -6
  22. package/dist/lib/internal/platform-security.js +93 -0
  23. package/dist/lib/internal/pr-review-grade.js +25 -12
  24. package/dist/lib/internal/release-workflow.js +23 -9
  25. package/dist/lib/internal/sandbox-control.js +50 -7
  26. package/dist/lib/internal/task-activity.js +16 -7
  27. package/dist/lib/internal/task-artifact.js +235 -24
  28. package/dist/lib/internal/task-context.js +4 -1
  29. package/dist/lib/internal/task-create.js +50 -17
  30. package/dist/lib/internal/task-delivery.js +4 -1
  31. package/dist/lib/internal/task-event.js +10 -4
  32. package/dist/lib/internal/task-finalization.js +6 -3
  33. package/dist/lib/internal/task-invalidation.js +63 -0
  34. package/dist/lib/internal/task-ledger.js +20 -6
  35. package/dist/lib/internal/task-lifecycle.js +6 -3
  36. package/dist/lib/internal/task-operation-registry.js +421 -0
  37. package/dist/lib/internal/task-orchestration.js +12 -0
  38. package/dist/lib/internal/task-override.js +14 -7
  39. package/dist/lib/internal/task-qualification.js +90 -0
  40. package/dist/lib/internal/task-review.js +7 -4
  41. package/dist/lib/internal/task-short-id.js +11 -1
  42. package/dist/lib/internal/task-snapshot.js +4 -1
  43. package/dist/lib/internal/task-validate.js +7 -2
  44. package/dist/lib/internal/task-verify.js +11 -7
  45. package/dist/lib/internal/task-warning.js +11 -4
  46. package/dist/lib/platform/adapters.js +1 -3
  47. package/dist/lib/platform/change-request-git-evidence.js +27 -7
  48. package/dist/lib/platform/comment-safety.js +226 -0
  49. package/dist/lib/platform/completion-backfill.js +6 -5
  50. package/dist/lib/platform/context.js +70 -130
  51. package/dist/lib/platform/github-client.js +1 -1
  52. package/dist/lib/platform/github-provider.js +1130 -0
  53. package/dist/lib/platform/in-label-sync.js +143 -0
  54. package/dist/lib/platform/issue-comments.js +237 -72
  55. package/dist/lib/platform/issues.js +335 -189
  56. package/dist/lib/platform/metadata-labels.js +6 -2
  57. package/dist/lib/platform/none-provider.js +28 -0
  58. package/dist/lib/platform/pr-change-report.js +545 -0
  59. package/dist/lib/platform/pr-checks.js +119 -104
  60. package/dist/lib/platform/pr-review.js +88 -72
  61. package/dist/lib/platform/pr-summary.js +541 -50
  62. package/dist/lib/platform/provider-bridge.js +45 -0
  63. package/dist/lib/platform/provider-contract.d.ts +553 -0
  64. package/dist/lib/platform/provider-contract.js +99 -0
  65. package/dist/lib/platform/provider-loader.js +251 -0
  66. package/dist/lib/platform/provider-validation.js +577 -0
  67. package/dist/lib/platform/pull-request-metadata.js +8 -6
  68. package/dist/lib/platform/pull-requests.js +734 -543
  69. package/dist/lib/platform/release-notes.js +78 -55
  70. package/dist/lib/platform/releases.js +79 -50
  71. package/dist/lib/platform/repository-metadata.js +377 -0
  72. package/dist/lib/platform/resource-identity.d.ts +26 -0
  73. package/dist/lib/platform/resource-identity.js +92 -0
  74. package/dist/lib/platform/security-alerts.js +155 -0
  75. package/dist/lib/platform/task-identities.js +31 -0
  76. package/dist/lib/platform/verification-required.js +11 -12
  77. package/dist/lib/platform/verification-sync.js +129 -248
  78. package/dist/lib/pr-review/evidence-grading.js +19 -6
  79. package/dist/lib/process-data/index.js +7 -0
  80. package/dist/lib/run/index.js +68 -23
  81. package/dist/lib/sandbox/commands/control-status.js +22 -0
  82. package/dist/lib/sandbox/commands/create.js +32 -6
  83. package/dist/lib/sandbox/commands/ls.js +5 -0
  84. package/dist/lib/sandbox/commands/rm.js +827 -120
  85. package/dist/lib/sandbox/commands/show.js +16 -0
  86. package/dist/lib/sandbox/control/client.js +174 -19
  87. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  88. package/dist/lib/sandbox/control/container-identity.js +28 -31
  89. package/dist/lib/sandbox/control/executor.js +151 -16
  90. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  91. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  92. package/dist/lib/sandbox/control/protocol.js +77 -0
  93. package/dist/lib/sandbox/control/server.js +677 -34
  94. package/dist/lib/sandbox/control/state.js +209 -4
  95. package/dist/lib/sandbox/control/task-view.js +183 -0
  96. package/dist/lib/sandbox/engines/authority.js +289 -0
  97. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  98. package/dist/lib/sandbox/index.js +5 -0
  99. package/dist/lib/sandbox/recovery.js +21 -1
  100. package/dist/lib/sandbox/workspace-view.js +2 -1
  101. package/dist/lib/sandbox/worktree-safety.js +4 -1
  102. package/dist/lib/server/index.js +8 -0
  103. package/dist/lib/server/protocol.js +38 -1
  104. package/dist/lib/task/activity-log.js +8 -1
  105. package/dist/lib/task/artifact-lifecycle.js +75 -18
  106. package/dist/lib/task/artifact-operations.js +520 -0
  107. package/dist/lib/task/artifact-repair-intent.js +59 -0
  108. package/dist/lib/task/artifact-schema.js +135 -0
  109. package/dist/lib/task/capabilities.js +310 -0
  110. package/dist/lib/task/commands/status.js +32 -0
  111. package/dist/lib/task/commit-operation.js +12 -1
  112. package/dist/lib/task/control-authority.js +12 -5
  113. package/dist/lib/task/create-service.js +111 -12
  114. package/dist/lib/task/create.js +25 -0
  115. package/dist/lib/task/current-contract.js +5 -1
  116. package/dist/lib/task/decision-details.js +1 -1
  117. package/dist/lib/task/events.js +416 -17
  118. package/dist/lib/task/finalization.js +37 -13
  119. package/dist/lib/task/human-override.js +58 -36
  120. package/dist/lib/task/index.js +11 -3
  121. package/dist/lib/task/invalidation-command.js +86 -0
  122. package/dist/lib/task/invalidation.js +260 -0
  123. package/dist/lib/task/ledger-intents.js +37 -0
  124. package/dist/lib/task/local-artifact-finalization.js +299 -0
  125. package/dist/lib/task/orchestration.js +240 -144
  126. package/dist/lib/task/pr-delivery-fact.js +102 -101
  127. package/dist/lib/task/qualification-audit.js +490 -0
  128. package/dist/lib/task/qualification-confirmation.js +109 -0
  129. package/dist/lib/task/qualification-intents.js +148 -0
  130. package/dist/lib/task/review-artifacts.js +22 -23
  131. package/dist/lib/task/review-finalization.js +121 -4
  132. package/dist/lib/task/review-fingerprint.js +24 -2
  133. package/dist/lib/task/rework-intent.js +138 -0
  134. package/dist/lib/task/sections.js +6 -4
  135. package/dist/lib/task/skill-scope.js +23 -0
  136. package/dist/lib/task/task-execution-lock.js +27 -17
  137. package/dist/lib/task/verification-config.js +11 -0
  138. package/dist/lib/task/verification-engine.js +93 -16
  139. package/dist/lib/task/verification.js +3 -3
  140. package/dist/lib/task/workflow-warning-intents.js +1 -1
  141. package/dist/lib/task/write.js +11 -0
  142. package/dist/lib/version.js +11 -1
  143. package/dist/lifecycle-build-manifest.json +3 -3
  144. package/lib/agent-client.ts +9 -0
  145. package/lib/agent-clients/next-steps.ts +6 -1
  146. package/lib/decide.ts +3 -3
  147. package/lib/init.ts +1 -1
  148. package/lib/internal/agent-client.ts +4 -2
  149. package/lib/internal/cli-route-inventory.ts +140 -0
  150. package/lib/internal/codex-lifecycle.ts +8 -6
  151. package/lib/internal/codex-sandbox-controller.ts +7 -2
  152. package/lib/internal/git-workflow.ts +9 -6
  153. package/lib/internal/platform-checks.ts +14 -6
  154. package/lib/internal/platform-comment.ts +20 -12
  155. package/lib/internal/platform-context.ts +5 -3
  156. package/lib/internal/platform-issue.ts +17 -11
  157. package/lib/internal/platform-metadata.ts +70 -0
  158. package/lib/internal/platform-pr-review.ts +23 -12
  159. package/lib/internal/platform-pr.ts +65 -27
  160. package/lib/internal/platform-release-notes.ts +8 -6
  161. package/lib/internal/platform-security.ts +83 -0
  162. package/lib/internal/pr-review-grade.ts +20 -11
  163. package/lib/internal/release-workflow.ts +21 -9
  164. package/lib/internal/sandbox-control.ts +44 -6
  165. package/lib/internal/task-activity.ts +15 -7
  166. package/lib/internal/task-artifact.ts +204 -22
  167. package/lib/internal/task-context.ts +3 -1
  168. package/lib/internal/task-create.ts +78 -17
  169. package/lib/internal/task-delivery.ts +3 -1
  170. package/lib/internal/task-event.ts +9 -4
  171. package/lib/internal/task-finalization.ts +6 -4
  172. package/lib/internal/task-invalidation.ts +41 -0
  173. package/lib/internal/task-ledger.ts +21 -8
  174. package/lib/internal/task-lifecycle.ts +5 -3
  175. package/lib/internal/task-operation-registry.ts +482 -0
  176. package/lib/internal/task-orchestration.ts +12 -0
  177. package/lib/internal/task-override.ts +15 -9
  178. package/lib/internal/task-qualification.ts +61 -0
  179. package/lib/internal/task-review.ts +6 -4
  180. package/lib/internal/task-short-id.ts +10 -1
  181. package/lib/internal/task-snapshot.ts +3 -1
  182. package/lib/internal/task-validate.ts +6 -2
  183. package/lib/internal/task-verify.ts +10 -7
  184. package/lib/internal/task-warning.ts +10 -4
  185. package/lib/platform/adapters.ts +3 -2
  186. package/lib/platform/change-request-git-evidence.ts +36 -9
  187. package/lib/platform/comment-safety.ts +250 -0
  188. package/lib/platform/completion-backfill.ts +9 -8
  189. package/lib/platform/context.ts +97 -134
  190. package/lib/platform/github-client.ts +2 -2
  191. package/lib/platform/github-provider.ts +1132 -0
  192. package/lib/platform/in-label-sync.ts +208 -0
  193. package/lib/platform/issue-comments.ts +255 -80
  194. package/lib/platform/issues.ts +344 -174
  195. package/lib/platform/metadata-labels.ts +5 -2
  196. package/lib/platform/none-provider.ts +35 -0
  197. package/lib/platform/pr-change-report.ts +639 -0
  198. package/lib/platform/pr-checks.ts +126 -106
  199. package/lib/platform/pr-review.ts +88 -85
  200. package/lib/platform/pr-summary.ts +581 -53
  201. package/lib/platform/provider-bridge.ts +82 -0
  202. package/lib/platform/provider-contract.ts +611 -0
  203. package/lib/platform/provider-loader.ts +307 -0
  204. package/lib/platform/provider-validation.ts +611 -0
  205. package/lib/platform/pull-request-metadata.ts +10 -7
  206. package/lib/platform/pull-requests.ts +781 -550
  207. package/lib/platform/release-notes.ts +76 -59
  208. package/lib/platform/releases.ts +77 -43
  209. package/lib/platform/repository-metadata.ts +453 -0
  210. package/lib/platform/resource-identity.ts +121 -0
  211. package/lib/platform/security-alerts.ts +191 -0
  212. package/lib/platform/task-identities.ts +40 -0
  213. package/lib/platform/types.ts +4 -2
  214. package/lib/platform/verification-required.ts +11 -13
  215. package/lib/platform/verification-sync.ts +128 -304
  216. package/lib/pr-review/evidence-grading.ts +25 -8
  217. package/lib/process-data/index.ts +7 -0
  218. package/lib/run/index.ts +62 -22
  219. package/lib/sandbox/commands/control-status.ts +26 -0
  220. package/lib/sandbox/commands/create.ts +46 -6
  221. package/lib/sandbox/commands/ls.ts +7 -0
  222. package/lib/sandbox/commands/rm.ts +1000 -87
  223. package/lib/sandbox/commands/show.ts +16 -0
  224. package/lib/sandbox/control/client.ts +203 -19
  225. package/lib/sandbox/control/completed-reentry.ts +98 -0
  226. package/lib/sandbox/control/container-identity.ts +27 -43
  227. package/lib/sandbox/control/executor.ts +180 -15
  228. package/lib/sandbox/control/lifecycle.ts +629 -17
  229. package/lib/sandbox/control/native-file-lock.ts +209 -0
  230. package/lib/sandbox/control/protocol.ts +118 -2
  231. package/lib/sandbox/control/server.ts +724 -34
  232. package/lib/sandbox/control/state.ts +257 -4
  233. package/lib/sandbox/control/task-view.ts +240 -0
  234. package/lib/sandbox/engines/authority.ts +365 -0
  235. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  236. package/lib/sandbox/index.ts +7 -0
  237. package/lib/sandbox/recovery.ts +21 -1
  238. package/lib/sandbox/workspace-view.ts +9 -3
  239. package/lib/sandbox/worktree-safety.ts +4 -1
  240. package/lib/server/index.ts +10 -0
  241. package/lib/server/protocol.ts +36 -1
  242. package/lib/task/activity-log.ts +8 -0
  243. package/lib/task/artifact-lifecycle.ts +70 -22
  244. package/lib/task/artifact-operations.ts +626 -0
  245. package/lib/task/artifact-repair-intent.ts +71 -0
  246. package/lib/task/artifact-schema.ts +192 -0
  247. package/lib/task/capabilities.ts +331 -0
  248. package/lib/task/commands/status.ts +38 -0
  249. package/lib/task/commit-operation.ts +12 -1
  250. package/lib/task/control-authority.ts +22 -9
  251. package/lib/task/create-service.ts +139 -14
  252. package/lib/task/create.ts +27 -0
  253. package/lib/task/current-contract.ts +6 -1
  254. package/lib/task/decision-details.ts +6 -2
  255. package/lib/task/events.ts +392 -16
  256. package/lib/task/finalization.ts +50 -14
  257. package/lib/task/human-override.ts +70 -46
  258. package/lib/task/index.ts +16 -3
  259. package/lib/task/invalidation-command.ts +86 -0
  260. package/lib/task/invalidation.ts +332 -0
  261. package/lib/task/ledger-intents.ts +35 -1
  262. package/lib/task/local-artifact-finalization.ts +432 -0
  263. package/lib/task/orchestration.ts +223 -133
  264. package/lib/task/pr-delivery-fact.ts +131 -148
  265. package/lib/task/qualification-audit.ts +599 -0
  266. package/lib/task/qualification-confirmation.ts +117 -0
  267. package/lib/task/qualification-intents.ts +150 -0
  268. package/lib/task/review-artifacts.ts +21 -22
  269. package/lib/task/review-finalization.ts +133 -4
  270. package/lib/task/review-fingerprint.ts +23 -2
  271. package/lib/task/rework-intent.ts +165 -0
  272. package/lib/task/sections.ts +6 -4
  273. package/lib/task/skill-scope.ts +24 -0
  274. package/lib/task/task-execution-lock.ts +23 -14
  275. package/lib/task/verification-config.ts +12 -0
  276. package/lib/task/verification-engine.ts +103 -17
  277. package/lib/task/verification.ts +4 -4
  278. package/lib/task/workflow-warning-intents.ts +1 -1
  279. package/lib/task/write.ts +19 -0
  280. package/lib/update.ts +1 -1
  281. package/lib/version.ts +15 -1
  282. package/package.json +12 -1
  283. package/templates/.agents/rules/README.en.md +3 -1
  284. package/templates/.agents/rules/README.zh-CN.md +3 -1
  285. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  286. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  287. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  288. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  289. package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
  290. package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
  291. package/templates/.agents/rules/issue-sync.en.md +2 -0
  292. package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
  293. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  294. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  295. package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
  296. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
  297. package/templates/.agents/rules/pr-sync.en.md +10 -4
  298. package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
  299. package/templates/.agents/rules/security-alerts.en.md +8 -8
  300. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  301. package/templates/.agents/rules/task-short-id.en.md +1 -4
  302. package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
  303. package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
  304. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
  305. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  306. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  307. package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
  308. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
  309. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  310. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  311. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  312. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  313. package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
  314. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
  315. package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
  316. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
  317. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  318. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  319. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  320. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  321. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  322. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  323. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  324. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  325. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
  326. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
  327. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
  328. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
  329. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
  330. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
  331. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  332. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  333. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
  334. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
  335. package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
  336. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
  337. package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
  338. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
  339. package/templates/.agents/skills/create-pr/config/verify.json +1 -0
  340. package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
  341. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
  342. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
  343. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
  344. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  345. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  346. package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
  347. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  348. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  349. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  350. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  351. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  352. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  353. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  354. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  355. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  356. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  357. package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
  358. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
  359. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  360. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  361. package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
  362. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
  363. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  364. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  365. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  366. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  367. package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
  368. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
  369. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  370. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  371. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  372. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  373. package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
  374. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
  375. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  376. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  377. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  378. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  379. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  380. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  381. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  382. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  383. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
  384. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
  385. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  386. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  387. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  388. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  389. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  390. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  391. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  392. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
  393. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
  394. package/templates/.agents/templates/task.en.md +24 -1
  395. package/templates/.agents/templates/task.zh-CN.md +24 -1
  396. package/templates/.github/workflows/pr-label.yml +7 -26
  397. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  398. package/templates/.agents/scripts/security-alerts.sh +0 -31
  399. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  400. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  401. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  402. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,11 +1,14 @@
1
+ import { completedReentryView } from './completed-reentry.ts';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
+ import { createHash, randomUUID } from 'node:crypto';
4
5
  import { getProcessStartTime } from '../../server/process-state.ts';
5
6
  import { assertGitWorktreeBinding } from '../../git/worktree-identity.ts';
6
7
  import {
7
8
  controlError,
8
9
  DEFAULT_SANDBOX_CONTROL_TIMING,
10
+ SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
11
+ SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
9
12
  type SandboxControlManifest,
10
13
  type SandboxControlRequest,
11
14
  type SandboxControlResponse,
@@ -14,13 +17,26 @@ import {
14
17
  import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
15
18
  import {
16
19
  appendSandboxControlAudit,
17
- atomicWriteJson,
20
+ atomicWriteJsonNoReplace,
21
+ createSandboxControlPayload,
18
22
  cleanupStaleSandboxControlLease,
19
23
  executionPath,
24
+ readJsonFile,
20
25
  readActiveLease,
21
26
  readExecution,
27
+ readSandboxControlPayload,
28
+ readSandboxControlResultEvidence,
29
+ sandboxControlEncodedJsonBytes,
30
+ sandboxControlGenerationUsage,
31
+ sanitizeSandboxControlOutput,
32
+ sanitizeSandboxControlResult,
33
+ payloadPath,
34
+ writeSandboxControlPayload,
35
+ writeSandboxControlReservation,
36
+ writeSandboxControlResultEvidence,
22
37
  terminateSandboxControlExecution,
23
- writeSandboxControlStatus
38
+ writeSandboxControlStatus,
39
+ readSandboxControlStatus
24
40
  } from './state.ts';
25
41
  import { validateSandboxControlRequest } from './protocol.ts';
26
42
  import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
@@ -32,15 +48,86 @@ import {
32
48
  } from './lifecycle.ts';
33
49
  import type { BrokerOwner } from './lifecycle.ts';
34
50
  import { nextSandboxControlBackoff } from './timing.ts';
51
+ import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
52
+ import { resolveTaskRef } from '../../task/resolve-ref.ts';
53
+ import {
54
+ mergeSandboxTaskView,
55
+ taskViewAfterFinalization,
56
+ taskViewForManifest,
57
+ type SandboxTaskView
58
+ } from './task-view.ts';
59
+ import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
35
60
 
36
61
  type ActiveExecution = {
37
62
  request: SandboxControlRequest;
38
63
  prepared: PreparedSandboxControlExecution;
39
64
  result: SandboxControlExecutionResult | null;
65
+ resultEvidenceWritten: boolean;
40
66
  failure: unknown;
41
67
  settled: boolean;
42
68
  };
43
69
 
70
+ function safeRealpath(value: string): string | null {
71
+ try {
72
+ return fs.realpathSync.native(value);
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ function appendBrokerAudit(
79
+ manifest: SandboxControlManifest,
80
+ event: string,
81
+ fields: Record<string, string | number | boolean | null> = {}
82
+ ): void {
83
+ try {
84
+ appendSandboxControlAudit(manifest, event, { source: 'broker', ...fields });
85
+ } catch {
86
+ // Diagnostics must never change the control protocol.
87
+ }
88
+ }
89
+
90
+ function requestAuditFields(
91
+ manifest: SandboxControlManifest,
92
+ manifestPath: string,
93
+ request: SandboxControlRequest
94
+ ): Record<string, string | number | boolean | null> {
95
+ const args = 'args' in request ? request.args : [];
96
+ const encodedArgs = JSON.stringify(args);
97
+ return {
98
+ requestId: request.id,
99
+ requestFamily: request.family,
100
+ sandboxTaskId: manifest.taskId,
101
+ requestGeneration: request.generation,
102
+ requestIssuedAt: request.issuedAt,
103
+ requestExpiresAt: request.expiresAt,
104
+ requestArgCount: args.length,
105
+ requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
106
+ requestTaskRef: args[0] ?? null,
107
+ requestCommand: args[1] ?? null,
108
+ controllerProofPresent: request.controllerProof !== null,
109
+ hostCwd: process.cwd(),
110
+ manifestPath,
111
+ manifestPathRealpath: safeRealpath(manifestPath),
112
+ repoRoot: manifest.repoRoot,
113
+ repoRootRealpath: safeRealpath(manifest.repoRoot),
114
+ worktreeRoot: manifest.worktreeRoot,
115
+ worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
116
+ runtimeDir: manifest.runtimeDir,
117
+ runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
118
+ };
119
+ }
120
+
121
+ function resultAuditFields(result: SandboxControlExecutionResult): Record<string, string | number | boolean | null> {
122
+ return {
123
+ exitCode: result.exitCode,
124
+ stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
125
+ stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
126
+ stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
127
+ stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
128
+ };
129
+ }
130
+
44
131
  function assertRealDirectory(directory: string, parent?: string): void {
45
132
  const stat = fs.lstatSync(directory);
46
133
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
@@ -53,8 +140,302 @@ function responsePath(manifest: SandboxControlManifest, id: string): string {
53
140
  return path.join(manifest.channelDir, 'responses', `${id}.json`);
54
141
  }
55
142
 
56
- function writeResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
57
- atomicWriteJson(responsePath(manifest, response.id), response);
143
+ function acceptedResponsePath(manifest: SandboxControlManifest, id: string): string {
144
+ return path.join(manifest.channelDir, 'responses', `${id}.accepted.json`);
145
+ }
146
+
147
+ export function writeSandboxControlResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): boolean {
148
+ const filePath = responsePath(manifest, response.id);
149
+ const normalized = {
150
+ ...response,
151
+ stdout: sanitizeSandboxControlOutput(manifest, response.stdout),
152
+ stderr: sanitizeSandboxControlOutput(manifest, response.stderr)
153
+ } satisfies SandboxControlResponse;
154
+ const terminal = sandboxControlEncodedJsonBytes(normalized) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES
155
+ ? normalized
156
+ : {
157
+ ...normalized,
158
+ stdout: '',
159
+ stderr: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: terminal output exceeded the compact record limit\n',
160
+ error: {
161
+ code: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE',
162
+ message: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: terminal output exceeded the compact record limit',
163
+ retryable: false
164
+ }
165
+ } satisfies SandboxControlResponse;
166
+ try {
167
+ atomicWriteJsonNoReplace(filePath, terminal);
168
+ } catch (error) {
169
+ if (!(error instanceof Error) || error.message !== 'SANDBOX_CONTROL_TERMINAL_ALREADY_EXISTS') throw error;
170
+ let persisted: unknown;
171
+ try {
172
+ persisted = readJsonFile(filePath);
173
+ } catch {
174
+ throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
175
+ }
176
+ if (JSON.stringify(persisted) !== JSON.stringify(terminal)) {
177
+ throw new Error('SANDBOX_CONTROL_TERMINAL_CONFLICT');
178
+ }
179
+ return true;
180
+ }
181
+ let persisted: unknown;
182
+ try {
183
+ persisted = readJsonFile(filePath);
184
+ } catch {
185
+ throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
186
+ }
187
+ if (JSON.stringify(persisted) !== JSON.stringify(terminal)) {
188
+ throw new Error('SANDBOX_CONTROL_TERMINAL_READBACK_FAILED');
189
+ }
190
+ return true;
191
+ }
192
+
193
+ type FinalizationRecovery = Readonly<{
194
+ status: 'matched' | 'deferred' | 'not-applicable';
195
+ response?: SandboxControlResponse;
196
+ }>;
197
+
198
+ function finalizationRecoveryResponse(
199
+ manifest: SandboxControlManifest,
200
+ requestId: string,
201
+ exitCode: number
202
+ ): FinalizationRecovery {
203
+ if (!manifest.taskId || exitCode !== 0) return { status: 'not-applicable' };
204
+ let receipt;
205
+ try {
206
+ receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
207
+ if (!receipt || !receipt.controlBinding
208
+ || receipt.controlBinding.generation !== manifest.generation
209
+ || receipt.controlBinding.requestId !== requestId) return { status: 'deferred' };
210
+ const resolved = resolveTaskRef(manifest.taskId, { repoRoot: manifest.repoRoot });
211
+ if (!resolved.ok || resolved.state !== 'completed' || receipt.lifecycle !== 'done') return { status: 'deferred' };
212
+ } catch {
213
+ return { status: 'deferred' };
214
+ }
215
+ const pendingSteps = [
216
+ receipt.taskComment === 'pending' ? 'task-comment' : null,
217
+ receipt.verification === 'pending' ? 'verification' : null
218
+ ].filter((step): step is string => step !== null);
219
+ const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
220
+ .filter((step): step is string => step !== null);
221
+ const warnings = receipt.warnings
222
+ .filter((warning) => warning.status === 'open')
223
+ .map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
224
+ const result = {
225
+ status: 'completed', changed: false, taskId: manifest.taskId,
226
+ lifecycle: { status: 'no-op', changed: false, error: null },
227
+ taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
228
+ verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
229
+ completedSteps, pendingSteps,
230
+ result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
231
+ ? 'completed_with_warnings' : 'completed',
232
+ warnings, error: null
233
+ };
234
+ return {
235
+ status: 'matched',
236
+ response: {
237
+ version: 2, id: requestId, phase: 'completed', exitCode: 0,
238
+ stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
239
+ stderr: '', error: null
240
+ }
241
+ };
242
+ }
243
+
244
+ function publishFinalizationTaskView(
245
+ manifest: SandboxControlManifest,
246
+ broker: BrokerOwner,
247
+ requestId: string,
248
+ state: 'starting' | 'healthy' | 'busy' | 'parked',
249
+ reasonCode: string | null,
250
+ activeRequestId: string | null
251
+ ): SandboxTaskView {
252
+ let view: SandboxTaskView;
253
+ try {
254
+ const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
255
+ const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
256
+ view = resolved.ok && resolved.state === 'completed'
257
+ ? taskViewAfterFinalization({
258
+ taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
259
+ })
260
+ : {
261
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
262
+ reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
263
+ };
264
+ } catch {
265
+ view = {
266
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
267
+ reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
268
+ };
269
+ }
270
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
271
+ return view;
272
+ }
273
+
274
+ function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
275
+ return {
276
+ version: payload.version,
277
+ id: payload.id,
278
+ generation: payload.generation,
279
+ stdoutBytes: payload.stdoutBytes,
280
+ stderrBytes: payload.stderrBytes,
281
+ stdoutSha256: payload.stdoutSha256,
282
+ stderrSha256: payload.stderrSha256
283
+ };
284
+ }
285
+
286
+ function payloadMatchesEvidence(
287
+ payload: ReturnType<typeof readSandboxControlPayload>,
288
+ evidence: ReturnType<typeof readSandboxControlResultEvidence>
289
+ ): boolean {
290
+ return payload.id === evidence.id
291
+ && payload.generation === evidence.generation
292
+ && payload.stdoutBytes === evidence.stdoutBytes
293
+ && payload.stderrBytes === evidence.stderrBytes
294
+ && payload.stdoutSha256 === evidence.stdoutSha256
295
+ && payload.stderrSha256 === evidence.stderrSha256;
296
+ }
297
+
298
+ function outputMatchesEvidence(output: string, bytes: number, sha256: string): boolean {
299
+ return Buffer.byteLength(output, 'utf8') === bytes
300
+ && createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
301
+ }
302
+
303
+ function genericRecoveryResponse(
304
+ request: SandboxControlRequest,
305
+ exitCode: number,
306
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
307
+ cause: 'publish' | 'recovery' = 'recovery'
308
+ ): SandboxControlResponse {
309
+ const outputUnavailable = request.family === 'task-create' && !payload;
310
+ return {
311
+ version: 2,
312
+ id: request.id,
313
+ phase: 'completed',
314
+ exitCode,
315
+ stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
316
+ stderr: payload ? '' : cause === 'publish'
317
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
318
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
319
+ error: null,
320
+ outputState: payload ? 'available' : 'unavailable',
321
+ payload: payload ? payloadReference(payload) : null
322
+ };
323
+ }
324
+
325
+ function recoveryResponse(
326
+ manifest: SandboxControlManifest,
327
+ request: SandboxControlRequest,
328
+ evidence: ReturnType<typeof readSandboxControlResultEvidence>,
329
+ payload: ReturnType<typeof readSandboxControlPayload> | null
330
+ ): SandboxControlResponse | null {
331
+ if (request.family === 'task-finalization' && evidence.exitCode === 0) {
332
+ const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
333
+ return finalization.status === 'matched' ? finalization.response ?? null : null;
334
+ }
335
+ return genericRecoveryResponse(request, evidence.exitCode, payload);
336
+ }
337
+
338
+ function terminalMatchesEvidence(
339
+ manifest: SandboxControlManifest,
340
+ request: SandboxControlRequest,
341
+ response: SandboxControlResponse,
342
+ evidence: ReturnType<typeof readSandboxControlResultEvidence>,
343
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
344
+ payloadInvalid: boolean
345
+ ): { valid: boolean; payloadReferenced: boolean } {
346
+ if (request.family === 'task-finalization' && evidence.exitCode === 0) {
347
+ const expected = recoveryResponse(manifest, request, evidence, payload);
348
+ return {
349
+ valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
350
+ payloadReferenced: false
351
+ };
352
+ }
353
+ if (response.version !== 2 || response.id !== request.id || response.phase !== 'completed'
354
+ || response.exitCode !== evidence.exitCode || response.error !== null) {
355
+ return { valid: false, payloadReferenced: false };
356
+ }
357
+ if (response.outputState === 'available') {
358
+ const reference = response.payload;
359
+ return {
360
+ valid: !payloadInvalid && payload !== null && response.stdout === '' && response.stderr === ''
361
+ && reference !== undefined && reference !== null
362
+ && reference.version === payload.version
363
+ && reference.id === payload.id
364
+ && reference.generation === payload.generation
365
+ && reference.stdoutBytes === payload.stdoutBytes
366
+ && reference.stderrBytes === payload.stderrBytes
367
+ && reference.stdoutSha256 === payload.stdoutSha256
368
+ && reference.stderrSha256 === payload.stderrSha256
369
+ && payloadMatchesEvidence(payload, evidence),
370
+ payloadReferenced: true
371
+ };
372
+ }
373
+ if (response.outputState === 'unavailable') {
374
+ const causes = ['recovery', 'publish'] as const;
375
+ const valid = causes.some((cause) => JSON.stringify(response)
376
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
377
+ return { valid, payloadReferenced: false };
378
+ }
379
+ return {
380
+ valid: response.payload === undefined
381
+ && outputMatchesEvidence(response.stdout, evidence.stdoutBytes, evidence.stdoutSha256)
382
+ && outputMatchesEvidence(response.stderr, evidence.stderrBytes, evidence.stderrSha256),
383
+ payloadReferenced: false
384
+ };
385
+ }
386
+
387
+ function publishExecutionResult(
388
+ manifest: SandboxControlManifest,
389
+ request: SandboxControlRequest,
390
+ result: SandboxControlExecutionResult,
391
+ broker: BrokerOwner,
392
+ brokerOwns: () => boolean
393
+ ): boolean {
394
+ const normalized = sanitizeSandboxControlResult(manifest, result);
395
+ let terminal: SandboxControlResponse | null = null;
396
+ if (request.family === 'task-finalization') {
397
+ const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
398
+ if (finalization.status === 'deferred') return false;
399
+ terminal = finalization.response ?? null;
400
+ }
401
+ if (!terminal) {
402
+ const inline: SandboxControlResponse = {
403
+ version: 2, id: request.id, phase: 'completed', exitCode: normalized.exitCode,
404
+ stdout: normalized.stdout, stderr: normalized.stderr, error: null
405
+ };
406
+ if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
407
+ terminal = inline;
408
+ }
409
+ let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
410
+ if (!terminal) {
411
+ try {
412
+ payload = createSandboxControlPayload(manifest, request.id, normalized);
413
+ const usage = sandboxControlGenerationUsage(manifest);
414
+ if (usage.bytes + sandboxControlEncodedJsonBytes(payload) <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
415
+ writeSandboxControlPayload(manifest, request.id, normalized);
416
+ } else {
417
+ payload = null;
418
+ }
419
+ } catch {
420
+ payload = null;
421
+ }
422
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
423
+ }
424
+ }
425
+ if (!brokerOwns()) return false;
426
+ if (request.family === 'task-finalization' && normalized.exitCode === 0) {
427
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
428
+ if (view.state === 'unknown') return false;
429
+ }
430
+ return writeSandboxControlResponse(manifest, terminal);
431
+ }
432
+
433
+ function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
434
+ atomicWriteJsonNoReplace(acceptedResponsePath(manifest, response.id), response);
435
+ }
436
+
437
+ function removeAcceptedResponse(manifest: SandboxControlManifest, id: string): void {
438
+ fs.rmSync(acceptedResponsePath(manifest, id), { force: true });
58
439
  }
59
440
 
60
441
  function rejected(id: string, error: unknown): SandboxControlResponse {
@@ -111,14 +492,52 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
111
492
  }
112
493
  }
113
494
 
114
- function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => boolean): boolean {
495
+ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
115
496
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
116
497
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
117
498
  if (!brokerOwns()) return false;
118
499
  const descriptor = executionPath(manifest, entry.name);
119
500
  const existingResponse = responsePath(manifest, entry.name);
501
+ const processingDirectory = path.join(manifest.processingDir, entry.name);
502
+ let payloadReferenced = false;
120
503
  if (fs.existsSync(descriptor)) {
121
504
  const execution = readExecution(descriptor);
505
+ let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
506
+ let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
507
+ let payloadInvalid = false;
508
+ let request: SandboxControlRequest | null = null;
509
+ let existingTerminalResponse: SandboxControlResponse | null = null;
510
+ try {
511
+ const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
512
+ request = validateSandboxControlRequest(rawRequest, manifest, {
513
+ now: typeof rawRequest.issuedAt === 'number' ? rawRequest.issuedAt : undefined
514
+ });
515
+ } catch {
516
+ // Missing or malformed request evidence remains fail-closed below.
517
+ }
518
+ const resultPath = path.join(manifest.processingDir, entry.name, 'result.json');
519
+ if (fs.existsSync(resultPath)) {
520
+ try {
521
+ resultEvidence = readSandboxControlResultEvidence(resultPath);
522
+ if (resultEvidence.id !== entry.name || resultEvidence.generation !== manifest.generation) {
523
+ resultEvidence = null;
524
+ }
525
+ } catch {
526
+ resultEvidence = null;
527
+ }
528
+ }
529
+ const publishedPayloadPath = payloadPath(manifest, entry.name);
530
+ if (fs.existsSync(publishedPayloadPath)) {
531
+ try {
532
+ payload = readSandboxControlPayload(publishedPayloadPath);
533
+ if (payload.id !== entry.name || payload.generation !== manifest.generation
534
+ || (resultEvidence !== null && !payloadMatchesEvidence(payload, resultEvidence))) {
535
+ payloadInvalid = true;
536
+ }
537
+ } catch {
538
+ payloadInvalid = true;
539
+ }
540
+ }
122
541
  if (!terminateSandboxControlExecution(execution)) {
123
542
  throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
124
543
  }
@@ -128,22 +547,58 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
128
547
  if (fs.existsSync(existingResponse)) {
129
548
  try {
130
549
  const response = JSON.parse(fs.readFileSync(existingResponse, 'utf8')) as SandboxControlResponse;
550
+ existingTerminalResponse = response;
131
551
  terminal = response.version === 2 && response.id === entry.name
132
552
  && (response.phase === 'completed' || response.phase === 'rejected');
553
+ if (terminal && response.outputState === 'available') {
554
+ if (!response.payload || payloadInvalid || !payload
555
+ || response.payload.version !== payload.version
556
+ || response.payload.id !== payload.id
557
+ || response.payload.generation !== payload.generation
558
+ || response.payload.stdoutBytes !== payload.stdoutBytes
559
+ || response.payload.stderrBytes !== payload.stderrBytes
560
+ || response.payload.stdoutSha256 !== payload.stdoutSha256
561
+ || response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
562
+ payloadReferenced = true;
563
+ } else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
564
+ || response.payload !== undefined && response.payload !== null)) {
565
+ throw new Error('payload state invalid');
566
+ }
567
+ if (!terminal) throw new Error('SANDBOX_CONTROL_RESPONSE_LAYOUT_INVALID');
133
568
  } catch {
134
- terminal = false;
569
+ throw new Error('SANDBOX_CONTROL_RESPONSE_LAYOUT_INVALID');
570
+ }
571
+ }
572
+ if (terminal && (!request || !resultEvidence)) continue;
573
+ if (terminal && request && resultEvidence) {
574
+ const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
575
+ if (!reconciliation.valid) continue;
576
+ payloadReferenced = reconciliation.payloadReferenced;
577
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
578
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
579
+ if (view.state === 'unknown') continue;
135
580
  }
136
581
  }
137
582
  if (!terminal) {
138
583
  if (!brokerOwns()) return false;
139
- writeResponse(manifest, unknown(entry.name));
584
+ if (!resultEvidence || !request || payloadInvalid) continue;
585
+ const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
586
+ if (!recovered) continue;
587
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
588
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
589
+ if (view.state === 'unknown') continue;
590
+ }
591
+ writeSandboxControlResponse(manifest, recovered);
592
+ payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
140
593
  }
141
594
  } else {
142
595
  if (!brokerOwns()) return false;
143
- writeResponse(manifest, notExecuted(entry.name));
596
+ writeSandboxControlResponse(manifest, notExecuted(entry.name));
144
597
  }
145
598
  if (!brokerOwns()) return false;
146
- fs.rmSync(path.join(manifest.processingDir, entry.name), { recursive: true, force: true });
599
+ removeAcceptedResponse(manifest, entry.name);
600
+ fs.rmSync(processingDirectory, { recursive: true, force: true });
601
+ if (!payloadReferenced) fs.rmSync(payloadPath(manifest, entry.name), { force: true });
147
602
  }
148
603
  return true;
149
604
  }
@@ -153,7 +608,13 @@ function delay(ms: number): Promise<void> {
153
608
  }
154
609
 
155
610
  export function sandboxControlSafeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
156
- return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
611
+ return Object.fromEntries(Object.entries(env).filter(([key]) => {
612
+ const normalized = key.toUpperCase();
613
+ return !normalized.startsWith('AGENT_INFRA_CONTROL_')
614
+ && normalized !== 'AGENT_INFRA_TASK_ID'
615
+ && normalized !== 'AGENT_INFRA_RUNTIME_DIR'
616
+ && normalized !== 'AGENT_INFRA_EXECUTOR_MANIFEST';
617
+ }));
157
618
  }
158
619
 
159
620
  export async function serveSandboxControl(
@@ -163,11 +624,14 @@ export async function serveSandboxControl(
163
624
  timing?: SandboxControlTimingPolicy;
164
625
  inspectContainer?: (manifest: SandboxControlManifest) => Promise<ContainerObservation>;
165
626
  bindingCheck?: (manifest: SandboxControlManifest) => string | null;
627
+ prepareExecution?: typeof prepareSandboxControlExecution;
628
+ internalCliPath?: string;
166
629
  } = {}
167
630
  ): Promise<void> {
168
631
  const timing = options.timing ?? DEFAULT_SANDBOX_CONTROL_TIMING;
169
632
  const inspectContainer = options.inspectContainer ?? ((value: SandboxControlManifest) => inspectSandboxControlContainer(value));
170
633
  const bindingCheck = options.bindingCheck ?? bindingReason;
634
+ const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
171
635
  const manifest = readSandboxControlManifest(manifestPath);
172
636
  const root = path.dirname(manifestPath);
173
637
  const startTime = getProcessStartTime(process.pid);
@@ -213,6 +677,25 @@ export async function serveSandboxControl(
213
677
  releaseStartup();
214
678
  }
215
679
  let active: ActiveExecution | null = null;
680
+ let startupReceipt: unknown = null;
681
+ if (manifest.taskId) {
682
+ try {
683
+ startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
684
+ } catch {
685
+ // Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
686
+ startupReceipt = {};
687
+ }
688
+ }
689
+ let taskView = taskViewForManifest({
690
+ ...manifest,
691
+ receipt: startupReceipt
692
+ });
693
+ try {
694
+ const previous = readSandboxControlStatus(manifest.publicStatusDir);
695
+ if (previous.generation === manifest.generation) taskView = mergeSandboxTaskView(taskView, previous.taskView);
696
+ } catch {
697
+ // A fresh control root has no previous task-view projection.
698
+ }
216
699
  let lastState = '';
217
700
  let lastStatusAt = 0;
218
701
  let nextBindingCheckAt = 0;
@@ -223,10 +706,43 @@ export async function serveSandboxControl(
223
706
  let bindingBackoffMs: number = timing.parkedBindingInitialMs;
224
707
  try {
225
708
  if (!brokerOwns()) return;
226
- writeSandboxControlStatus(manifest, broker, 'starting', null, null);
709
+ if (taskView.state === 'current' && taskView.observedSource === 'completed') {
710
+ // Recheck durable re-entry evidence before publishing a trusted startup view.
711
+ const unverified = { ...taskView, state: 'unknown' as const,
712
+ reasonCode: 'SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID' };
713
+ try {
714
+ taskView = await completedReentryView(manifest, inspectContainer) ?? unverified;
715
+ } catch {
716
+ taskView = unverified;
717
+ }
718
+ if (!brokerOwns()) return;
719
+ }
720
+ writeSandboxControlStatus(manifest, broker, 'starting', null, null, Date.now(), taskView);
227
721
  if (!brokerOwns()) return;
228
- appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
229
- if (!recoverProcessing(manifest, brokerOwns)) return;
722
+ appendBrokerAudit(manifest, 'broker-start', {
723
+ pid: broker.pid,
724
+ brokerId: broker.brokerId,
725
+ hostCwd: process.cwd(),
726
+ manifestPath,
727
+ manifestPathRealpath: safeRealpath(manifestPath),
728
+ repoRoot: manifest.repoRoot,
729
+ repoRootRealpath: safeRealpath(manifest.repoRoot),
730
+ worktreeRoot: manifest.worktreeRoot,
731
+ worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
732
+ runtimeDir: manifest.runtimeDir,
733
+ runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
734
+ channelDir: manifest.channelDir,
735
+ publicStatusDir: manifest.publicStatusDir,
736
+ processingDir: manifest.processingDir,
737
+ internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
738
+ });
739
+ if (!recoverProcessing(manifest, broker, brokerOwns)) return;
740
+ try {
741
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
742
+ if (published.generation === manifest.generation) taskView = published.taskView;
743
+ } catch {
744
+ // The regular heartbeat will publish an invalid or missing projection as unknown.
745
+ }
230
746
  while (!signal.aborted) {
231
747
  let settledExecution: ActiveExecution | null = null;
232
748
  if (!brokerOwns()) break;
@@ -237,6 +753,17 @@ export async function serveSandboxControl(
237
753
  break;
238
754
  }
239
755
  if (current.token !== manifest.token || current.generation !== manifest.generation) break;
756
+ if (active?.result && !active.resultEvidenceWritten) {
757
+ try {
758
+ active.result = sanitizeSandboxControlResult(manifest, active.result);
759
+ writeSandboxControlResultEvidence(manifest, active.request.id, active.result);
760
+ active.resultEvidenceWritten = true;
761
+ active.failure = null;
762
+ active.settled = true;
763
+ } catch (error) {
764
+ active.failure = error;
765
+ }
766
+ }
240
767
  const heartbeatNow = Date.now();
241
768
  if (heartbeatNow >= nextContainerHeartbeatAt) {
242
769
  let observation: ContainerObservation;
@@ -287,7 +814,7 @@ export async function serveSandboxControl(
287
814
  if (!brokerOwns()) break;
288
815
  if (cleanupStaleSandboxControlLease(manifest)) {
289
816
  if (!brokerOwns()) break;
290
- appendSandboxControlAudit(manifest, 'lease-stale-cleanup');
817
+ appendBrokerAudit(manifest, 'lease-stale-cleanup');
291
818
  }
292
819
  if (readActiveLease(manifest)) reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
293
820
  } catch {
@@ -307,27 +834,51 @@ export async function serveSandboxControl(
307
834
  }
308
835
  reasonCode ??= containerReasonCode;
309
836
  reasonCode ??= bindingReasonCode;
837
+ if (!reasonCode && !active && taskView.state !== 'current') {
838
+ try {
839
+ const reentered = await completedReentryView(manifest, inspectContainer);
840
+ if (reentered && brokerOwns() && !readActiveLease(manifest) && !bindingCheck(manifest)) {
841
+ taskView = reentered;
842
+ }
843
+ } catch {
844
+ // Invalid or changed re-entry evidence never clears the existing task view.
845
+ }
846
+ }
310
847
  const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
311
848
  const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
312
849
  if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
313
- writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
850
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
314
851
  lastStatusAt = now;
315
852
  }
316
853
  if (brokerOwns() && stateKey !== lastState) {
317
- appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
854
+ appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
318
855
  lastState = stateKey;
319
856
  }
320
857
  if (settledExecution) {
321
858
  if (!brokerOwns()) break;
322
- if (settledExecution.result) {
323
- writeResponse(manifest, {
324
- version: 2, id: settledExecution.request.id, phase: 'completed', exitCode: settledExecution.result.exitCode,
325
- stdout: settledExecution.result.stdout, stderr: settledExecution.result.stderr, error: null
326
- });
859
+ let terminalCommitted = false;
860
+ if (settledExecution.result && settledExecution.resultEvidenceWritten) {
861
+ terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
862
+ if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
863
+ try {
864
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
865
+ if (published.generation === manifest.generation) taskView = published.taskView;
866
+ } catch {
867
+ taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
868
+ }
869
+ }
327
870
  } else {
328
- writeResponse(manifest, unknown(settledExecution.request.id));
871
+ terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
329
872
  }
873
+ if (!terminalCommitted) return;
874
+ appendBrokerAudit(manifest, 'executor-result-published', {
875
+ ...requestAuditFields(manifest, manifestPath, settledExecution.request),
876
+ resultAvailable: settledExecution.result !== null,
877
+ resultEvidenceWritten: settledExecution.resultEvidenceWritten,
878
+ ...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
879
+ });
330
880
  if (!brokerOwns()) break;
881
+ removeAcceptedResponse(manifest, settledExecution.request.id);
331
882
  fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
332
883
  }
333
884
 
@@ -337,12 +888,19 @@ export async function serveSandboxControl(
337
888
  const id = name.slice(0, -5);
338
889
  const source = path.join(requestsDir, name);
339
890
  let claimed: string | null = null;
891
+ let validatedRequest: SandboxControlRequest | null = null;
340
892
  try {
341
893
  if (!brokerOwns()) {
342
894
  retiring = true;
343
895
  break;
344
896
  }
345
897
  claimed = claimRequest(manifest, source, id);
898
+ appendBrokerAudit(manifest, 'request-claimed', {
899
+ requestId: id,
900
+ requestSourcePath: source,
901
+ requestClaimedPath: claimed,
902
+ requestProcessingDir: path.dirname(claimed)
903
+ });
346
904
  if (!brokerOwns()) {
347
905
  retiring = true;
348
906
  break;
@@ -355,16 +913,84 @@ export async function serveSandboxControl(
355
913
  if (reasonCode) throw new Error(reasonCode);
356
914
  if (active) throw new Error('SANDBOX_CONTROL_BUSY');
357
915
  const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
916
+ validatedRequest = request;
917
+ appendBrokerAudit(manifest, 'request-validated', {
918
+ ...requestAuditFields(manifest, manifestPath, request),
919
+ requestPath: claimed
920
+ });
358
921
  if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
359
922
  if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
360
- const prepared = await prepareSandboxControlExecution({
361
- manifest, manifestPath, request, requestPath: claimed, internalCliPath: process.argv[1]!
923
+ appendBrokerAudit(manifest, 'request-gates-passed', {
924
+ ...requestAuditFields(manifest, manifestPath, request),
925
+ requestPath: claimed,
926
+ bindingChecked: true,
927
+ handoffLeaseChecked: true
928
+ });
929
+ writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
930
+ appendBrokerAudit(manifest, 'executor-reservation-written', {
931
+ ...requestAuditFields(manifest, manifestPath, request),
932
+ requestPath: claimed,
933
+ executionPath: executionPath(manifest, request.id)
934
+ });
935
+ appendBrokerAudit(manifest, 'executor-prepare-start', {
936
+ ...requestAuditFields(manifest, manifestPath, request),
937
+ requestPath: claimed,
938
+ executorCwd: manifest.repoRoot,
939
+ executorEntry: options.internalCliPath ?? process.argv[1] ?? null
940
+ });
941
+ const prepared = await prepareExecution({
942
+ manifest, manifestPath, request, requestPath: claimed,
943
+ internalCliPath: options.internalCliPath ?? process.argv[1]!
944
+ });
945
+ appendBrokerAudit(manifest, 'executor-prepared', {
946
+ ...requestAuditFields(manifest, manifestPath, request),
947
+ requestPath: claimed,
948
+ executorCwd: manifest.repoRoot,
949
+ childPid: prepared.execution.child.pid,
950
+ childStartTime: prepared.execution.child.startTime,
951
+ childProcessGroupId: prepared.execution.child.processGroupId,
952
+ executionPath: executionPath(manifest, request.id)
362
953
  });
363
- const execution: ActiveExecution = { request, prepared, result: null, failure: null, settled: false };
954
+ const execution: ActiveExecution = {
955
+ request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
956
+ };
364
957
  active = execution;
365
958
  prepared.completion.then(
366
- (result) => { execution.result = result; execution.settled = true; },
367
- (error) => { execution.failure = error; execution.settled = true; }
959
+ (result) => {
960
+ execution.result = sanitizeSandboxControlResult(manifest, result);
961
+ appendBrokerAudit(manifest, 'executor-completed', {
962
+ ...requestAuditFields(manifest, manifestPath, request),
963
+ ...resultAuditFields(execution.result),
964
+ childPid: prepared.execution.child.pid,
965
+ childStartTime: prepared.execution.child.startTime
966
+ });
967
+ try {
968
+ writeSandboxControlResultEvidence(manifest, request.id, execution.result);
969
+ execution.resultEvidenceWritten = true;
970
+ execution.settled = true;
971
+ appendBrokerAudit(manifest, 'executor-result-evidence-written', {
972
+ ...requestAuditFields(manifest, manifestPath, request),
973
+ resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
974
+ ...resultAuditFields(execution.result)
975
+ });
976
+ } catch (error) {
977
+ execution.failure = error;
978
+ appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
979
+ ...requestAuditFields(manifest, manifestPath, request),
980
+ errorType: error instanceof Error ? error.name : typeof error,
981
+ });
982
+ }
983
+ },
984
+ (error) => {
985
+ execution.failure = error;
986
+ execution.settled = true;
987
+ appendBrokerAudit(manifest, 'executor-failed', {
988
+ ...requestAuditFields(manifest, manifestPath, request),
989
+ childPid: prepared.execution.child.pid,
990
+ childStartTime: prepared.execution.child.startTime,
991
+ errorType: error instanceof Error ? error.name : typeof error,
992
+ });
993
+ }
368
994
  );
369
995
  if (!brokerOwns()) {
370
996
  prepared.terminate(false);
@@ -378,9 +1004,14 @@ export async function serveSandboxControl(
378
1004
  retiring = true;
379
1005
  continue;
380
1006
  }
381
- writeResponse(manifest, {
1007
+ writeAcceptedResponse(manifest, {
382
1008
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
383
1009
  });
1010
+ appendBrokerAudit(manifest, 'request-accepted', {
1011
+ ...requestAuditFields(manifest, manifestPath, request),
1012
+ acceptedPath: acceptedResponsePath(manifest, id),
1013
+ executionPhase: 'prepared'
1014
+ });
384
1015
  if (!brokerOwns()) {
385
1016
  prepared.terminate(false);
386
1017
  active = null;
@@ -388,8 +1019,18 @@ export async function serveSandboxControl(
388
1019
  break;
389
1020
  }
390
1021
  try {
1022
+ appendBrokerAudit(manifest, 'executor-start', {
1023
+ ...requestAuditFields(manifest, manifestPath, request),
1024
+ childPid: prepared.execution.child.pid,
1025
+ childStartTime: prepared.execution.child.startTime,
1026
+ executionPath: executionPath(manifest, request.id)
1027
+ });
391
1028
  prepared.start(brokerOwns);
392
1029
  } catch (error) {
1030
+ appendBrokerAudit(manifest, 'executor-start-failed', {
1031
+ ...requestAuditFields(manifest, manifestPath, request),
1032
+ errorType: error instanceof Error ? error.name : typeof error,
1033
+ });
393
1034
  const owned = brokerOwns();
394
1035
  prepared.terminate(owned);
395
1036
  if (!owned || !brokerOwns()) {
@@ -397,12 +1038,13 @@ export async function serveSandboxControl(
397
1038
  retiring = true;
398
1039
  break;
399
1040
  }
400
- writeResponse(manifest, unknown(id));
1041
+ writeSandboxControlResponse(manifest, unknown(id));
401
1042
  if (!brokerOwns()) {
402
1043
  active = null;
403
1044
  retiring = true;
404
1045
  break;
405
1046
  }
1047
+ removeAcceptedResponse(manifest, id);
406
1048
  fs.rmSync(path.join(manifest.processingDir, id), { recursive: true, force: true });
407
1049
  active = null;
408
1050
  claimed = null;
@@ -410,7 +1052,7 @@ export async function serveSandboxControl(
410
1052
  retiring = true;
411
1053
  break;
412
1054
  }
413
- appendSandboxControlAudit(manifest, 'executor-gate-failed', { requestId: id });
1055
+ appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
414
1056
  continue;
415
1057
  }
416
1058
  } catch (error) {
@@ -418,7 +1060,17 @@ export async function serveSandboxControl(
418
1060
  retiring = true;
419
1061
  break;
420
1062
  }
421
- writeResponse(manifest, rejected(id, error));
1063
+ const detail = controlError(error);
1064
+ appendBrokerAudit(manifest, 'request-rejected', {
1065
+ requestId: id,
1066
+ requestFamily: validatedRequest?.family ?? null,
1067
+ sandboxTaskId: manifest.taskId,
1068
+ requestPath: claimed,
1069
+ errorCode: detail.code,
1070
+ errorRetryable: detail.retryable,
1071
+ errorType: error instanceof Error ? error.name : typeof error,
1072
+ });
1073
+ writeSandboxControlResponse(manifest, rejected(id, error));
422
1074
  if (!brokerOwns()) {
423
1075
  retiring = true;
424
1076
  break;
@@ -432,8 +1084,46 @@ export async function serveSandboxControl(
432
1084
  } finally {
433
1085
  if (active) {
434
1086
  const owned = brokerOwns();
435
- active.prepared.terminate(owned);
436
- if (owned && brokerOwns()) writeResponse(manifest, unknown(active.request.id));
1087
+ if (owned && active.result && !active.resultEvidenceWritten) {
1088
+ try {
1089
+ active.result = sanitizeSandboxControlResult(manifest, active.result);
1090
+ writeSandboxControlResultEvidence(manifest, active.request.id, active.result);
1091
+ active.resultEvidenceWritten = true;
1092
+ active.failure = null;
1093
+ active.settled = true;
1094
+ } catch (error) {
1095
+ active.failure = error;
1096
+ }
1097
+ }
1098
+ if (owned && active.result && active.resultEvidenceWritten) {
1099
+ if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1100
+ if (brokerOwns()) {
1101
+ removeAcceptedResponse(manifest, active.request.id);
1102
+ fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
1103
+ active = null;
1104
+ }
1105
+ }
1106
+ }
1107
+ if (active) {
1108
+ const terminationConfirmed = active.prepared.terminate(owned);
1109
+ if (owned && brokerOwns() && active.request.family === 'task-finalization') {
1110
+ const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
1111
+ const view = recovered.status === 'matched'
1112
+ ? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
1113
+ : null;
1114
+ if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
1115
+ && writeSandboxControlResponse(manifest, recovered.response)) {
1116
+ if (terminationConfirmed && brokerOwns()) {
1117
+ removeAcceptedResponse(manifest, active.request.id);
1118
+ fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
1119
+ active = null;
1120
+ }
1121
+ }
1122
+ }
1123
+ if (owned && brokerOwns() && active && active.request.family !== 'task-finalization' && !active.resultEvidenceWritten) {
1124
+ writeSandboxControlResponse(manifest, unknown(active.request.id));
1125
+ }
1126
+ }
437
1127
  }
438
1128
  if (brokerOwns()) {
439
1129
  appendSandboxControlAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });