@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.ts';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { createHash, randomUUID } from 'node:crypto';
@@ -34,7 +35,8 @@ import {
34
35
  writeSandboxControlReservation,
35
36
  writeSandboxControlResultEvidence,
36
37
  terminateSandboxControlExecution,
37
- writeSandboxControlStatus
38
+ writeSandboxControlStatus,
39
+ readSandboxControlStatus
38
40
  } from './state.ts';
39
41
  import { validateSandboxControlRequest } from './protocol.ts';
40
42
  import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
@@ -48,6 +50,13 @@ import type { BrokerOwner } from './lifecycle.ts';
48
50
  import { nextSandboxControlBackoff } from './timing.ts';
49
51
  import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
50
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';
51
60
 
52
61
  type ActiveExecution = {
53
62
  request: SandboxControlRequest;
@@ -58,6 +67,67 @@ type ActiveExecution = {
58
67
  settled: boolean;
59
68
  };
60
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
+
61
131
  function assertRealDirectory(directory: string, parent?: string): void {
62
132
  const stat = fs.lstatSync(directory);
63
133
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
@@ -171,6 +241,36 @@ function finalizationRecoveryResponse(
171
241
  };
172
242
  }
173
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
+
174
274
  function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
175
275
  return {
176
276
  version: payload.version,
@@ -201,17 +301,21 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
201
301
  }
202
302
 
203
303
  function genericRecoveryResponse(
204
- requestId: string,
205
- evidence: ReturnType<typeof readSandboxControlResultEvidence>,
206
- payload: ReturnType<typeof readSandboxControlPayload> | null
304
+ request: SandboxControlRequest,
305
+ exitCode: number,
306
+ payload: ReturnType<typeof readSandboxControlPayload> | null,
307
+ cause: 'publish' | 'recovery' = 'recovery'
207
308
  ): SandboxControlResponse {
309
+ const outputUnavailable = request.family === 'task-create' && !payload;
208
310
  return {
209
311
  version: 2,
210
- id: requestId,
312
+ id: request.id,
211
313
  phase: 'completed',
212
- exitCode: evidence.exitCode,
213
- stdout: '',
214
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
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',
215
319
  error: null,
216
320
  outputState: payload ? 'available' : 'unavailable',
217
321
  payload: payload ? payloadReference(payload) : null
@@ -228,7 +332,7 @@ function recoveryResponse(
228
332
  const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
229
333
  return finalization.status === 'matched' ? finalization.response ?? null : null;
230
334
  }
231
- return genericRecoveryResponse(request.id, evidence, payload);
335
+ return genericRecoveryResponse(request, evidence.exitCode, payload);
232
336
  }
233
337
 
234
338
  function terminalMatchesEvidence(
@@ -267,8 +371,10 @@ function terminalMatchesEvidence(
267
371
  };
268
372
  }
269
373
  if (response.outputState === 'unavailable') {
270
- const expected = genericRecoveryResponse(request.id, evidence, null);
271
- return { valid: JSON.stringify(response) === JSON.stringify(expected), payloadReferenced: false };
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 };
272
378
  }
273
379
  return {
274
380
  valid: response.payload === undefined
@@ -282,6 +388,7 @@ function publishExecutionResult(
282
388
  manifest: SandboxControlManifest,
283
389
  request: SandboxControlRequest,
284
390
  result: SandboxControlExecutionResult,
391
+ broker: BrokerOwner,
285
392
  brokerOwns: () => boolean
286
393
  ): boolean {
287
394
  const normalized = sanitizeSandboxControlResult(manifest, result);
@@ -299,7 +406,7 @@ function publishExecutionResult(
299
406
  if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
300
407
  terminal = inline;
301
408
  }
302
- let payload = null;
409
+ let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
303
410
  if (!terminal) {
304
411
  try {
305
412
  payload = createSandboxControlPayload(manifest, request.id, normalized);
@@ -312,20 +419,14 @@ function publishExecutionResult(
312
419
  } catch {
313
420
  payload = null;
314
421
  }
315
- terminal = {
316
- version: 2,
317
- id: request.id,
318
- phase: 'completed',
319
- exitCode: normalized.exitCode,
320
- stdout: '',
321
- stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
322
- error: null,
323
- outputState: payload ? 'available' : 'unavailable',
324
- payload: payload ? payloadReference(payload) : null
325
- };
422
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
326
423
  }
327
424
  }
328
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
+ }
329
430
  return writeSandboxControlResponse(manifest, terminal);
330
431
  }
331
432
 
@@ -391,7 +492,7 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
391
492
  }
392
493
  }
393
494
 
394
- function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => boolean): boolean {
495
+ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
395
496
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
396
497
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
397
498
  if (!brokerOwns()) return false;
@@ -459,7 +560,7 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
459
560
  || response.payload.stdoutSha256 !== payload.stdoutSha256
460
561
  || response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
461
562
  payloadReferenced = true;
462
- } else if (terminal && (response.outputState !== undefined
563
+ } else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
463
564
  || response.payload !== undefined && response.payload !== null)) {
464
565
  throw new Error('payload state invalid');
465
566
  }
