@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
 
3
4
  import {
4
5
  accessSandboxTaskView,
@@ -13,12 +14,18 @@ import {
13
14
  PUBLIC_CLI_SELECTOR_ALIASES,
14
15
  internalRouteSelector
15
16
  } from './cli-route-inventory.ts';
17
+ import { readSandboxControlIdentitySentinel } from '../sandbox/control/identity-sentinel.ts';
16
18
 
17
19
  export type TaskOperationDispatcher = 'public' | 'internal';
18
20
  export type TaskOperationScope = 'task-bound' | 'non-task' | 'conditional';
19
21
  export type TaskOperationEffect = TaskViewAccessEffect;
20
22
  export type TaskRefSource = 'argv' | 'environment' | 'none' | 'delegated' | 'input';
21
23
 
24
+ export type SandboxControlTransportDecision = Readonly<{
25
+ kind: 'direct-host' | 'broker-client' | 'fail-closed';
26
+ reasonCode: string | null;
27
+ }>;
28
+
22
29
  export type TaskOperationDescriptor = Readonly<{
23
30
  dispatcher: TaskOperationDispatcher;
24
31
  command: string;
@@ -49,6 +56,10 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
49
56
  descriptor('internal', 'sandbox-control', 'execute', 'non-task', 'progress', 'none'),
50
57
  descriptor('internal', 'sandbox-control', 'recover', 'conditional', 'recovery', 'delegated'),
51
58
  descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
59
+ descriptor('internal', 'host-control', 'serve', 'non-task', 'progress', 'none'),
60
+ descriptor('internal', 'host-control', 'status', 'non-task', 'diagnostic', 'none'),
61
+ descriptor('internal', 'host-control', 'install', 'non-task', 'progress', 'none'),
62
+ descriptor('internal', 'host-control', 'uninstall', 'non-task', 'progress', 'none'),
52
63
  descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
53
64
  descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
54
65
  descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
@@ -145,7 +156,8 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
145
156
  descriptor('internal', 'task-snapshot', 'snapshot', 'conditional', 'diagnostic'),
146
157
  descriptor('internal', 'task-verify', 'event', 'task-bound', 'terminal-verdict'),
147
158
  descriptor('internal', 'task-validate', 'snapshot', 'conditional', 'diagnostic'),
148
- descriptor('internal', 'task-validate', 'inplace', 'task-bound', 'progress')
159
+ descriptor('internal', 'task-validate', 'inplace', 'task-bound', 'progress'),
160
+ descriptor('internal', 'manual-validation', 'transaction', 'task-bound', 'remote-write')
149
161
  ];
150
162
  }
151
163
 
@@ -207,12 +219,12 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
207
219
  ]);
208
220
 
209
221
  export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
210
- 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
222
+ 'task-create', 'task-qualification', 'sandbox-control', 'host-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
211
223
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
212
224
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
213
225
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
214
226
  'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
215
- 'task-finalization', 'task-override', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate'
227
+ 'task-finalization', 'task-override', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
216
228
  ]);
217
229
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
218
230
  'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
@@ -284,6 +296,35 @@ const TASK_CONTROL_MARKER_KEYS = [
284
296
  'AGENT_INFRA_CONTROL_STATUS_DIR'
285
297
  ] as const;
286
298
 
299
+ const TASK_CONTROL_CONFIG_KEYS = [
300
+ 'AGENT_INFRA_CONTROL_TOKEN',
301
+ 'AGENT_INFRA_CONTROL_GENERATION',
302
+ 'AGENT_INFRA_CONTROL_ROOT_ID',
303
+ 'AGENT_INFRA_CONTROL_DIR',
304
+ 'AGENT_INFRA_CONTROL_STATUS_DIR'
305
+ ] as const;
306
+ export const SANDBOX_CONTROL_STATUS_MOUNT = '/run/agent-infra/control-status';
307
+
308
+ type NativeDirectoryProbe = 'present' | 'absent' | 'unknown';
309
+
310
+ function nativeDirectoryProbe(candidate: string): NativeDirectoryProbe {
311
+ try {
312
+ // The fixed mount is a trust anchor. Do not use a mutable node:fs export:
313
+ // a preload must not turn a mounted sandbox into the direct-host path by
314
+ // replacing the ordinary filesystem probe.
315
+ const binding = (process as unknown as {
316
+ binding(name: string): { internalModuleStat(filePath: string): number }
317
+ }).binding('fs');
318
+ if (!Function.prototype.toString.call(binding.internalModuleStat).includes('[native code]')) return 'unknown';
319
+ const result = binding.internalModuleStat(candidate);
320
+ if (result === 1) return 'present';
321
+ if (result === -2) return 'absent';
322
+ return 'unknown';
323
+ } catch {
324
+ return 'unknown';
325
+ }
326
+ }
327
+
287
328
  type TaskMarkerState = 'none' | 'branch-only' | 'task-bound' | 'incomplete';
