@fitlab-ai/agent-infra 0.9.13 → 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 (280) hide show
  1. package/bin/cli.ts +26 -9
  2. package/dist/bin/cli.js +173 -157
  3. package/dist/bin/internal-cli.js +42 -21
  4. package/dist/lib/agent-client.js +5 -0
  5. package/dist/lib/internal/agent-client.js +5 -2
  6. package/dist/lib/internal/cli-route-inventory.js +152 -0
  7. package/dist/lib/internal/codex-lifecycle.js +9 -6
  8. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  9. package/dist/lib/internal/git-workflow.js +10 -6
  10. package/dist/lib/internal/platform-checks.js +16 -4
  11. package/dist/lib/internal/platform-comment.js +16 -4
  12. package/dist/lib/internal/platform-context.js +4 -1
  13. package/dist/lib/internal/platform-issue.js +12 -4
  14. package/dist/lib/internal/platform-metadata.js +88 -0
  15. package/dist/lib/internal/platform-pr-review.js +16 -3
  16. package/dist/lib/internal/platform-pr.js +24 -10
  17. package/dist/lib/internal/platform-release-notes.js +6 -3
  18. package/dist/lib/internal/platform-security.js +93 -0
  19. package/dist/lib/internal/pr-review-grade.js +14 -3
  20. package/dist/lib/internal/release-workflow.js +19 -5
  21. package/dist/lib/internal/sandbox-control.js +22 -5
  22. package/dist/lib/internal/task-activity.js +13 -4
  23. package/dist/lib/internal/task-artifact.js +186 -27
  24. package/dist/lib/internal/task-context.js +4 -1
  25. package/dist/lib/internal/task-create.js +48 -15
  26. package/dist/lib/internal/task-delivery.js +4 -1
  27. package/dist/lib/internal/task-event.js +4 -1
  28. package/dist/lib/internal/task-finalization.js +4 -1
  29. package/dist/lib/internal/task-invalidation.js +4 -1
  30. package/dist/lib/internal/task-ledger.js +12 -1
  31. package/dist/lib/internal/task-lifecycle.js +4 -1
  32. package/dist/lib/internal/task-operation-registry.js +421 -0
  33. package/dist/lib/internal/task-orchestration.js +12 -0
  34. package/dist/lib/internal/task-override.js +10 -3
  35. package/dist/lib/internal/task-qualification.js +90 -0
  36. package/dist/lib/internal/task-review.js +4 -1
  37. package/dist/lib/internal/task-short-id.js +11 -1
  38. package/dist/lib/internal/task-snapshot.js +4 -1
  39. package/dist/lib/internal/task-validate.js +7 -2
  40. package/dist/lib/internal/task-verify.js +5 -1
  41. package/dist/lib/internal/task-warning.js +8 -1
  42. package/dist/lib/platform/comment-safety.js +226 -0
  43. package/dist/lib/platform/github-provider.js +233 -6
  44. package/dist/lib/platform/issue-comments.js +146 -34
  45. package/dist/lib/platform/pr-change-report.js +6 -12
  46. package/dist/lib/platform/pr-summary.js +28 -7
  47. package/dist/lib/platform/provider-contract.d.ts +67 -1
  48. package/dist/lib/platform/provider-contract.js +6 -2
  49. package/dist/lib/platform/provider-validation.js +76 -3
  50. package/dist/lib/platform/pull-requests.js +115 -170
  51. package/dist/lib/platform/repository-metadata.js +377 -0
  52. package/dist/lib/platform/security-alerts.js +155 -0
  53. package/dist/lib/platform/verification-sync.js +33 -26
  54. package/dist/lib/process-data/index.js +7 -0
  55. package/dist/lib/run/index.js +5 -0
  56. package/dist/lib/sandbox/commands/control-status.js +22 -0
  57. package/dist/lib/sandbox/commands/create.js +32 -6
  58. package/dist/lib/sandbox/commands/ls.js +5 -0
  59. package/dist/lib/sandbox/commands/rm.js +827 -120
  60. package/dist/lib/sandbox/commands/show.js +16 -0
  61. package/dist/lib/sandbox/control/client.js +83 -9
  62. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  63. package/dist/lib/sandbox/control/container-identity.js +28 -31
  64. package/dist/lib/sandbox/control/executor.js +149 -14
  65. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  66. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  67. package/dist/lib/sandbox/control/server.js +305 -35
  68. package/dist/lib/sandbox/control/state.js +5 -3
  69. package/dist/lib/sandbox/control/task-view.js +183 -0
  70. package/dist/lib/sandbox/engines/authority.js +289 -0
  71. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  72. package/dist/lib/sandbox/index.js +5 -0
  73. package/dist/lib/sandbox/recovery.js +21 -1
  74. package/dist/lib/sandbox/workspace-view.js +2 -1
  75. package/dist/lib/sandbox/worktree-safety.js +4 -1
  76. package/dist/lib/server/index.js +8 -0
  77. package/dist/lib/task/artifact-lifecycle.js +42 -13
  78. package/dist/lib/task/artifact-operations.js +520 -0
  79. package/dist/lib/task/artifact-repair-intent.js +59 -0
  80. package/dist/lib/task/artifact-schema.js +135 -0
  81. package/dist/lib/task/capabilities.js +72 -1
  82. package/dist/lib/task/control-authority.js +4 -0
  83. package/dist/lib/task/create-service.js +103 -4
  84. package/dist/lib/task/create.js +25 -0
  85. package/dist/lib/task/events.js +186 -67
  86. package/dist/lib/task/index.js +11 -3
  87. package/dist/lib/task/local-artifact-finalization.js +75 -150
  88. package/dist/lib/task/orchestration.js +192 -52
  89. package/dist/lib/task/qualification-audit.js +490 -0
  90. package/dist/lib/task/qualification-confirmation.js +109 -0
  91. package/dist/lib/task/qualification-intents.js +148 -0
  92. package/dist/lib/task/review-artifacts.js +22 -23
  93. package/dist/lib/task/review-finalization.js +121 -4
  94. package/dist/lib/task/sections.js +6 -4
  95. package/dist/lib/task/verification-config.js +11 -0
  96. package/dist/lib/task/verification-engine.js +54 -4
  97. package/dist/lifecycle-build-manifest.json +3 -3
  98. package/lib/agent-client.ts +9 -0
  99. package/lib/internal/agent-client.ts +4 -2
  100. package/lib/internal/cli-route-inventory.ts +140 -0
  101. package/lib/internal/codex-lifecycle.ts +8 -6
  102. package/lib/internal/codex-sandbox-controller.ts +7 -2
  103. package/lib/internal/git-workflow.ts +9 -6
  104. package/lib/internal/platform-checks.ts +12 -4
  105. package/lib/internal/platform-comment.ts +12 -4
  106. package/lib/internal/platform-context.ts +3 -1
  107. package/lib/internal/platform-issue.ts +11 -4
  108. package/lib/internal/platform-metadata.ts +70 -0
  109. package/lib/internal/platform-pr-review.ts +14 -3
  110. package/lib/internal/platform-pr.ts +23 -10
  111. package/lib/internal/platform-release-notes.ts +5 -3
  112. package/lib/internal/platform-security.ts +83 -0
  113. package/lib/internal/pr-review-grade.ts +10 -3
  114. package/lib/internal/release-workflow.ts +17 -5
  115. package/lib/internal/sandbox-control.ts +19 -5
  116. package/lib/internal/task-activity.ts +12 -4
  117. package/lib/internal/task-artifact.ts +164 -25
  118. package/lib/internal/task-context.ts +3 -1
  119. package/lib/internal/task-create.ts +76 -15
  120. package/lib/internal/task-delivery.ts +3 -1
  121. package/lib/internal/task-event.ts +3 -1
  122. package/lib/internal/task-finalization.ts +3 -1
  123. package/lib/internal/task-invalidation.ts +3 -1
  124. package/lib/internal/task-ledger.ts +11 -1
  125. package/lib/internal/task-lifecycle.ts +3 -1
  126. package/lib/internal/task-operation-registry.ts +482 -0
  127. package/lib/internal/task-orchestration.ts +12 -0
  128. package/lib/internal/task-override.ts +9 -3
  129. package/lib/internal/task-qualification.ts +61 -0
  130. package/lib/internal/task-review.ts +3 -1
  131. package/lib/internal/task-short-id.ts +10 -1
  132. package/lib/internal/task-snapshot.ts +3 -1
  133. package/lib/internal/task-validate.ts +6 -2
  134. package/lib/internal/task-verify.ts +4 -1
  135. package/lib/internal/task-warning.ts +7 -1
  136. package/lib/platform/comment-safety.ts +250 -0
  137. package/lib/platform/github-provider.ts +229 -7
  138. package/lib/platform/issue-comments.ts +160 -35
  139. package/lib/platform/pr-change-report.ts +6 -12
  140. package/lib/platform/pr-summary.ts +27 -6
  141. package/lib/platform/provider-contract.ts +78 -3
  142. package/lib/platform/provider-validation.ts +85 -3
  143. package/lib/platform/pull-requests.ts +126 -178
  144. package/lib/platform/repository-metadata.ts +453 -0
  145. package/lib/platform/security-alerts.ts +191 -0
  146. package/lib/platform/verification-sync.ts +32 -28
  147. package/lib/process-data/index.ts +7 -0
  148. package/lib/run/index.ts +5 -0
  149. package/lib/sandbox/commands/control-status.ts +26 -0
  150. package/lib/sandbox/commands/create.ts +46 -6
  151. package/lib/sandbox/commands/ls.ts +7 -0
  152. package/lib/sandbox/commands/rm.ts +1000 -87
  153. package/lib/sandbox/commands/show.ts +16 -0
  154. package/lib/sandbox/control/client.ts +103 -9
  155. package/lib/sandbox/control/completed-reentry.ts +98 -0
  156. package/lib/sandbox/control/container-identity.ts +27 -43
  157. package/lib/sandbox/control/executor.ts +177 -12
  158. package/lib/sandbox/control/lifecycle.ts +629 -17
  159. package/lib/sandbox/control/native-file-lock.ts +209 -0
  160. package/lib/sandbox/control/protocol.ts +5 -2
  161. package/lib/sandbox/control/server.ts +319 -37
  162. package/lib/sandbox/control/state.ts +6 -3
  163. package/lib/sandbox/control/task-view.ts +240 -0
  164. package/lib/sandbox/engines/authority.ts +365 -0
  165. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  166. package/lib/sandbox/index.ts +7 -0
  167. package/lib/sandbox/recovery.ts +21 -1
  168. package/lib/sandbox/workspace-view.ts +9 -3
  169. package/lib/sandbox/worktree-safety.ts +4 -1
  170. package/lib/server/index.ts +10 -0
  171. package/lib/task/artifact-lifecycle.ts +38 -16
  172. package/lib/task/artifact-operations.ts +626 -0
  173. package/lib/task/artifact-repair-intent.ts +71 -0
  174. package/lib/task/artifact-schema.ts +192 -0
  175. package/lib/task/capabilities.ts +70 -1
  176. package/lib/task/control-authority.ts +11 -1
  177. package/lib/task/create-service.ts +131 -6
  178. package/lib/task/create.ts +27 -0
  179. package/lib/task/events.ts +161 -65
  180. package/lib/task/index.ts +16 -3
  181. package/lib/task/local-artifact-finalization.ts +87 -218
  182. package/lib/task/orchestration.ts +172 -49
  183. package/lib/task/qualification-audit.ts +599 -0
  184. package/lib/task/qualification-confirmation.ts +117 -0
  185. package/lib/task/qualification-intents.ts +150 -0
  186. package/lib/task/review-artifacts.ts +21 -22
  187. package/lib/task/review-finalization.ts +133 -4
  188. package/lib/task/sections.ts +6 -4
  189. package/lib/task/verification-config.ts +12 -0
  190. package/lib/task/verification-engine.ts +55 -4
  191. package/package.json +6 -1
  192. package/templates/.agents/rules/README.en.md +2 -0
  193. package/templates/.agents/rules/README.zh-CN.md +2 -0
  194. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  195. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  196. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  197. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  198. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  199. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  200. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  201. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  202. package/templates/.agents/rules/security-alerts.en.md +8 -8
  203. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  204. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  206. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  207. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  208. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  213. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  214. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  215. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  216. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  217. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  218. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  219. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  221. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  222. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  223. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
  226. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  227. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  228. package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
  229. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
  230. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  231. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  232. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  233. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  234. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  235. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  236. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  237. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  238. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  239. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  240. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  241. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  242. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  243. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  244. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  245. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  246. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  247. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  248. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  249. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  250. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  251. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  252. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  253. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  254. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  255. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  256. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  257. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  258. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  259. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  260. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  261. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  262. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  263. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  264. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  265. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
  268. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  269. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  270. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  271. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  272. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  273. package/templates/.agents/templates/task.en.md +6 -0
  274. package/templates/.agents/templates/task.zh-CN.md +6 -0
  275. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  276. package/templates/.agents/scripts/security-alerts.sh +0 -31
  277. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  278. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  279. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  280. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,3 +1,4 @@