@@ -473,12 +574,20 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
473
574
  const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
474
575
  if (!reconciliation.valid) continue;
475
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;
580
+ }
476
581
  }
477
582
  if (!terminal) {
478
583
  if (!brokerOwns()) return false;
479
584
  if (!resultEvidence || !request || payloadInvalid) continue;
480
585
  const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
481
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
+ }
482
591
  writeSandboxControlResponse(manifest, recovered);
483
592
  payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
484
593
  }
@@ -499,7 +608,13 @@ function delay(ms: number): Promise<void> {
499
608
  }
500
609
 
501
610
  export function sandboxControlSafeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
502
- 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
+ }));
503
618
  }
504
619
 
505
620
  export async function serveSandboxControl(
@@ -562,6 +677,25 @@ export async function serveSandboxControl(
562
677
  releaseStartup();
563
678
  }
564
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
+ }
565
699
  let lastState = '';
566
700
  let lastStatusAt = 0;
567
701
  let nextBindingCheckAt = 0;
@@ -572,10 +706,43 @@ export async function serveSandboxControl(
572
706
  let bindingBackoffMs: number = timing.parkedBindingInitialMs;
573
707
  try {
574
708
  if (!brokerOwns()) return;
575
- 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);
576
721
  if (!brokerOwns()) return;
577
- appendSandboxControlAudit(manifest, 'broker-start', { pid: broker.pid });
578
- 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
+ }
579
746
  while (!signal.aborted) {
580
747
  let settledExecution: ActiveExecution | null = null;
581
748
  if (!brokerOwns()) break;
@@ -647,7 +814,7 @@ export async function serveSandboxControl(
647
814
  if (!brokerOwns()) break;
648
815
  if (cleanupStaleSandboxControlLease(manifest)) {
649
816
  if (!brokerOwns()) break;
650
- appendSandboxControlAudit(manifest, 'lease-stale-cleanup');
817
+ appendBrokerAudit(manifest, 'lease-stale-cleanup');
651
818
  }
652
819
  if (readActiveLease(manifest)) reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
653
820
  } catch {
@@ -667,25 +834,49 @@ export async function serveSandboxControl(
667
834
  }
668
835
  reasonCode ??= containerReasonCode;
669
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
+ }
670
847
  const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
671
848
  const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
672
849
  if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
673
- writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
850
+ writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
674
851
  lastStatusAt = now;
675
852
  }
676
853
  if (brokerOwns() && stateKey !== lastState) {
677
- appendSandboxControlAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
854
+ appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
678
855
  lastState = stateKey;
679
856
  }
680
857
  if (settledExecution) {
681
858
  if (!brokerOwns()) break;
682
859
  let terminalCommitted = false;
683
860
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
684
- terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
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
+ }
685
870
  } else {
686
871
  terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
687
872
  }
688
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
+ });
689
880
  if (!brokerOwns()) break;
690
881
  removeAcceptedResponse(manifest, settledExecution.request.id);
691
882
  fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
@@ -697,12 +888,19 @@ export async function serveSandboxControl(
697
888
  const id = name.slice(0, -5);
698
889
  const source = path.join(requestsDir, name);
699
890
  let claimed: string | null = null;
891
+ let validatedRequest: SandboxControlRequest | null = null;
700
892
  try {
701
893
  if (!brokerOwns()) {
702
894
  retiring = true;
703
895
  break;
704
896
  }
705
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
+ });
706
904
  if (!brokerOwns()) {
707
905
  retiring = true;
708
906
  break;
@@ -715,13 +913,44 @@ export async function serveSandboxControl(
715
913
  if (reasonCode) throw new Error(reasonCode);
716
914
  if (active) throw new Error('SANDBOX_CONTROL_BUSY');
717
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
+ });
718
921
  if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
719
922
  if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
923
+ appendBrokerAudit(manifest, 'request-gates-passed', {
924
+ ...requestAuditFields(manifest, manifestPath, request),
925
+ requestPath: claimed,
926
+ bindingChecked: true,
927
+ handoffLeaseChecked: true
928
+ });
720
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
+ });
721
941
  const prepared = await prepareExecution({
722
942
  manifest, manifestPath, request, requestPath: claimed,
723
943
  internalCliPath: options.internalCliPath ?? process.argv[1]!
724
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)
953
+ });
725
954
  const execution: ActiveExecution = {
726
955
  request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
727
956
  };
@@ -729,15 +958,39 @@ export async function serveSandboxControl(
729
958
  prepared.completion.then(
730
959
  (result) => {
731
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
+ });
732
967
  try {
733
968
  writeSandboxControlResultEvidence(manifest, request.id, execution.result);
734
969
  execution.resultEvidenceWritten = true;
735
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
+ });
736
976
  } catch (error) {
737
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
+ });
738
982
  }
739
983
  },
740
- (error) => { execution.failure = error; execution.settled = true; }
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
+ }
741
994
  );