288
329
 
289
330
  function taskMarkerState(env: NodeJS.ProcessEnv): TaskMarkerState {
@@ -298,6 +339,73 @@ function taskMarkerState(env: NodeJS.ProcessEnv): TaskMarkerState {
298
339
  return 'incomplete';
299
340
  }
300
341
 
342
+ export function resolveSandboxControlTransport(
343
+ env: NodeJS.ProcessEnv = process.env,
344
+ options: Readonly<{ statusMountPath?: string }> = {}
345
+ ): SandboxControlTransportDecision {
346
+ const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
347
+ const fixedStatusDir = options.statusMountPath
348
+ ?? SANDBOX_CONTROL_STATUS_MOUNT;
349
+ const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
350
+ || Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
351
+ || Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
352
+ const fixedStatusProbe = !env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT && path.isAbsolute(fixedStatusDir)
353
+ ? nativeDirectoryProbe(fixedStatusDir) : 'absent';
354
+ if (fixedStatusProbe === 'unknown') {
355
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
356
+ }
357
+ const fixedStatusMounted = fixedStatusProbe === 'present';
358
+ const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
359
+ ? nativeDirectoryProbe(configuredStatusDir) : 'absent';
360
+ if (configuredStatusProbe === 'unknown') {
361
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
362
+ }
363
+ const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir) && configuredStatusProbe === 'present'
364
+ ? configuredStatusDir
365
+ : fixedStatusMounted ? fixedStatusDir : null;
366
+ const statusMounted = statusDir !== null;
367
+ const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
368
+ if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
369
+ return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
370
+ }
371
+ const taskBound = Boolean(env.AGENT_INFRA_TASK_ID);
372
+ const runtime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
373
+
374
+ if (statusMounted) {
375
+ let sentinel;
376
+ try {
377
+ sentinel = readSandboxControlIdentitySentinel(statusDir);
378
+ } catch (error) {
379
+ const message = error instanceof Error ? error.message : '';
380
+ return { kind: 'fail-closed', reasonCode: message.endsWith('MISSING')
381
+ ? 'SANDBOX_CONTROL_IDENTITY_MISSING'
382
+ : 'SANDBOX_CONTROL_IDENTITY_MALFORMED' };
383
+ }
384
+ if (sentinel.generation !== env.AGENT_INFRA_CONTROL_GENERATION) {
385
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH' };
386
+ }
387
+ if (env.AGENT_INFRA_CONTROL_ROOT_ID && sentinel.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
388
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH' };
389
+ }
390
+ if (sentinel.mode === 'task-bound'
391
+ && (sentinel.taskId !== env.AGENT_INFRA_TASK_ID || !runtime)) {
392
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
393
+ }
394
+ if (sentinel.mode === 'branch-only' && (taskBound || runtime)) {
395
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
396
+ }
397
+ if (!hasCompleteConfig) {
398
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
399
+ }
400
+ return { kind: 'broker-client', reasonCode: null };
401
+ }
402
+ if (!hasAnyMarker) return { kind: 'direct-host', reasonCode: null };
403
+ if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
404
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
405
+ }
406
+ return { kind: 'broker-client', reasonCode: null };
407
+ }
408
+
301
409
  export function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
302
410
  return taskMarkerState(env) === 'task-bound';
303
411
  }
@@ -1,8 +1,9 @@
1
+ import { parseReviewCommand } from '../task/review-command.ts';
1
2
  import { finalizeReviewSummary } from '../task/review-finalization.ts';
2
3
  import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.ts';
4
5
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
5
- import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
6
+ import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
6
7
 
7
8
  const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
8
9
 
@@ -23,56 +24,10 @@ async function taskReview(args: string[] = []): Promise<void> {
23
24
  process.stdout.write(USAGE);
24
25
  return;
25
26
  }
