@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
@@ -16,6 +16,7 @@ import { fetchSandboxRows, selectSandboxContainer } from './list-running.ts';
16
16
  import { resolveTools, toolConfigDirCandidates } from '../tools.ts';
17
17
  import { detectEngine } from '../engine.ts';
18
18
  import { resolveSandboxTarget } from '../workspace-identity.ts';
19
+ import { readSandboxControlStatusForRow } from './control-status.ts';
19
20
 
20
21
  const USAGE = `Usage: ai sandbox show <branch | TASK-id | N>
21
22
 
@@ -93,6 +94,21 @@ export function show(args: string[] = []): void {
93
94
  process.stdout.write(` Mode: ${container?.workspaceMode ?? 'legacy-invalid'}\n`);
94
95
  process.stdout.write(` Task: ${container?.taskId ?? '-'}\n`);
95
96
 
97
+ p.log.step('Control status');
98
+ const controlStatus = container
99
+ ? readSandboxControlStatusForRow(config, container)
100
+ : null;
101
+ if (!controlStatus) {
102
+ p.log.warn(' Control status unavailable');
103
+ } else {
104
+ process.stdout.write(` Health: ${controlStatus.state}\n`);
105
+ process.stdout.write(` Task view: ${controlStatus.taskView.state}\n`);
106
+ process.stdout.write(` Task source: ${controlStatus.taskView.observedSource ?? '-'}\n`);
107
+ if (controlStatus.taskView.reasonCode) {
108
+ process.stdout.write(` Task view reason: ${controlStatus.taskView.reasonCode}\n`);
109
+ }
110
+ }
111
+
96
112
  for (const tool of detail.toolStates) {
97
113
  p.log.step(`${tool.name} state`);
98
114
  if (tool.entries.length === 0) {
@@ -22,9 +22,66 @@ import type { ProcessIdentity } from '../../server/process-state.ts';
22
22
  import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
23
23
  import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts';
24
24
  import type { TaskCreateCandidateV1 } from '../../task/create.ts';
25
+ import { accessSandboxTaskView, taskViewFromStatus, type TaskViewAccessEffect } from './task-view.ts';
25
26
 
26
27
  const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
27
28
 
29
+ type SandboxControlEnvironmentClassification = Readonly<{
30
+ kind: 'direct' | 'controlled' | 'invalid';
31
+ mode?: 'task-bound' | 'branch-only';
32
+ code?: 'TASK_CONTROL_TRANSPORT_INVALID';
33
+ message?: string;
34
+ }>;
35
+
36
+ export function classifySandboxControlEnvironment(
37
+ env: NodeJS.ProcessEnv = process.env
38
+ ): SandboxControlEnvironmentClassification {
39
+ const controlKeys = [
40
+ 'AGENT_INFRA_CONTROL_TOKEN',
41
+ 'AGENT_INFRA_CONTROL_GENERATION',
42
+ 'AGENT_INFRA_CONTROL_DIR',
43
+ 'AGENT_INFRA_CONTROL_STATUS_DIR'
44
+ ] as const;
45
+ const hasControlMarker = controlKeys.some((key) => Boolean(env[key]));
46
+ const hasTaskIdentity = Boolean(env.AGENT_INFRA_TASK_ID);
47
+ const hasRuntime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
48
+ const hasExecutorMarker = Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
49
+ const hasControllerBinding = Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING);
50
+ const hasAnyMarker = hasControlMarker || hasTaskIdentity || hasRuntime || hasExecutorMarker || hasControllerBinding;
51
+ const hasCompleteControl = controlKeys.every((key) => Boolean(env[key]));
52
+
53
+ if (!hasAnyMarker) return { kind: 'direct' };
54
+ if (hasExecutorMarker) {
55
+ return {
56
+ kind: 'invalid',
57
+ code: 'TASK_CONTROL_TRANSPORT_INVALID',
58
+ message: 'executor context is only valid for sandbox-control execute'
59
+ };
60
+ }
61
+ if (hasControllerBinding) {
62
+ return {
63
+ kind: 'invalid',
64
+ code: 'TASK_CONTROL_TRANSPORT_INVALID',
65
+ message: 'sandbox client control configuration is incomplete or conflicting'
66
+ };
67
+ }
68
+ if (!hasCompleteControl) {
69
+ return {
70
+ kind: 'invalid',
71
+ code: 'TASK_CONTROL_TRANSPORT_INVALID',
72
+ message: 'sandbox client control configuration is incomplete or conflicting'
73
+ };
74
+ }
75
+ if (hasTaskIdentity !== hasRuntime) {
76
+ return {
77
+ kind: 'invalid',
78
+ code: 'TASK_CONTROL_TRANSPORT_INVALID',
79
+ message: 'sandbox task identity and runtime binding must be provided together'
80
+ };
81
+ }
82
+ return { kind: 'controlled', mode: hasTaskIdentity ? 'task-bound' : 'branch-only' };
83
+ }
84
+
28
85
  export class SandboxControlClientError extends Error {
29
86
  readonly detail: SandboxControlError;
30
87
  readonly accepted: boolean;
@@ -52,7 +109,19 @@ function clientError(
52
109
  throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
53
110
  }
54
111
 
55
- function preflight(statusDir: string, generation: string, now = Date.now()): void {
112
+ function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
113
+ return Boolean(env.AGENT_INFRA_TASK_ID)
114
+ && Boolean(env.AGENT_INFRA_CONTROL_TOKEN || env.AGENT_INFRA_CONTROL_GENERATION
115
+ || env.AGENT_INFRA_CONTROL_STATUS_DIR || env.AGENT_INFRA_RUNTIME_DIR || env.AGENT_INFRA_CONTROL_DIR);
116
+ }
117
+
118
+ function preflight(
119
+ statusDir: string,
120
+ generation: string,
121
+ taskViewEffect: TaskViewAccessEffect | null = null,
122
+ now = Date.now(),
123
+ env: NodeJS.ProcessEnv = process.env
124
+ ): void {
56
125
  let status;
57
126
  try {
58
127
  status = readSandboxControlStatus(statusDir);
@@ -71,9 +140,29 @@ function preflight(statusDir: string, generation: string, now = Date.now()): voi
71
140
  clientError(code, 'broker is parked until the host restores a valid binding', true);
72
141
  }
73
142
  if (status.state !== 'healthy') clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', 'broker is not ready', true);
143
+ if (taskViewEffect && hasTaskBoundMarker(env)) {
144
+ const view = taskViewFromStatus(status);
145
+ if (view.taskId !== env.AGENT_INFRA_TASK_ID) {
146
+ clientError('SANDBOX_TASK_VIEW_TASK_ID_MISMATCH', 'task view task id does not match the sandbox task marker', false);
147
+ }
148
+ const access = accessSandboxTaskView(view, taskViewEffect);
149
+ if (!access.allowed) {
150
+ clientError(
151
+ access.reasonCode ?? 'SANDBOX_TASK_VIEW_DENIED',
152
+ access.message ?? 'task view denied',
153
+ false
154
+ );
155
+ }
156
+ }
74
157
  }
75
158
 
76
- function parseResponse(raw: string, id: string): SandboxControlResponse {
159
+ function taskViewEffectForRequest(request: SandboxControlRequest): TaskViewAccessEffect | null {
160
+ if (request.family === 'task-lifecycle' || request.family === 'task-finalization') return 'progress';
161
+ if (request.family === 'task-orchestration') return request.args[1] === 'status' ? 'diagnostic' : 'progress';
162
+ return null;
163
+ }
164
+
165
+ function parseResponse(raw: string, id: string, accepted = false): SandboxControlResponse {
77
166
  const response = JSON.parse(raw) as SandboxControlResponse;
78
167
  if (response.version !== 2 || response.id !== id
79
168
  || !['completed', 'rejected'].includes(response.phase)
@@ -81,13 +170,13 @@ function parseResponse(raw: string, id: string): SandboxControlResponse {
81
170
  || (response.outputState !== undefined && response.outputState !== 'available' && response.outputState !== 'unavailable')
82
171
  || (response.outputState === 'available' && !response.payload)
83
172
  || (response.outputState === 'unavailable' && response.payload !== null && response.payload !== undefined)) {
84
- clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false, false, id);
173
+ clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false, accepted, id);
85
174
  }
86
175
  return response;
87
176
  }
88
177
 
89
- function readPublishedResponse(raw: string, id: string, channelDir: string): SandboxControlResponse {
90
- const response = parseResponse(raw, id);
178
+ function readPublishedResponse(raw: string, id: string, channelDir: string, accepted = false): SandboxControlResponse {
179
+ const response = parseResponse(raw, id, accepted);
91
180
  if (response.outputState !== 'available') return response;
92
181
  const filePath = path.join(channelDir, 'responses', `${id}.payload.json`);
93
182
  let payload;
@@ -122,7 +211,8 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
122
211
  }>): SandboxControlResponse {
123
212
  const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
124
213
  const statusDir = params.statusDir ?? process.env.AGENT_INFRA_CONTROL_STATUS_DIR ?? '/run/agent-infra/control-status';
125
- preflight(statusDir, request.generation);
214
+ const taskViewEffect = taskViewEffectForRequest(request);
215
+ preflight(statusDir, request.generation, taskViewEffect);
126
216
  const encoded = `${JSON.stringify(request)}\n`;
127
217
  if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
128
218
  clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false, false, request.id);
@@ -154,7 +244,9 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
154
244
  }
155
245
  let response: SandboxControlResponse;
156
246
  try {
157
- response = readPublishedResponse(raw, request.id, channelDir);
247
+ // The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
248
+ accepted = true;
249
+ response = readPublishedResponse(raw, request.id, channelDir, accepted);
158
250
  malformedResponseRaw = null;
159
251
  } catch (error) {
160
252
  if (!(error instanceof SyntaxError)) throw error;
@@ -176,7 +268,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
176
268
  }
177
269
  if (!accepted) {
178
270
  try {
179
- preflight(statusDir, request.generation);
271
+ preflight(statusDir, request.generation, taskViewEffect);
180
272
  } catch (error) {
181
273
  if (cancelPendingRequest(requestPath)) throw error;
182
274
  clientError(
@@ -236,7 +328,9 @@ export function recoverSandboxControl(requestId: string, params: Readonly<{
236
328
  }
237
329
  let response: SandboxControlResponse;
238
330
  try {
239
- response = readPublishedResponse(raw, requestId, channelDir);
331
+ // The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
332
+ accepted = true;
333
+ response = readPublishedResponse(raw, requestId, channelDir, accepted);
240
334
  malformedResponseRaw = null;
241
335
  } catch (error) {
242
336
  if (!(error instanceof SyntaxError)) throw error;
@@ -0,0 +1,98 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveTaskWorkspace } from '../task-resolver.ts';
4
+ import { assertGitWorktreeBinding } from '../../git/worktree-identity.ts';
5
+ import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
6
+ import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
7
+ import { inspectSandboxControlContainer } from './container-identity.ts';
8
+ import type { SandboxControlManifest } from './protocol.ts';
9
+ import { atomicWriteJson, readSandboxControlStatus } from './state.ts';
10
+ import { taskViewForManifest, type SandboxTaskView } from './task-view.ts';
11
+
12
+ type ReentryEvidence = Readonly<{
13
+ taskId: string;
14
+ generation: string;
15
+ containerId: string;
16
+ branch: string;
17
+ worktreeRoot: string;
18
+ source: string;
19
+ sourceDevice: string;
20
+ sourceInode: string;
21
+ receipt: NonNullable<SandboxTaskView['receipt']>;
22
+ }>;
23
+
24
+ function evidencePath(manifest: SandboxControlManifest): string {
25
+ return path.join(path.dirname(manifest.processingDir), 'completed-reentry.json');
26
+ }
27
+
28
+ function invalid(): Error {
29
+ return new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
30
+ }
31
+
32
+ export async function prepareCompletedReentry(
33
+ manifest: SandboxControlManifest,
34
+ inspect: typeof inspectSandboxControlContainer = inspectSandboxControlContainer
35
+ ): Promise<ReentryEvidence> {
36
+ if (manifest.mode !== 'task-bound' || !manifest.taskId) throw invalid();
37
+ assertGitWorktreeBinding(manifest.repoRoot, manifest.worktreeRoot, manifest.branch);
38
+ const container = await inspect(manifest);
39
+ if (container.state !== 'found' || container.id !== manifest.containerIdentity.id) throw invalid();
40
+ const task = resolveTaskWorkspace(manifest.taskId, manifest.repoRoot);
41
+ const metadata = parseTypedTaskFrontmatter(fs.readFileSync(task.taskMd, 'utf8'));
42
+ if (task.state !== 'completed' || task.branch !== manifest.branch
43
+ || metadata.id !== manifest.taskId || metadata.status !== 'completed') throw invalid();
44
+ const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
45
+ if (!receipt || receipt.taskId !== manifest.taskId) throw invalid();
46
+ const view = taskViewForManifest({ ...manifest, receipt });
47
+ if (view.state !== 'current' || view.observedSource !== 'completed' || !view.receipt) throw invalid();
48
+ const status = readSandboxControlStatus(manifest.publicStatusDir);
49
+ const previous = status.taskView;
50
+ if (status.generation !== manifest.generation || status.activeRequestId !== null
51
+ || previous.taskId !== manifest.taskId || !previous.receipt
52
+ || previous.receipt.receiptId !== view.receipt.receiptId
53
+ || previous.receipt.generation !== view.receipt.generation
54
+ || previous.receipt.requestId !== view.receipt.requestId
55
+ || previous.receipt.revision > view.receipt.revision) throw invalid();
56
+ const completedRoot = fs.realpathSync.native(path.join(manifest.repoRoot, '.agents', 'workspace', 'completed'));
57
+ const source = fs.realpathSync.native(path.join(completedRoot, manifest.taskId));
58
+ if (source !== path.join(completedRoot, manifest.taskId)) throw invalid();
59
+ const stat = fs.statSync(source, { bigint: true });
60
+ return {
61
+ taskId: manifest.taskId, generation: manifest.generation,
62
+ containerId: container.id, branch: manifest.branch,
63
+ worktreeRoot: fs.realpathSync.native(manifest.worktreeRoot),
64
+ source, sourceDevice: String(stat.dev), sourceInode: String(stat.ino), receipt: view.receipt
65
+ };
66
+ }
67
+
68
+ export function publishCompletedReentry(manifest: SandboxControlManifest, evidence: ReentryEvidence): void {
69
+ atomicWriteJson(evidencePath(manifest), evidence);
70
+ }
71
+
72
+ export async function completedReentryView(
73
+ manifest: SandboxControlManifest,
74
+ inspect: typeof inspectSandboxControlContainer = inspectSandboxControlContainer
75
+ ): Promise<SandboxTaskView | null> {
76
+ const file = evidencePath(manifest);
77
+ if (!fs.existsSync(file)) return null;
78
+ const evidence: unknown = JSON.parse(fs.readFileSync(file, 'utf8'));
79
+ const current = await prepareCompletedReentry(manifest, inspect);
80
+ if (JSON.stringify(evidence) !== JSON.stringify(current)) throw invalid();
81
+ return {
82
+ state: 'current', taskId: current.taskId, observedSource: 'completed',
83
+ receipt: current.receipt, reasonCode: null
84
+ };
85
+ }
86
+
87
+ export async function waitForCompletedReentry(manifest: SandboxControlManifest, evidence: ReentryEvidence): Promise<void> {
88
+ const deadline = Date.now() + 10_000;
89
+ while (Date.now() < deadline) {
90
+ const status = readSandboxControlStatus(manifest.publicStatusDir);
91
+ if (status.generation !== evidence.generation) throw invalid();
92
+ if (status.taskView.state === 'current' && status.taskView.observedSource === 'completed'
93
+ && status.taskView.taskId === evidence.taskId
94
+ && JSON.stringify(status.taskView.receipt) === JSON.stringify(evidence.receipt)) return;
95
+ await new Promise((resolve) => setTimeout(resolve, 100));
96
+ }
97
+ throw new Error('SANDBOX_COMPLETED_REENTRY_STATUS_TIMEOUT');
98
+ }
@@ -1,5 +1,7 @@
1
1
  import type { SandboxControlManifest } from './protocol.ts';
2
- import { commandForEngine, runEngine, runProbe } from '../shell.ts';
2
+ import { runProbe } from '../shell.ts';
3
+ import { captureSandboxAuthority, isSandboxAuthorityEvidence, verifySandboxAuthority } from '../engines/authority.ts';
4
+ import { discoverExactContainer } from '../engines/docker-exact-discovery.ts';
3
5
 
4
6
  export type ContainerObservation =
5
7
  | Readonly<{ state: 'found'; id: string; running: boolean; labels: Readonly<Record<string, string>> }>
@@ -12,20 +14,13 @@ export type ContainerInspection = Readonly<{
12
14
  labels?: unknown;
13
15
  }>;
14
16
 
15
- function isAuthoritativeNotFound(error: unknown): boolean {
16
- const message = error instanceof Error ? error.message : String(error);
17
- return /(?:no such (?:container|object)|container .* not found|cannot find container)/i.test(message);
18
- }
19
-
20
17
  export function classifySandboxContainerInspection(
21
18
  expected: SandboxControlManifest['containerIdentity'],
22
19
  inspection: ContainerInspection | { notFound: true } | { error: unknown }
23
20
  ): ContainerObservation {
24
21
  if ('notFound' in inspection) return { state: 'absent', id: expected.id };
25
22
  if ('error' in inspection) {
26
- return isAuthoritativeNotFound(inspection.error)
27
- ? { state: 'absent', id: expected.id }
28
- : { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
23
+ return { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
29
24
  }
30
25
  const response = inspection as ContainerInspection;
31
26
  if (typeof response.id !== 'string' || response.id !== expected.id
@@ -49,44 +44,33 @@ export function classifySandboxContainerInspection(
49
44
 
50
45
  export async function inspectSandboxControlContainer(
51
46
  manifest: SandboxControlManifest,
52
- options: { run?: typeof runEngine; probe?: typeof runProbe; timeoutMs?: number } = {}
47
+ options: { probe?: typeof runProbe; timeoutMs?: number } = {}
53
48
  ): Promise<ContainerObservation> {
49
+ if (!isSandboxAuthorityEvidence(manifest.authorityEvidence)) {
50
+ return { state: 'unknown', reason: 'SANDBOX_AUTHORITY_EVIDENCE_MISSING' };
51
+ }
54
52
  try {
55
- const args = ['inspect', '--format', '{{json .}}', manifest.containerIdentity.id];
56
- let raw: string;
57
- if (options.run) {
58
- raw = options.run(manifest.engine, 'docker', args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
59
- } else {
60
- const command = commandForEngine(manifest.engine, 'docker', args);
61
- const result = (options.probe ?? runProbe)(
62
- command.cmd,
63
- command.args,
64
- options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs }
65
- );
66
- if (result.status !== 0) {
67
- const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8') ?? '';
68
- return classifySandboxContainerInspection(manifest.containerIdentity, {
69
- error: new Error(stderr || `container inspection exited with ${result.status ?? 'unknown'}`)
70
- });
71
- }
72
- raw = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8') ?? '';
53
+ const authority = captureSandboxAuthority(manifest.engine, {
54
+ probe: options.probe,
55
+ timeoutMs: options.timeoutMs,
56
+ lockDomain: manifest.authorityEvidence.lockDomain,
57
+ route: manifest.authorityEvidence
58
+ });
59
+ const authorityState = verifySandboxAuthority(manifest.authorityEvidence, authority);
60
+ if (authorityState.state !== 'verified') {
61
+ return { state: 'unknown', reason: authorityState.reason ?? 'SANDBOX_AUTHORITY_UNAVAILABLE' };
73
62
  }
74
- const parsed = JSON.parse(raw) as {
75
- Id?: unknown;
76
- State?: { Running?: unknown };
77
- Config?: { Labels?: unknown };
78
- } | Array<{
79
- Id?: unknown;
80
- State?: { Running?: unknown };
81
- Config?: { Labels?: unknown };
82
- }>;
83
- const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
84
- if (!value || Array.isArray(value)) return { state: 'unknown', reason: 'container inspection response is incomplete' };
85
- return classifySandboxContainerInspection(manifest.containerIdentity, {
86
- id: value.Id,
87
- running: value.State?.Running,
88
- labels: value.Config?.Labels
63
+ const discovered = discoverExactContainer(manifest.engine, manifest.containerIdentity.id, {
64
+ probe: options.probe,
65
+ timeoutMs: options.timeoutMs,
66
+ authority: manifest.authorityEvidence
89
67
  });
68
+ if (discovered.state === 'absent') return { state: 'absent', id: discovered.id };
69
+ if (discovered.state === 'conflict') {
70
+ return { state: 'unknown', reason: `SANDBOX_CONTROL_CONTAINER_CONFLICT: ${discovered.reason}` };
71
+ }
72
+ if (discovered.state === 'unknown') return discovered;
73
+ return classifySandboxContainerInspection(manifest.containerIdentity, discovered);
90
74
  } catch (error) {
91
75
  return classifySandboxContainerInspection(manifest.containerIdentity, { error });
92
76
  }