742
995
  if (!brokerOwns()) {
743
996
  prepared.terminate(false);
@@ -754,6 +1007,11 @@ export async function serveSandboxControl(
754
1007
  writeAcceptedResponse(manifest, {
755
1008
  version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
756
1009
  });
1010
+ appendBrokerAudit(manifest, 'request-accepted', {
1011
+ ...requestAuditFields(manifest, manifestPath, request),
1012
+ acceptedPath: acceptedResponsePath(manifest, id),
1013
+ executionPhase: 'prepared'
1014
+ });
757
1015
  if (!brokerOwns()) {
758
1016
  prepared.terminate(false);
759
1017
  active = null;
@@ -761,8 +1019,18 @@ export async function serveSandboxControl(
761
1019
  break;
762
1020
  }
763
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
+ });
764
1028
  prepared.start(brokerOwns);
765
1029
  } catch (error) {
1030
+ appendBrokerAudit(manifest, 'executor-start-failed', {
1031
+ ...requestAuditFields(manifest, manifestPath, request),
1032
+ errorType: error instanceof Error ? error.name : typeof error,
1033
+ });
766
1034
  const owned = brokerOwns();
767
1035
  prepared.terminate(owned);
768
1036
  if (!owned || !brokerOwns()) {
@@ -784,7 +1052,7 @@ export async function serveSandboxControl(
784
1052
  retiring = true;
785
1053
  break;
786
1054
  }
787
- appendSandboxControlAudit(manifest, 'executor-gate-failed', { requestId: id });
1055
+ appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
788
1056
  continue;
789
1057
  }
790
1058
  } catch (error) {
@@ -792,6 +1060,16 @@ export async function serveSandboxControl(
792
1060
  retiring = true;
793
1061
  break;
794
1062
  }
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
+ });
795
1073
  writeSandboxControlResponse(manifest, rejected(id, error));
796
1074
  if (!brokerOwns()) {
797
1075
  retiring = true;
@@ -818,7 +1096,7 @@ export async function serveSandboxControl(
818
1096
  }
819
1097
  }
820
1098
  if (owned && active.result && active.resultEvidenceWritten) {
821
- if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
1099
+ if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
822
1100
  if (brokerOwns()) {
823
1101
  removeAcceptedResponse(manifest, active.request.id);
824
1102
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -830,7 +1108,11 @@ export async function serveSandboxControl(
830
1108
  const terminationConfirmed = active.prepared.terminate(owned);
831
1109
  if (owned && brokerOwns() && active.request.family === 'task-finalization') {
832
1110
  const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
833
- if (recovered.status === 'matched' && recovered.response && writeSandboxControlResponse(manifest, recovered.response)) {
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)) {
834
1116
  if (terminationConfirmed && brokerOwns()) {
835
1117
  removeAcceptedResponse(manifest, active.request.id);
836
1118
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -23,6 +23,7 @@ import {
23
23
  SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
24
24
  SANDBOX_CONTROL_RESERVATION_BYTES
25
25
  } from './protocol.ts';
26
+ import { parseSandboxTaskView, taskViewForManifest, type SandboxTaskView } from './task-view.ts';
26
27
 
27
28
  export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
28
29
  export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
@@ -296,10 +297,11 @@ export function writeSandboxControlStatus(
296
297
  state: SandboxControlStatus['state'],
297
298
  reasonCode: string | null,
298
299
  activeRequestId: string | null,
299
- now = Date.now()
300
+ now = Date.now(),
301
+ taskView: SandboxTaskView = taskViewForManifest(manifest)
300
302
  ): SandboxControlStatus {
301
303
  const status: SandboxControlStatus = {
302
- version: 2, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now
304
+ version: 3, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now, taskView
303
305
  };
304
306
  atomicWriteJson(statusPath(manifest), status, 0o600, false);
305
307
  return status;
@@ -307,7 +309,7 @@ export function writeSandboxControlStatus(
307
309
 
308
310
  export function parseSandboxControlStatus(value: unknown): SandboxControlStatus {
309
311
  const status = value as Partial<SandboxControlStatus> | null;
310
- if (!status || status.version !== 2 || typeof status.generation !== 'string'
312
+ if (!status || status.version !== 3 || typeof status.generation !== 'string'
311
313
  || !status.broker || !Number.isSafeInteger(status.broker.pid) || typeof status.broker.startTime !== 'number'
312
314
  || !Number.isSafeInteger(status.broker.startTime) || typeof status.broker.brokerId !== 'string'
313
315
  || status.broker.brokerId.length === 0
@@ -315,6 +317,7 @@ export function parseSandboxControlStatus(value: unknown): SandboxControlStatus
315
317
  || (status.reasonCode !== null && typeof status.reasonCode !== 'string')
316
318
  || (status.activeRequestId !== null && typeof status.activeRequestId !== 'string')
317
319
  || !Number.isSafeInteger(status.updatedAt)) throw new Error('SANDBOX_CONTROL_STATUS_INVALID');
320
+ parseSandboxTaskView(status.taskView);
318
321
  return status as SandboxControlStatus;
319
322
  }
320
323