1
+ import { completedReentryView } from './completed-reentry.js';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { createHash, randomUUID } from 'node:crypto';
@@ -5,13 +6,66 @@ import { getProcessStartTime } from '../../server/process-state.js';
5
6
  import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
6
7
  import { controlError, DEFAULT_SANDBOX_CONTROL_TIMING, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES } from './protocol.js';
7
8
  import { prepareSandboxControlExecution } from './executor.js';
8
- import { appendSandboxControlAudit, atomicWriteJsonNoReplace, createSandboxControlPayload, cleanupStaleSandboxControlLease, executionPath, readJsonFile, readActiveLease, readExecution, readSandboxControlPayload, readSandboxControlResultEvidence, sandboxControlEncodedJsonBytes, sandboxControlGenerationUsage, sanitizeSandboxControlOutput, sanitizeSandboxControlResult, payloadPath, writeSandboxControlPayload, writeSandboxControlReservation, writeSandboxControlResultEvidence, terminateSandboxControlExecution, writeSandboxControlStatus } from './state.js';
9
+ import { appendSandboxControlAudit, atomicWriteJsonNoReplace, createSandboxControlPayload, cleanupStaleSandboxControlLease, executionPath, readJsonFile, readActiveLease, readExecution, readSandboxControlPayload, readSandboxControlResultEvidence, sandboxControlEncodedJsonBytes, sandboxControlGenerationUsage, sanitizeSandboxControlOutput, sanitizeSandboxControlResult, payloadPath, writeSandboxControlPayload, writeSandboxControlReservation, writeSandboxControlResultEvidence, terminateSandboxControlExecution, writeSandboxControlStatus, readSandboxControlStatus } from './state.js';
9
10
  import { validateSandboxControlRequest } from './protocol.js';