26
- if (args.length < 2) {
27
- failUsage('task ref and intent are required');
28
- return;
29
- }
30
- if (!internalHandlerRoute('task-review', 'finalize-summary', args[1] ?? '')) {
31
- failUsage(`unknown intent '${args[1]}'`);
32
- return;
33
- }
34
- let stage = '';
35
- let artifact = '';
36
- let dryRun = false;
37
- let orchestrated = false;
38
- let overrideTicket = '';
39
- let overrideTarget = '';
40
- let overrideScope = '';
41
- const seen = new Set<string>();
42
- for (let index = 2; index < args.length; index += 1) {
43
- const flag = args[index]!;
44
- if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
45
- failUsage(`unknown option '${flag}'`);
46
- return;
47
- }
48
- if (seen.has(flag)) {
49
- failUsage(`duplicate option '${flag}'`);
50
- return;
51
- }
52
- seen.add(flag);
53
- if (flag === '--dry-run') {
54
- dryRun = true;
55
- continue;
56
- }
57
- if (flag === '--orchestrated') {
58
- orchestrated = true;
59
- continue;
60
- }
61
- const value = args[++index];
62
- if (!value || value.startsWith('--')) {
63
- failUsage(`option '${flag}' requires a value`);
64
- return;
65
- }
66
- if (flag === '--stage') stage = value;
67
- else if (flag === '--artifact') artifact = value;
68
- else if (flag === '--override-ticket') overrideTicket = value;
69
- else if (flag === '--override-target') overrideTarget = value;
70
- else if (flag === '--override-scope') overrideScope = value;
71
- }
72
- if (!stage || !artifact) {
73
- failUsage("options '--stage' and '--artifact' are required");
74
- return;
75
- }
27
+ let request;
28
+ try { request = parseReviewCommand(args); }
29
+ catch (error) { failUsage(error instanceof Error ? error.message : String(error)); return; }
30
+ const { stage, artifact, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
76
31
  const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
77
32
  if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
78
33
  const resolved = resolveTaskRef(args[0]!);
@@ -24,9 +24,12 @@ import {
24
24
  } from '../sandbox/control/protocol.ts';
25
25
  import { getProcessStartTime } from '../server/process-state.ts';
26
26
  import { assertGitWorktreeBinding } from '../git/worktree-identity.ts';
27
+ import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.ts';
28
+ import type { ManualValidationCleanup } from '../task/manual-validation-evidence.ts';
29
+ import { writeJsonAtomic } from '../task/manual-validation-shared.ts';
27
30
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
28
31
 
29
- const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] -- <command> [args...]`;
32
+ const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] [--evidence-file <path>] -- <command> [args...]`;
30
33
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
31
34
  const RECOVERY_GRACE_MS = 30_000;
32
35
 
@@ -35,13 +38,14 @@ type ValidateOptions = {
35
38
  scope: 'snapshot' | 'inplace';
36
39
  timeoutMs: number;
37
40
  format: 'text' | 'json';
41
+ evidenceFile: string | null;
38
42
  command: string[];
39
43
  help: boolean;
40
44
  };
41
45
 
