@fitlab-ai/agent-infra 0.9.1 → 0.9.2

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 (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -0,0 +1,378 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { execFile } from 'node:child_process';
3
+ import { isAgentClientId } from '../agent-clients/types.ts';
4
+ import {
5
+ listAgentClientAdapters,
6
+ listInstalledAgentClientAdapters
7
+ } from '../agent-clients/registry.ts';
8
+ import { hostJoin } from './engines/wsl2-paths.ts';
9
+ import type { AgentClientAdapter } from '../agent-clients/adapter.ts';
10
+ import type { AgentClientState } from '../agent-clients/types.ts';
11
+ import type {
12
+ AgentClientSandboxRecoveryCheck,
13
+ AgentClientSandboxHook,
14
+ AgentClientSandboxHookContext,
15
+ AgentClientSandboxHookResult,
16
+ SandboxAlias,
17
+ SandboxHookCommand,
18
+ SandboxHookCommandResult,
19
+ SandboxHookPhase,
20
+ SandboxHookStatus,
21
+ SandboxTool
22
+ } from './tool-types.ts';
23
+
24
+ const DEFAULT_SANDBOX_HOOK_TIMEOUT_MS = 30_000;
25
+
26
+ type SandboxCapabilityConfig = Readonly<{
27
+ home: string;
28
+ project: string;
29
+ tools: readonly string[];
30
+ customTools?: readonly SandboxTool[];
31
+ agentClientState?: AgentClientState;
32
+ }>;
33
+
34
+ type SandboxRuntimeCapabilityProjection = Readonly<{
35
+ tools: readonly Readonly<Record<string, unknown>>[];
36
+ selectedAgentClients: readonly string[];
37
+ hooks: readonly Readonly<{
38
+ adapterId: string;
39
+ id: string;
40
+ phase: SandboxHookPhase;
41
+ timeoutMs: number;
42
+ }>[];
43
+ recoveryChecks: readonly Readonly<{
44
+ adapterId: string;
45
+ id: string;
46
+ when?: AgentClientSandboxRecoveryCheck['when'];
47
+ probe: AgentClientSandboxRecoveryCheck['probe'];
48
+ finding: Readonly<{
49
+ repairKind: AgentClientSandboxRecoveryCheck['finding']['repairKind'];
50
+ path?: string;
51
+ }>;
52
+ repair?: AgentClientSandboxRecoveryCheck['repair'];
53
+ }>[];
54
+ }>;
55
+
56
+ type PlannedAgentClientRecoveryCheck = Readonly<{
57
+ adapterId: string;
58
+ check: AgentClientSandboxRecoveryCheck;
59
+ }>;
60
+
61
+ type SandboxCapabilityPlan = Readonly<{
62
+ tools: readonly SandboxTool[];
63
+ selectedAgentClients: readonly AgentClientAdapter[];
64
+ hooksByPhase: Readonly<Record<SandboxHookPhase, readonly AgentClientSandboxHook[]>>;
65
+ recoveryChecks: readonly PlannedAgentClientRecoveryCheck[];
66
+ aliases: readonly SandboxAlias[];
67
+ imageSignature: string;
68
+ runtimeSignature: string;
69
+ runtimeProjection: SandboxRuntimeCapabilityProjection;
70
+ cleanupInventory: readonly SandboxTool[];
71
+ }>;
72
+
73
+ type HookExecutionResult = Readonly<{
74
+ hookId: string;
75
+ phase: SandboxHookPhase;
76
+ status: SandboxHookStatus;
77
+ message?: string;
78
+ }>;
79
+
80
+ type TimerHandle = ReturnType<typeof setTimeout> | unknown;
81
+
82
+ function agentInfraTool(home: string): SandboxTool {
83
+ return {
84
+ id: 'agent-infra',
85
+ name: 'agent-infra CLI',
86
+ install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
87
+ sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
88
+ containerMount: '/home/devuser/.agent-infra-cli',
89
+ versionCmd: 'ai version --raw',
90
+ setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
91
+ };
92
+ }
93
+
94
+ function compatAgentClientState(tools: readonly string[]): AgentClientState {
95
+ return Object.fromEntries(
96
+ listAgentClientAdapters().map((adapter) => [
97
+ adapter.id,
98
+ {
99
+ enabled: true,
100
+ installInSandbox: tools.includes(adapter.id)
101
+ }
102
+ ])
103
+ ) as AgentClientState;
104
+ }
105
+
106
+ function stableValue(value: unknown): unknown {
107
+ if (Array.isArray(value)) {
108
+ return value.map(stableValue);
109
+ }
110
+ if (value && typeof value === 'object') {
111
+ return Object.fromEntries(
112
+ Object.entries(value as Record<string, unknown>)
113
+ .sort(([left], [right]) => left.localeCompare(right))
114
+ .map(([key, nested]) => [key, stableValue(nested)])
115
+ );
116
+ }
117
+ return value;
118
+ }
119
+
120
+ function runtimeToolProjection(tool: SandboxTool): Readonly<Record<string, unknown>> {
121
+ return stableValue({
122
+ id: tool.id,
123
+ install: tool.install,
124
+ containerMount: tool.containerMount,
125
+ versionCmd: tool.versionCmd,
126
+ envVars: tool.envVars ?? {},
127
+ hostPreSeedFiles: (tool.hostPreSeedFiles ?? []).map(({ sandboxName }) => ({ sandboxName })),
128
+ hostPreSeedDirs: (tool.hostPreSeedDirs ?? []).map(({ sandboxSubdir }) => ({ sandboxSubdir })),
129
+ pathRewriteFiles: tool.pathRewriteFiles ?? [],
130
+ hostLiveMounts: (tool.hostLiveMounts ?? []).map(({ containerSubpath }) => ({ containerSubpath })),
131
+ postSetupCmds: tool.postSetupCmds ?? [],
132
+ tmpfs: tool.tmpfs ?? null
133
+ }) as Readonly<Record<string, unknown>>;
134
+ }
135
+
136
+ function hash(value: unknown): string {
137
+ return createHash('sha256').update(JSON.stringify(stableValue(value))).digest('hex');
138
+ }
139
+
140
+ function assertUniqueTools(tools: readonly SandboxTool[]): void {
141
+ const seen = new Set<string>();
142
+ for (const tool of tools) {
143
+ if (seen.has(tool.id)) {
144
+ throw new Error(`Duplicate sandbox tool id "${tool.id}"`);
145
+ }
146
+ seen.add(tool.id);
147
+ }
148
+ }
149
+
150
+ function hooksByPhase(
151
+ adapters: readonly AgentClientAdapter[]
152
+ ): SandboxCapabilityPlan['hooksByPhase'] {
153
+ const phases: SandboxHookPhase[] = [
154
+ 'prepare',
155
+ 'before-container-create',
156
+ 'after-container-start',
157
+ 'before-enter'
158
+ ];
159
+ return Object.freeze(Object.fromEntries(
160
+ phases.map((phase) => [
161
+ phase,
162
+ Object.freeze(adapters.flatMap((adapter) =>
163
+ adapter.sandbox.hooks.filter((hook) => hook.phase === phase)
164
+ ))
165
+ ])
166
+ ) as Record<SandboxHookPhase, readonly AgentClientSandboxHook[]>);
167
+ }
168
+
169
+ function createSandboxCapabilityPlan(
170
+ config: SandboxCapabilityConfig
171
+ ): SandboxCapabilityPlan {
172
+ const state = config.agentClientState ?? compatAgentClientState(config.tools);
173
+ const selectedAgentClients = listInstalledAgentClientAdapters(state);
174
+ const context = { home: config.home, project: config.project };
175
+ const selectedTools = selectedAgentClients.map((adapter) =>
176
+ adapter.sandbox.createTool(context)
177
+ );
178
+ const allClientTools = listAgentClientAdapters().map((adapter) =>
179
+ adapter.sandbox.createTool(context)
180
+ );
181
+ const customById = new Map((config.customTools ?? []).map((tool) => [tool.id, tool]));
182
+ if (customById.size !== (config.customTools ?? []).length) {
183
+ throw new Error('Duplicate sandbox tool id in customTools');
184
+ }
185
+ for (const id of customById.keys()) {
186
+ if (id === 'agent-infra' || isAgentClientId(id)) {
187
+ throw new Error(`Custom sandbox tool id "${id}" collides with a built-in tool`);
188
+ }
189
+ }
190
+
191
+ const nonClientTools: SandboxTool[] = [];
192
+ const selectedCustomTools: SandboxTool[] = [];
193
+ for (const id of config.tools) {
194
+ if (isAgentClientId(id)) continue;
195
+ if (id === 'agent-infra') {
196
+ if (!nonClientTools.some((tool) => tool.id === id)) {
197
+ nonClientTools.push(agentInfraTool(config.home));
198
+ }
199
+ continue;
200
+ }
201
+ const custom = customById.get(id);
202
+ if (!custom) {
203
+ throw new Error(`Unknown sandbox tool: ${id}`);
204
+ }
205
+ selectedCustomTools.push(custom);
206
+ }
207
+
208
+ const tools = Object.freeze([...nonClientTools, ...selectedTools, ...selectedCustomTools]);
209
+ assertUniqueTools(tools);
210
+ const phaseHooks = hooksByPhase(selectedAgentClients);
211
+ const hookProjection = selectedAgentClients.flatMap((adapter) =>
212
+ adapter.sandbox.hooks.map((hook) => ({
213
+ adapterId: adapter.id,
214
+ id: hook.id,
215
+ phase: hook.phase,
216
+ timeoutMs: hook.timeoutMs ?? DEFAULT_SANDBOX_HOOK_TIMEOUT_MS
217
+ }))
218
+ );
219
+ const recoveryChecks = Object.freeze(selectedAgentClients.flatMap((adapter) =>
220
+ (adapter.sandbox.recoveryChecks ?? []).map((check) => Object.freeze({
221
+ adapterId: adapter.id,
222
+ check
223
+ }))
224
+ ));
225
+ const recoveryCheckProjection = Object.freeze(recoveryChecks.map(({ adapterId, check }) =>
226
+ Object.freeze({
227
+ adapterId,
228
+ id: check.id,
229
+ ...(check.when === undefined ? {} : { when: check.when }),
230
+ probe: check.probe,
231
+ finding: Object.freeze({
232
+ repairKind: check.finding.repairKind,
233
+ ...(check.finding.path === undefined ? {} : { path: check.finding.path })
234
+ }),
235
+ ...(check.repair === undefined ? {} : { repair: check.repair })
236
+ })
237
+ ));
238
+ const runtimeProjection = Object.freeze({
239
+ tools: Object.freeze(tools.map(runtimeToolProjection)),
240
+ selectedAgentClients: Object.freeze(selectedAgentClients.map((adapter) => adapter.id)),
241
+ hooks: Object.freeze(hookProjection),
242
+ recoveryChecks: recoveryCheckProjection
243
+ });
244
+ const cleanupInventory = Object.freeze([
245
+ agentInfraTool(config.home),
246
+ ...allClientTools,
247
+ ...customById.values()
248
+ ]);
249
+ assertUniqueTools(cleanupInventory);
250
+
251
+ return Object.freeze({
252
+ tools,
253
+ selectedAgentClients,
254
+ hooksByPhase: phaseHooks,
255
+ recoveryChecks,
256
+ aliases: Object.freeze(
257
+ listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases)
258
+ ),
259
+ imageSignature: hash(tools.map(({ id, install }) => ({ id, install }))),
260
+ runtimeSignature: hash(runtimeProjection),
261
+ runtimeProjection,
262
+ cleanupInventory
263
+ });
264
+ }
265
+
266
+ function timeoutStatus(phase: SandboxHookPhase): SandboxHookStatus {
267
+ if (phase === 'before-enter') return 'warning';
268
+ return 'fatal';
269
+ }
270
+
271
+ async function runSandboxHooks({
272
+ hooks,
273
+ phase,
274
+ context,
275
+ runCommand = async () => {
276
+ throw new Error('Sandbox hook command runner is unavailable');
277
+ },
278
+ scheduleTimeout = (callback, timeoutMs) => setTimeout(callback, timeoutMs),
279
+ clearScheduledTimeout = (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>)
280
+ }: {
281
+ hooks: readonly AgentClientSandboxHook[];
282
+ phase: SandboxHookPhase;
283
+ context: Omit<AgentClientSandboxHookContext, 'signal' | 'runCommand'>;
284
+ runCommand?: (
285
+ command: SandboxHookCommand,
286
+ options: { signal: AbortSignal; timeoutMs: number }
287
+ ) => Promise<SandboxHookCommandResult>;
288
+ scheduleTimeout?: (callback: () => void, timeoutMs: number) => TimerHandle;
289
+ clearScheduledTimeout?: (handle: TimerHandle) => void;
290
+ }): Promise<HookExecutionResult[]> {
291
+ const results: HookExecutionResult[] = [];
292
+ for (const hook of hooks) {
293
+ if (hook.phase !== phase) continue;
294
+ const timeoutMs = hook.timeoutMs ?? DEFAULT_SANDBOX_HOOK_TIMEOUT_MS;
295
+ const controller = new AbortController();
296
+ let timedOut = false;
297
+ let timer: TimerHandle;
298
+ const timeout = new Promise<AgentClientSandboxHookResult>((resolve) => {
299
+ timer = scheduleTimeout(() => {
300
+ timedOut = true;
301
+ controller.abort();
302
+ resolve({
303
+ status: timeoutStatus(phase),
304
+ message: `Sandbox hook '${hook.id}' timed out after ${timeoutMs}ms.`
305
+ });
306
+ }, timeoutMs);
307
+ });
308
+ let result: AgentClientSandboxHookResult;
309
+ try {
310
+ const execution = Promise.resolve().then(() => hook.run({
311
+ ...context,
312
+ signal: controller.signal,
313
+ runCommand: (command) => runCommand(command, {
314
+ signal: controller.signal,
315
+ timeoutMs
316
+ })
317
+ }));
318
+ result = await Promise.race([execution, timeout]);
319
+ } catch (error) {
320
+ result = {
321
+ status: timeoutStatus(phase),
322
+ message: error instanceof Error ? error.message : String(error)
323
+ };
324
+ } finally {
325
+ clearScheduledTimeout(timer!);
326
+ }
327
+ results.push({
328
+ hookId: hook.id,
329
+ phase,
330
+ status: result.status,
331
+ ...(result.message ? { message: result.message } : {})
332
+ });
333
+ if (timedOut || result.status === 'fatal' || result.status === 'hard-failure') {
334
+ break;
335
+ }
336
+ }
337
+ return results;
338
+ }
339
+
340
+ function runBoundedSandboxHookCommand(
341
+ command: SandboxHookCommand,
342
+ options: { signal: AbortSignal; timeoutMs: number }
343
+ ): Promise<SandboxHookCommandResult> {
344
+ return new Promise((resolve, reject) => {
345
+ execFile(
346
+ command.command,
347
+ [...(command.args ?? [])],
348
+ {
349
+ cwd: command.cwd,
350
+ env: command.env,
351
+ encoding: 'utf8',
352
+ signal: options.signal,
353
+ timeout: options.timeoutMs
354
+ },
355
+ (error, stdout) => {
356
+ if (error) {
357
+ reject(error);
358
+ return;
359
+ }
360
+ resolve({ stdout: stdout.trim() });
361
+ }
362
+ );
363
+ });
364
+ }
365
+
366
+ export {
367
+ DEFAULT_SANDBOX_HOOK_TIMEOUT_MS,
368
+ createSandboxCapabilityPlan,
369
+ runBoundedSandboxHookCommand,
370
+ runSandboxHooks
371
+ };
372
+ export type {
373
+ HookExecutionResult,
374
+ SandboxCapabilityConfig,
375
+ SandboxCapabilityPlan,
376
+ PlannedAgentClientRecoveryCheck,
377
+ SandboxRuntimeCapabilityProjection
378
+ };
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import type { ExecFileSyncOptions } from 'node:child_process';
6
+ import { removeDirRecursive } from '../../remove-dir.ts';
6
7
 
7
8
  const PROBE_TIMEOUT_MS = 2_000;
8
9
  const READ_IMAGE_TIMEOUT_MS = 5_000;
@@ -26,7 +27,7 @@ export function createLinuxClipboardAdapter({
26
27
  execToFileFn = execToFile,
27
28
  mkdtempFn = fs.mkdtempSync,
28
29
  readFileFn = fs.readFileSync,
29
- rmFn = fs.rmSync
30
+ rmFn = removeDirRecursive as typeof fs.rmSync
30
31
  }: {
31
32
  env?: NodeJS.ProcessEnv;
32
33
  execFn?: ExecFn;