10
11
  import { inspectSandboxControlContainer } from './container-identity.js';
11
12
  import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
12
13
  import { nextSandboxControlBackoff } from './timing.js';
13
14
  import { readTaskFinalizationReceipt } from '../../task/finalization.js';
14
15
  import { resolveTaskRef } from '../../task/resolve-ref.js';
16
+ import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
17
+ import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
18
+ function safeRealpath(value) {
19
+ try {
20
+ return fs.realpathSync.native(value);
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function appendBrokerAudit(manifest, event, fields = {}) {
27
+ try {
28
+ appendSandboxControlAudit(manifest, event, { source: 'broker', ...fields });
29
+ }
30
+ catch {
31
+ // Diagnostics must never change the control protocol.
32
+ }
33
+ }
34
+ function requestAuditFields(manifest, manifestPath, request) {
35
+ const args = 'args' in request ? request.args : [];
36
+ const encodedArgs = JSON.stringify(args);
37
+ return {
38
+ requestId: request.id,
39
+ requestFamily: request.family,
40
+ sandboxTaskId: manifest.taskId,
41
+ requestGeneration: request.generation,
42
+ requestIssuedAt: request.issuedAt,
43
+ requestExpiresAt: request.expiresAt,
44
+ requestArgCount: args.length,
45
+ requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
46
+ requestTaskRef: args[0] ?? null,
47
+ requestCommand: args[1] ?? null,
48
+ controllerProofPresent: request.controllerProof !== null,
49
+ hostCwd: process.cwd(),
50
+ manifestPath,
51
+ manifestPathRealpath: safeRealpath(manifestPath),
52
+ repoRoot: manifest.repoRoot,
53
+ repoRootRealpath: safeRealpath(manifest.repoRoot),
54
+ worktreeRoot: manifest.worktreeRoot,
55
+ worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
56
+ runtimeDir: manifest.runtimeDir,
57
+ runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
58
+ };
59
+ }
60
+ function resultAuditFields(result) {
61
+ return {
62
+ exitCode: result.exitCode,
63
+ stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
64
+ stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
65
+ stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
66
+ stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
67
+ };
68
+ }
15
69
  function assertRealDirectory(directory, parent) {
16
70
  const stat = fs.lstatSync(directory);
17
71
  if (!stat.isDirectory() || stat.isSymbolicLink())
@@ -120,6 +174,29 @@ function finalizationRecoveryResponse(manifest, requestId, exitCode) {
120
174
  }
121
175
  };
122
176
  }
177
+ function publishFinalizationTaskView(manifest, broker, requestId, state, reasonCode, activeRequestId) {
178
+ let view;
179
+ try {
180
+ const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
181
+ const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
182
+ view = resolved.ok && resolved.state === 'completed'
183
+ ? taskViewAfterFinalization({
184
+ taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
185
+ })
186
+ : {
187
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
188
+ reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
189
+ };
190
+ }
191
+ catch {
192
+ view = {
193
+ state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
194
+ reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
195
+ };
196
+ }
197
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
198
+ return view;
199
+ }
123
200
  function payloadReference(payload) {
124
201
  return {
125
202
  version: payload.version,
@@ -143,14 +220,17 @@ function outputMatchesEvidence(output, bytes, sha256) {
143
220
  return Buffer.byteLength(output, 'utf8') === bytes
144
221
  && createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
145
222
  }
146
- function genericRecoveryResponse(requestId, evidence, payload) {
223
+ function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery') {
224
+ const outputUnavailable = request.family === 'task-create' && !payload;
147
225
  return {
148
226
  version: 2,
149
- id: requestId,
227
+ id: request.id,
150
228
  phase: 'completed',
151
- exitCode: evidence.exitCode,
152
- stdout: '',
153
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
229
+ exitCode,
230
+ stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
231
+ stderr: payload ? '' : cause === 'publish'
232
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
233
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
154
234
  error: null,
155
235
  outputState: payload ? 'available' : 'unavailable',
156
236
  payload: payload ? payloadReference(payload) : null
@@ -161,7 +241,7 @@ function recoveryResponse(manifest, request, evidence, payload) {
161
241
  const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
162
242
  return finalization.status === 'matched' ? finalization.response ?? null : null;
163
243
  }
164
- return genericRecoveryResponse(request.id, evidence, payload);
244
+ return genericRecoveryResponse(request, evidence.exitCode, payload);
165
245
  }
166
246
  function terminalMatchesEvidence(manifest, request, response, evidence, payload, payloadInvalid) {
167
247
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
@@ -192,8 +272,10 @@ function terminalMatchesEvidence(manifest, request, response, evidence, payload,
192
272
  };
193
273
  }
194
274
  if (response.outputState === 'unavailable') {
195
- const expected = genericRecoveryResponse(request.id, evidence, null);
196
- return { valid: JSON.stringify(response) === JSON.stringify(expected), payloadReferenced: false };
275
+ const causes = ['recovery', 'publish'];
276
+ const valid = causes.some((cause) => JSON.stringify(response)
277
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
278
+ return { valid, payloadReferenced: false };
197
279
  }
198
280
  return {
199
281
  valid: response.payload === undefined
@@ -202,7 +284,7 @@ function terminalMatchesEvidence(manifest, request, response, evidence, payload,
202
284
  payloadReferenced: false
203
285
  };
204
286
  }
205
- function publishExecutionResult(manifest, request, result, brokerOwns) {
287
+ function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
206
288
  const normalized = sanitizeSandboxControlResult(manifest, result);
207
289
  let terminal = null;
208
290
  if (request.family === 'task-finalization') {
@@ -234,21 +316,16 @@ function publishExecutionResult(manifest, request, result, brokerOwns) {
234
316
  catch {
235
317
  payload = null;
236
318
  }
237
- terminal = {
238
- version: 2,
239
- id: request.id,
240
- phase: 'completed',
241
- exitCode: normalized.exitCode,
242
- stdout: '',
243
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
244
- error: null,
245
- outputState: payload ? 'available' : 'unavailable',
246
- payload: payload ? payloadReference(payload) : null
247
- };
319
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
248
320
  }
249
321
  }
250
322
  if (!brokerOwns())
251
323
  return false;
324
+ if (request.family === 'task-finalization' && normalized.exitCode === 0) {
325
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
326
+ if (view.state === 'unknown')
327
+ return false;
328
+ }
252
329
  return writeSandboxControlResponse(manifest, terminal);
253
330
  }
254
331
  function writeAcceptedResponse(manifest, response) {
@@ -309,7 +386,7 @@ function bindingReason(manifest) {
309
386
  return 'SANDBOX_WORKTREE_BINDING_LOST';
310
387
  }
311
388
  }
312
- function recoverProcessing(manifest, brokerOwns) {
389
+ function recoverProcessing(manifest, broker, brokerOwns) {
313
390
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
314
391
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name))
315
392
  continue;
@@ -385,7 +462,7 @@ function recoverProcessing(manifest, brokerOwns) {
385
462
  throw new Error('payload missing or mismatched');
386
463
  payloadReferenced = true;
387
464
  }
388
- else if (terminal && (response.outputState !== undefined
465
+ else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
389
466
  || response.payload !== undefined && response.payload !== null)) {
390
467
  throw new Error('payload state invalid');
391
468
  }
@@ -403,6 +480,11 @@ function recoverProcessing(manifest, brokerOwns) {
403
480
  if (!reconciliation.valid)
404
481
  continue;
405
482
  payloadReferenced = reconciliation.payloadReferenced;
483
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
484
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
485
+ if (view.state === 'unknown')
486
+ continue;
487
+ }
406
488
  }
407
489
  if (!terminal) {
408
490
  if (!brokerOwns())
@@ -412,6 +494,11 @@ function recoverProcessing(manifest, brokerOwns) {
412
494
  const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
413
495
  if (!recovered)
414
496
  continue;
497
+ if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
498
+ const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
499
+ if (view.state === 'unknown')
500
+ continue;
501
+ }
415
502
  writeSandboxControlResponse(manifest, recovered);
416
503
  payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
417
504
  }
@@ -434,7 +521,13 @@ function delay(ms) {
434
521
  return new Promise((resolve) => setTimeout(resolve, ms));
435
522
  }
436
523
  export function sandboxControlSafeEnv(env = process.env) {
437
- return Object.fromEntries(Object.entries(env).filter(([key]) => !key.toUpperCase().startsWith('AGENT_INFRA_CONTROL_')));
524
+ return Object.fromEntries(Object.entries(env).filter(([key]) => {
525
+ const normalized = key.toUpperCase();
526
+ return !normalized.startsWith('AGENT_INFRA_CONTROL_')
527
+ && normalized !== 'AGENT_INFRA_TASK_ID'
528
+ && normalized !== 'AGENT_INFRA_RUNTIME_DIR'
529
+ && normalized !== 'AGENT_INFRA_EXECUTOR_MANIFEST';
530
+ }));
438
531
  }
439
532
  export async function serveSandboxControl(manifestPath, signal = new AbortController().signal, options = {}) {
440
533
  const timing = options.timing ?? DEFAULT_SANDBOX_CONTROL_TIMING;
@@ -490,6 +583,28 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
490
583
  releaseStartup();
491
584
  }
492
585
  let active = null;
586
+ let startupReceipt = null;
587
+ if (manifest.taskId) {
588
+ try {
589
+ startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
590
+ }
591
+ catch {
592
+ // Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
593
+ startupReceipt = {};
594
+ }
595
+ }
596
+ let taskView = taskViewForManifest({
597
+ ...manifest,
598
+ receipt: startupReceipt
599
+ });
600
+ try {
601
+ const previous = readSandboxControlStatus(manifest.publicStatusDir);
602
+ if (previous.generation === manifest.generation)
603
+ taskView = mergeSandboxTaskView(taskView, previous.taskView);
604
+ }
605
+ catch {
606
+ // A fresh control root has no previous task-view projection.
607
+ }
493
608
  let lastState = '';
494
609
  let lastStatusAt = 0;
495
610
  let nextBindingCheckAt = 0;
@@ -501,12 +616,49 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
501
616
  try {
502
617
  if (!brokerOwns())
503
618
  return;
504
- writeSandboxControlStatus(manifest, broker, 'starting', null, null);
619
+ if (taskView.state === 'current' && taskView.observedSource === 'completed') {
620
+ // Recheck durable re-entry evidence before publishing a trusted startup view.
621
+ const unverified = { ...taskView, state: 'unknown',
622
+ reasonCode: 'SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID' };
623
+ try {
624
+ taskView = await completedReentryView(manifest, inspectContainer) ?? unverified;
625
+ }
626
+ catch {
627
+ taskView = unverified;
628
+ }
629
+ if (!brokerOwns())
630
+ return;
631
+ }
632
+ writeSandboxControlStatus(manifest, broker, 'starting', null, null, Date.now(), taskView);
505
633
  if (!brokerOwns())
506
634
  return;
507
- appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
508
- if (!recoverProcessing(manifest, brokerOwns))
635
+ appendBrokerAudit(manifest, 'broker-start', {
636
+ pid: broker.pid,
637
+ brokerId: broker.brokerId,
638
+ hostCwd: process.cwd(),
639
+ manifestPath,
640
+ manifestPathRealpath: safeRealpath(manifestPath),
641
+ repoRoot: manifest.repoRoot,
642
+ repoRootRealpath: safeRealpath(manifest.repoRoot),
643
+ worktreeRoot: manifest.worktreeRoot,
644
+ worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
645
+ runtimeDir: manifest.runtimeDir,
646
+ runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
647
+ channelDir: manifest.channelDir,
648
+ publicStatusDir: manifest.publicStatusDir,
649
+ processingDir: manifest.processingDir,
650
+ internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
651
+ });
652
+ if (!recoverProcessing(manifest, broker, brokerOwns))
509
653
  return;
654
+ try {
655
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
656
+ if (published.generation === manifest.generation)
657
+ taskView = published.taskView;
658
+ }
659
+ catch {
660
+ // The regular heartbeat will publish an invalid or missing projection as unknown.
661
+ }
510
662
  while (!signal.aborted) {
511
663
  let settledExecution = null;
512
664
  if (!brokerOwns())
@@ -589,7 +741,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
589
741
  if (cleanupStaleSandboxControlLease(manifest)) {
590
742
  if (!brokerOwns())
591
743
  break;
592
- appendSandboxControlAudit(manifest, 'lease-stale-cleanup');
744
+ appendBrokerAudit(manifest, 'lease-stale-cleanup');
593
745
  }
594
746
  if (readActiveLease(manifest))
595
747
  reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
@@ -612,14 +764,25 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
612
764
  }
613
765
  reasonCode ??= containerReasonCode;
614
766
  reasonCode ??= bindingReasonCode;
767
+ if (!reasonCode && !active && taskView.state !== 'current') {
768
+ try {
769
+ const reentered = await completedReentryView(manifest, inspectContainer);
770
+ if (reentered && brokerOwns() && !readActiveLease(manifest) && !bindingCheck(manifest)) {
771
+ taskView = reentered;
772
+ }
773
+ }
774
+ catch {
775
+ // Invalid or changed re-entry evidence never clears the existing task view.
776
+ }
777
+ }
615
778
  const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
616
779
  const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
617
780
  if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
618
- writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
781
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
619
782
  lastStatusAt = now;
620
783
  }
621
784
  if (brokerOwns() && stateKey !== lastState) {
622
- appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
785
+ appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
623
786
  lastState = stateKey;
624
787
  }
625
788
  if (settledExecution) {
@@ -627,13 +790,29 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
627
790
  break;
628
791
  let terminalCommitted = false;
629
792
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
630
- terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
793
+ terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
794
+ if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
795
+ try {
796
+ const published = readSandboxControlStatus(manifest.publicStatusDir);
797
+ if (published.generation === manifest.generation)
798
+ taskView = published.taskView;
799
+ }
800
+ catch {
801
+ taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
802
+ }
803
+ }
631
804
  }
632
805
  else {
633
806
  terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
634
807
  }
635
808
  if (!terminalCommitted)
636
809
  return;
810
+ appendBrokerAudit(manifest, 'executor-result-published', {
811
+ ...requestAuditFields(manifest, manifestPath, settledExecution.request),
812
+ resultAvailable: settledExecution.result !== null,
813
+ resultEvidenceWritten: settledExecution.resultEvidenceWritten,
814
+ ...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
815
+ });
637
816
  if (!brokerOwns())
638
817
  break;
639
818
  removeAcceptedResponse(manifest, settledExecution.request.id);
@@ -646,12 +825,19 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
646
825
  const id = name.slice(0, -5);
647
826
  const source = path.join(requestsDir, name);
648
827
  let claimed = null;
828
+ let validatedRequest = null;
649
829
  try {
650
830
  if (!brokerOwns()) {
651
831
  retiring = true;
652
832
  break;
653
833
  }
654
834
  claimed = claimRequest(manifest, source, id);
835
+ appendBrokerAudit(manifest, 'request-claimed', {
836
+ requestId: id,
837
+ requestSourcePath: source,
838
+ requestClaimedPath: claimed,
839
+ requestProcessingDir: path.dirname(claimed)
840
+ });
655
841
  if (!brokerOwns()) {
656
842
  retiring = true;
657
843
  break;
@@ -666,30 +852,85 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
666
852
  if (active)
667
853
  throw new Error('SANDBOX_CONTROL_BUSY');
668
854
  const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
855
+ validatedRequest = request;
856
+ appendBrokerAudit(manifest, 'request-validated', {
857
+ ...requestAuditFields(manifest, manifestPath, request),
858
+ requestPath: claimed
859
+ });
669
860
  if (bindingCheck(manifest))
670
861
  throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
671
862
  if (readActiveLease(manifest))
672
863
  throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
864
+ appendBrokerAudit(manifest, 'request-gates-passed', {
865
+ ...requestAuditFields(manifest, manifestPath, request),
866
+ requestPath: claimed,
867
+ bindingChecked: true,
868
+ handoffLeaseChecked: true
869
+ });
673
870
  writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
871
+ appendBrokerAudit(manifest, 'executor-reservation-written', {
872
+ ...requestAuditFields(manifest, manifestPath, request),
873
+ requestPath: claimed,
874
+ executionPath: executionPath(manifest, request.id)
875
+ });
876
+ appendBrokerAudit(manifest, 'executor-prepare-start', {
877
+ ...requestAuditFields(manifest, manifestPath, request),
878
+ requestPath: claimed,
879
+ executorCwd: manifest.repoRoot,
880
+ executorEntry: options.internalCliPath ?? process.argv[1] ?? null
881
+ });
674
882
  const prepared = await prepareExecution({
675
883
  manifest, manifestPath, request, requestPath: claimed,
676
884
  internalCliPath: options.internalCliPath ?? process.argv[1]
677
885
  });
886
+ appendBrokerAudit(manifest, 'executor-prepared', {
887
+ ...requestAuditFields(manifest, manifestPath, request),
888
+ requestPath: claimed,
889
+ executorCwd: manifest.repoRoot,
890
+ childPid: prepared.execution.child.pid,
891
+ childStartTime: prepared.execution.child.startTime,
892
+ childProcessGroupId: prepared.execution.child.processGroupId,
893
+ executionPath: executionPath(manifest, request.id)
894
+ });
678
895
  const execution = {
679
896
  request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
680
897
  };
681
898
  active = execution;
682
899
  prepared.completion.then((result) => {
683
900
  execution.result = sanitizeSandboxControlResult(manifest, result);
901
+ appendBrokerAudit(manifest, 'executor-completed', {
902
+ ...requestAuditFields(manifest, manifestPath, request),
903
+ ...resultAuditFields(execution.result),
904
+ childPid: prepared.execution.child.pid,
905
+ childStartTime: prepared.execution.child.startTime
906
+ });
684
907
  try {
685
908
  writeSandboxControlResultEvidence(manifest, request.id, execution.result);
686
909
  execution.resultEvidenceWritten = true;
687
910
  execution.settled = true;
911
+ appendBrokerAudit(manifest, 'executor-result-evidence-written', {
912
+ ...requestAuditFields(manifest, manifestPath, request),
913
+ resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
914
+ ...resultAuditFields(execution.result)
915
+ });
688
916
  }
689
917
  catch (error) {
690
918
  execution.failure = error;
919
+ appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
920
+ ...requestAuditFields(manifest, manifestPath, request),
921
+ errorType: error instanceof Error ? error.name : typeof error,
922
+ });
691
923
  }
692
- }, (error) => { execution.failure = error; execution.settled = true; });
924
+ }, (error) => {
925
+ execution.failure = error;
926
+ execution.settled = true;
927
+ appendBrokerAudit(manifest, 'executor-failed', {
928
+ ...requestAuditFields(manifest, manifestPath, request),
929
+ childPid: prepared.execution.child.pid,
930
+ childStartTime: prepared.execution.child.startTime,
931
+ errorType: error instanceof Error ? error.name : typeof error,
932
+ });
933
+ });
693
934
  if (!brokerOwns()) {
694
935
  prepared.terminate(false);
695
936
  active = null;
@@ -705,6 +946,11 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
705
946
  writeAcceptedResponse(manifest, {
706
947
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
707
948
  });
949
+ appendBrokerAudit(manifest, 'request-accepted', {
950
+ ...requestAuditFields(manifest, manifestPath, request),
951
+ acceptedPath: acceptedResponsePath(manifest, id),
952
+ executionPhase: 'prepared'
953
+ });
708
954
  if (!brokerOwns()) {
709
955
  prepared.terminate(false);
710
956
  active = null;
@@ -712,9 +958,19 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
712
958
  break;
713
959
  }
714
960
  try {
961
+ appendBrokerAudit(manifest, 'executor-start', {
962
+ ...requestAuditFields(manifest, manifestPath, request),
963
+ childPid: prepared.execution.child.pid,
964
+ childStartTime: prepared.execution.child.startTime,
965
+ executionPath: executionPath(manifest, request.id)
966
+ });
715
967
  prepared.start(brokerOwns);
716
968
  }
717
969
  catch (error) {
970
+ appendBrokerAudit(manifest, 'executor-start-failed', {
971
+ ...requestAuditFields(manifest, manifestPath, request),
972
+ errorType: error instanceof Error ? error.name : typeof error,
973
+ });
718
974
  const owned = brokerOwns();
719
975
  prepared.terminate(owned);
720
976
  if (!owned || !brokerOwns()) {
@@ -736,7 +992,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
736
992
  retiring = true;
737
993
  break;
738
994
  }
739
- appendSandboxControlAudit(manifest, 'executor-gate-failed', { requestId: id });
995
+ appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
740
996
  continue;
741
997
  }
742
998
  }
@@ -745,6 +1001,16 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
745
1001
  retiring = true;
746
1002
  break;
747
1003
  }