42
46
  export function parseValidateArgs(args: string[]): ValidateOptions {
43
47
  if (args.includes('--help') || args.includes('-h')) {
44
- return { target: '', scope: 'snapshot', timeoutMs: 300_000, format: 'text', command: [], help: true };
48
+ return { target: '', scope: 'snapshot', timeoutMs: 300_000, format: 'text', evidenceFile: null, command: [], help: true };
45
49
  }
46
50
  const separator = args.indexOf('--');
47
51
  if (separator < 0) throw new Error(`${USAGE}\nA literal -- must separate validation options from the command.`);
@@ -50,6 +54,7 @@ export function parseValidateArgs(args: string[]): ValidateOptions {
50
54
  let scope: ValidateOptions['scope'] = 'snapshot';
51
55
  let timeoutMs = 300_000;
52
56
  let format: ValidateOptions['format'] = 'text';
57
+ let evidenceFile: string | null = null;
53
58
  const positionals: string[] = [];
54
59
  for (let index = 0; index < optionArgs.length; index += 1) {
55
60
  const arg = optionArgs[index]!;
@@ -65,6 +70,10 @@ export function parseValidateArgs(args: string[]): ValidateOptions {
65
70
  const value = optionArgs[++index];
66
71
  if (value !== 'text' && value !== 'json') throw new Error('validate --format must be text or json');
67
72
  format = value;
73
+ } else if (arg === '--evidence-file') {
74
+ const value = optionArgs[++index];
75
+ if (!value || value.startsWith('-')) throw new Error('validate --evidence-file requires a path');
76
+ evidenceFile = value;
68
77
  } else if (arg.startsWith('-')) {
69
78
  throw new Error(`Unknown option: ${arg}`);
70
79
  } else {
@@ -72,7 +81,7 @@ export function parseValidateArgs(args: string[]): ValidateOptions {
72
81
  }
73
82
  }
74
83
  if (positionals.length !== 1 || command.length === 0) throw new Error(USAGE);
75
- return { target: positionals[0]!, scope, timeoutMs, format, command, help: false };
84
+ return { target: positionals[0]!, scope, timeoutMs, format, evidenceFile, command, help: false };
76
85
  }
77
86
 
78
87
  function runValidationCommand(command: string[], cwd: string, scope: string, timeoutMs: number, json: boolean) {
@@ -294,23 +303,30 @@ function taskValidate(args: string[]): void {
294
303
  : internalHandlerRoute('task-validate', 'inplace', options.scope)
295
304
  ? inplaceValidation(config, target, options)
296
305
  : (() => { throw new Error('TASK_VALIDATE_SCOPE_UNREGISTERED'); })();
297
- const evidence = {
298
- version: 1,
306
+ const cleanup = result.cleanup() as ManualValidationCleanup;
307
+ const evidence = createManualValidationEvidence({
308
+ mode: target.workspace.mode,
299
309
  taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
300
310
  branch: target.branch,
301
- scope: options.scope,
302
311
  commit,
312
+ recoverable: target.workspace.mode === 'task-bound',
313
+ scope: options.scope,
303
314
  command: path.basename(options.command[0]!),
304
315
  startedAt,
305
316
  completedAt: new Date().toISOString(),
306
317
  exitCode: result.exitCode,
307
318
  signal: result.signal,
308
- cleanup: result.cleanup()
309
- };
319
+ cleanup
320
+ });
321
+ if (options.evidenceFile) {
322
+ try { writeJsonAtomic(path.resolve(options.evidenceFile), evidence); }
323
+ catch (error) { throw new Error(`MANUAL_VALIDATION_EVIDENCE_WRITE_FAILED: ${error instanceof Error ? error.message : String(error)}`); }
324
+ }
325
+ const evidenceSummary = manualValidationEvidenceSummary(evidence);
310
326
  if (options.format === 'json') {
311
- process.stdout.write(`${JSON.stringify({ status: 'applied', changed: false, evidence, error: null })}\n`);
327
+ process.stdout.write(`${JSON.stringify({ status: 'applied', changed: false, evidence: evidenceSummary, evidenceFile: options.evidenceFile, error: null })}\n`);
312
328
  } else {
313
- process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidence.command}, cleanup=${evidence.cleanup}).\n`);
329
+ process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidenceSummary.command}, cleanup=${evidenceSummary.cleanup}).\n`);
314
330
  }
315
331
  process.exitCode = result.exitCode;
316
332
  } catch (error) {
@@ -0,0 +1,26 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // Inspect descendants without following symlinks. The caller supplies the trusted root.
5
+ export function inspectOwnedPath(rootInput: string, targetInput: string, expect: 'file' | 'directory'): fs.Stats | null {
6
+ const root = path.resolve(rootInput);
7
+ const target = path.resolve(targetInput);
8
+ const relative = path.relative(root, target);
9
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return null;
10
+ try {
11
+ const rootStat = fs.lstatSync(root);
12
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
13
+ let current = root;
14
+ for (const segment of relative.split(path.sep)) {
15
+ current = path.join(current, segment);
16
+ const stat = fs.lstatSync(current);
17
+ if (stat.isSymbolicLink()) return null;
18
+ if (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid()) return null;
19
+ if (current === target) return (expect === 'file' ? stat.isFile() : stat.isDirectory()) ? stat : null;
20
+ if (!stat.isDirectory()) return null;
21
+ }
22
+ } catch {
23
+ return null;
24
+ }
25
+ return null;
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { PlatformCheckSnapshot } from './snapshots.ts';
2
+
3
+ export function checkStatusBucket(status: string): PlatformCheckSnapshot['bucket'] {
4
+ const value = status.toLowerCase();
5
+ if (['pass', 'success', 'successful', 'neutral'].includes(value)) return 'pass';
6
+ if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(value)) return 'fail';
7
+ if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(value)) return 'cancel';
8
+ return 'pending';
9
+ }
@@ -185,6 +185,10 @@ function sanitizeMarkdownDocument(value: string, options: SanitizationOptions =
185
185
  return ok(result + tail.value);
186
186
  }
187
187
 
188
+ function canonicalizeCommentBody(value: string): SanitizationResult<string> {
189
+ return sanitizeMarkdownDocument(value, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
190
+ }
191
+
188
192
  function renderSafeCodeFence(value: string, language = '', reservedMarkers: readonly RegExp[] = [CONTROL_MARKER_PATTERN]): string {
189
193
  const content = markerSafe(normalized(value), reservedMarkers);
190
194
  const longestRun = Math.max(0, ...(content.match(/`+/g) || []).map((run) => run.length));
@@ -244,6 +248,7 @@ export {
244
248
  escapeMarkdownLiteral,
245
249
  fenceRanges,
246
250
  renderSafeCodeFence,
251
+ canonicalizeCommentBody,
247
252
  sanitizeMarkdownDocument,
248
253
  splitDocumentPlaceholder
249
254
  };
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  import { COMPLETION_BACKFILL_FAMILIES, inspectCompletionArtifacts } from '../task/finalization-artifacts.ts';
5
- import { parseArtifactName } from '../task/artifact-lifecycle.ts';
5
+ import { parseArtifactName } from '../task/artifact-name.ts';
6
6
  import { parseTaskFrontmatter } from '../task/frontmatter.ts';
7
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
8
  import { getOpenWorkflowWarnings } from '../task/workflow-warnings.ts';