1004
+ const detail = controlError(error);
1005
+ appendBrokerAudit(manifest, 'request-rejected', {
1006
+ requestId: id,
1007
+ requestFamily: validatedRequest?.family ?? null,
1008
+ sandboxTaskId: manifest.taskId,
1009
+ requestPath: claimed,
1010
+ errorCode: detail.code,
1011
+ errorRetryable: detail.retryable,
1012
+ errorType: error instanceof Error ? error.name : typeof error,
1013
+ });
748
1014
  writeSandboxControlResponse(manifest, rejected(id, error));
749
1015
  if (!brokerOwns()) {
750
1016
  retiring = true;
@@ -775,7 +1041,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
775
1041
  }
776
1042
  }
777
1043
  if (owned && active.result && active.resultEvidenceWritten) {
778
- if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
1044
+ if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
779
1045
  if (brokerOwns()) {
780
1046
  removeAcceptedResponse(manifest, active.request.id);
781
1047
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -787,7 +1053,11 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
787
1053
  const terminationConfirmed = active.prepared.terminate(owned);
788
1054
  if (owned && brokerOwns() && active.request.family === 'task-finalization') {
789
1055
  const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
790
- if (recovered.status === 'matched' && recovered.response && writeSandboxControlResponse(manifest, recovered.response)) {
1056
+ const view = recovered.status === 'matched'
1057
+ ? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
1058
+ : null;
1059
+ if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
1060
+ && writeSandboxControlResponse(manifest, recovered.response)) {
791
1061
  if (terminationConfirmed && brokerOwns()) {
792
1062
  removeAcceptedResponse(manifest, active.request.id);
793
1063
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -5,6 +5,7 @@ import { createHash, randomUUID } from 'node:crypto';
5
5
  import { buildProcessTreeStopCommand } from '../../server/process-control.js';
6
6
  import { getProcessIdentityState } from '../../server/process-state.js';
7
7
  import { parseSandboxControlResultEvidence, parseSandboxControlPayload, parseSandboxControlReservation, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_LOGICAL_RECORDS, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES, SANDBOX_CONTROL_RESERVATION_BYTES } from './protocol.js';
8
+ import { parseSandboxTaskView, taskViewForManifest } from './task-view.js';
8
9
  export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
9
10
  export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
10
11
  function assertRegularFile(filePath) {
@@ -233,16 +234,16 @@ export function sandboxControlGenerationUsage(manifest) {
233
234
  export function statusPath(manifest) {
234
235
  return path.join(manifest.publicStatusDir, 'status.json');
235
236
  }
236
- export function writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, now = Date.now()) {
237
+ export function writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, now = Date.now(), taskView = taskViewForManifest(manifest)) {
237
238
  const status = {
238
- version: 2, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now
239
+ version: 3, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now, taskView
239
240
  };
240
241
  atomicWriteJson(statusPath(manifest), status, 0o600, false);
241
242
  return status;
242
243
  }
243
244
  export function parseSandboxControlStatus(value) {
244
245
  const status = value;
245
- if (!status || status.version !== 2 || typeof status.generation !== 'string'
246
+ if (!status || status.version !== 3 || typeof status.generation !== 'string'
246
247
  || !status.broker || !Number.isSafeInteger(status.broker.pid) || typeof status.broker.startTime !== 'number'
247
248
  || !Number.isSafeInteger(status.broker.startTime) || typeof status.broker.brokerId !== 'string'
248
249
  || status.broker.brokerId.length === 0
@@ -251,6 +252,7 @@ export function parseSandboxControlStatus(value) {
251
252
  || (status.activeRequestId !== null && typeof status.activeRequestId !== 'string')
252
253
  || !Number.isSafeInteger(status.updatedAt))
253
254
  throw new Error('SANDBOX_CONTROL_STATUS_INVALID');
255
+ parseSandboxTaskView(status.taskView);
254
256
  return status;
255
257
  }
256
258
  export function readSandboxControlStatus(directory) {