@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
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
3
3
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { inspectPlatformRequiredChecks, registerPlatformCapabilities } from './adapters.js';
4
5
  import { resolvePlatformContext } from './context.js';
5
6
  import { createGitHubClient } from './github-client.js';
6
7
  import { inspectPlatformPullRequest } from './pull-requests.js';
@@ -87,6 +88,18 @@ function normalizeChecks(value) {
87
88
  }] : [];
88
89
  });
89
90
  }
91
+ registerPlatformCapabilities('github', {
92
+ inspectRequiredChecks({ client, repository, number, cwd }) {
93
+ const github = client || createGitHubClient();
94
+ const inspected = github.json([
95
+ 'pr', 'checks', String(number), '--repo', repository, '--required',
96
+ '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
97
+ ], { cwd });
98
+ return inspected.ok
99
+ ? { ok: true, value: normalizeChecks(inspected.value) }
100
+ : { ok: false, error: inspected.error };
101
+ }
102
+ });
90
103
  function resolvedTask(taskRef, options) {
91
104
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
92
105
  if (!resolved.ok)
@@ -110,18 +123,26 @@ function inspectRequiredChecks(taskRef, options = {}) {
110
123
  if (!base.ok)
111
124
  return base.output;
112
125
  const repository = base.context.platform.repository;
113
- const inspected = base.client.json([
114
- 'pr', 'checks', String(base.prNumber), '--repo', repository, '--required',
115
- '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
116
- ], { cwd: base.resolved.repoRoot });
117
- if (!inspected.ok) {
118
- return checksResult(inspected.error.retryable ? 'blocked' : 'failed', {
126
+ const inspected = inspectPlatformRequiredChecks(base.context.platform.type, {
127
+ cwd: base.resolved.repoRoot,
128
+ repository,
129
+ number: base.prNumber,
130
+ headSha: base.pullRequest.head.sha,
131
+ client: options.client || base.client
132
+ });
133
+ if (!inspected.ok || !inspected.value) {
134
+ const error = inspected.error || {
135
+ code: 'REQUIRED_CHECKS_INSPECTION_INVALID',
136
+ message: 'Platform adapter returned no required-checks snapshot',
137
+ retryable: false
138
+ };
139
+ return checksResult(error.retryable ? 'blocked' : 'failed', {
119
140
  platform: base.context.platform, capabilities: base.context.capabilities,
120
141
  resource: { kind: 'pull-request', number: base.prNumber },
121
- pullRequest: base.pullRequest, error: inspected.error
142
+ pullRequest: base.pullRequest, error
122
143
  });
123
144
  }
124
- const classified = classifyRequiredChecks(normalizeChecks(inspected.value));
145
+ const classified = classifyRequiredChecks(inspected.value);
125
146
  const status = classified.state === 'passed' || classified.state === 'no-required'
126
147
  ? 'no-op'
127
148
  : classified.state === 'failed' || classified.state === 'cancelled' ? 'failed' : 'blocked';
@@ -194,6 +215,13 @@ function resolvePlatformCheckRun(taskRef, options) {
194
215
  error: resolution.status === 'resolved' ? null : { code: resolution.status === 'ambiguous' ? 'CHECK_RUN_AMBIGUOUS' : 'CHECK_RUN_NOT_FOUND', message: `Check run is ${resolution.status}`, retryable: false }
195
216
  });
196
217
  }
218
+ function fetchCheckLogText(client, args, cwd) {
219
+ const fetched = client.text(args, { cwd });
220
+ if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
221
+ return fetched;
222
+ }
223
+ return client.text([...args, '--allow-escape-sequences'], { cwd });
224
+ }
197
225
  function fetchPlatformCheckLogs(taskRef, options) {
198
226
  const base = resolvedTask(taskRef, options);
199
227
  if (!base.ok)
@@ -201,7 +229,7 @@ function fetchPlatformCheckLogs(taskRef, options) {
201
229
  const args = options.job
202
230
  ? ['api', `repos/${base.context.platform.repository}/actions/jobs/${options.job}/logs`]
203
231
  : ['run', 'view', String(options.run), '--repo', base.context.platform.repository, '--log-failed'];
204
- const fetched = base.client.text(args, { cwd: base.resolved.repoRoot });
232
+ const fetched = fetchCheckLogText(base.client, args, base.resolved.repoRoot);
205
233
  if (!fetched.ok)
206
234
  return checksResult(fetched.error.retryable ? 'blocked' : 'failed', { platform: base.context.platform, capabilities: base.context.capabilities, error: fetched.error });
207
235
  if (!fetched.value)
@@ -212,5 +240,5 @@ function fetchPlatformCheckLogs(taskRef, options) {
212
240
  logs: { runId: options.run, ...(options.job ? { jobId: options.job } : {}), text: fetched.value }, error: null
213
241
  });
214
242
  }
215
- export { classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, parseRunJobIdentity, resolvePlatformCheckRun, resolveRunCandidate, watchPlatformChecks, watchRequiredChecks };
243
+ export { classifyRequiredChecks, fetchCheckLogText, fetchPlatformCheckLogs, inspectRequiredChecks, parseRunJobIdentity, resolvePlatformCheckRun, resolveRunCandidate, watchPlatformChecks, watchRequiredChecks };
216
244
  //# sourceMappingURL=pr-checks.js.map
@@ -1,9 +1,11 @@
1
1
  import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
2
3
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
5
  import { extractSection } from '../task/sections.js';
5
6
  import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
6
- import { resolvePlatformContext } from './context.js';
7
+ import { inspectPlatformChangeRequest, registerPlatformCapabilities } from './adapters.js';
8
+ import { parseGitHubRemote, resolvePlatformContext } from './context.js';
7
9
  import { createGitHubClient } from './github-client.js';
8
10
  import { inspectPlatformIssue } from './issues.js';
9
11
  import { planPullRequestMetadata } from './pull-request-metadata.js';
@@ -90,6 +92,97 @@ function inspectGitHubPullRequest(client, repository, number, cwd) {
90
92
  ? { ok: true, value: pullRequest }
91
93
  : { ok: false, error: { code: 'PR_IDENTITY_INVALID', message: 'Remote resource is not a valid pull request', retryable: false } };
92
94
  }
95
+ function configuredGitRemotes(cwd) {
96
+ try {
97
+ return execFileSync('git', ['remote'], {
98
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
99
+ }).split(/\r?\n/).filter(Boolean).flatMap((name) => {
100
+ try {
101
+ const url = execFileSync('git', ['config', '--get', `remote.${name}.url`], {
102
+ cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe']
103
+ }).trim();
104
+ const repository = parseGitHubRemote(url);
105
+ return repository ? [{ name, url, repository }] : [];
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ });
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ function rewriteGitHubRemote(remote, repository) {
117
+ const match = remote.trim().match(/^(https?:\/\/github\.com\/|ssh:\/\/(?:git@)?github\.com\/|git@github\.com:)[^/\s]+\/[^/\s]+(?:\.git)?$/i);
118
+ return match ? `${match[1]}${repository}.git` : null;
119
+ }
120
+ function resolveGitHubChangeRequestGitEvidence({ cwd, repository, pullRequest }) {
121
+ if (pullRequest.repository !== repository ||
122
+ pullRequest.base.repository !== repository ||
123
+ pullRequest.number <= 0) {
124
+ return {
125
+ ok: false,
126
+ error: {
127
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
128
+ message: 'Pull request repository identity is inconsistent',
129
+ retryable: false
130
+ }
131
+ };
132
+ }
133
+ const reviewedHeadRef = `refs/pull/${pullRequest.number}/head`;
134
+ const targetHeadRef = `refs/heads/${pullRequest.base.ref}`;
135
+ const refValid = (ref) => {
136
+ try {
137
+ execFileSync('git', ['check-ref-format', ref], {
138
+ cwd, encoding: 'utf8', stdio: ['ignore', 'ignore', 'ignore']
139
+ });
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ };
146
+ if (!refValid(reviewedHeadRef) || !refValid(targetHeadRef)) {
147
+ return {
148
+ ok: false,
149
+ error: {
150
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
151
+ message: 'Pull request Git refs are invalid',
152
+ retryable: false
153
+ }
154
+ };
155
+ }
156
+ const remotes = configuredGitRemotes(cwd);
157
+ const exact = remotes
158
+ .filter((remote) => remote.repository === repository)
159
+ .sort((left, right) => Number(right.name === 'origin') - Number(left.name === 'origin') ||
160
+ left.name.localeCompare(right.name))[0];
161
+ const origin = remotes.find((remote) => remote.name === 'origin');
162
+ const remoteUrl = exact?.url || (origin ? rewriteGitHubRemote(origin.url, repository) : null);
163
+ if (!remoteUrl) {
164
+ return {
165
+ ok: false,
166
+ error: {
167
+ code: 'PR_MERGE_EVIDENCE_SOURCE_UNAVAILABLE',
168
+ message: `No GitHub remote can provide evidence for ${repository}`,
169
+ retryable: false
170
+ }
171
+ };
172
+ }
173
+ return {
174
+ ok: true,
175
+ value: { remoteUrl, reviewedHeadRef, targetHeadRef }
176
+ };
177
+ }
178
+ registerPlatformCapabilities('github', {
179
+ inspectChangeRequest({ client, repository, number, cwd }) {
180
+ return inspectGitHubPullRequest(client || createGitHubClient(), repository, number, cwd);
181
+ },
182
+ resolveChangeRequestGitEvidence(context) {
183
+ return resolveGitHubChangeRequestGitEvidence(context);
184
+ }
185
+ });
93
186
  function locatePullRequest(base, head, target) {
94
187
  const repository = base.context.platform.repository;
95
188
  const listed = base.client.json([
@@ -115,12 +208,23 @@ function inspectPlatformPullRequest(taskRef, options = {}) {
115
208
  platform: base.context.platform, capabilities: base.context.capabilities,
116
209
  error: { code: 'PR_NOT_LINKED', message: 'Task has no valid pr_number', retryable: false }
117
210
  });
118
- const fetched = inspectGitHubPullRequest(base.client, base.context.platform.repository, base.prNumber, base.resolved.repoRoot);
119
- if (!fetched.ok)
120
- return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
211
+ const fetched = inspectPlatformChangeRequest(base.context.platform.type, {
212
+ cwd: base.resolved.repoRoot,
213
+ repository: base.context.platform.repository,
214
+ number: base.prNumber,
215
+ client: options.client || base.client
216
+ });
217
+ if (!fetched.ok || !fetched.value) {
218
+ const error = fetched.error || {
219
+ code: 'PR_INSPECTION_INVALID',
220
+ message: 'Platform adapter returned no change-request snapshot',
221
+ retryable: false
222
+ };
223
+ return result(error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
121
224
  platform: base.context.platform, capabilities: base.context.capabilities,
122
- resource: { kind: 'pull-request', number: base.prNumber }, error: fetched.error
225
+ resource: { kind: 'pull-request', number: base.prNumber }, error
123
226
  });
227
+ }
124
228
  return result('no-op', base.resolved.taskId, base.issueNumber, base.prNumber, {
125
229
  platform: base.context.platform, capabilities: base.context.capabilities,
126
230
  resource: { kind: 'pull-request', number: base.prNumber }, pullRequest: fetched.value, error: null
@@ -336,5 +440,5 @@ function syncPlatformPullRequest(taskRef, options) {
336
440
  operations: operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
337
441
  });
338
442
  }
339
- export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, selectPullRequest, syncPlatformPullRequest };
443
+ export { bindPlatformPullRequest, createPlatformPullRequest, inspectGitHubPullRequest, inspectPlatformPullRequest, normalizePullRequest, resolveGitHubChangeRequestGitEvidence, selectPullRequest, syncPlatformPullRequest };
340
444
  //# sourceMappingURL=pull-requests.js.map
@@ -18,7 +18,7 @@ function inspectPlatformRelease(tag, options = {}) {
18
18
  return { ...platformResult(result.error.retryable ? 'blocked' : 'failed'), platform: context.platform, release: null, workflows: [], error: result.error };
19
19
  }
20
20
  const release = { tag: String(result.value.tagName || tag), published: !Boolean(result.value.isDraft), draft: Boolean(result.value.isDraft), url: result.value.url ? String(result.value.url) : null };
21
- const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,headBranch,status,conclusion,url'], { cwd: options.cwd });
21
+ const runs = client.json(['run', 'list', '--repo', context.platform.repository, '--limit', '100', '--json', 'name,workflowName,displayTitle,event,headBranch,headSha,status,conclusion,createdAt,databaseId,attempt,url'], { cwd: options.cwd });
22
22
  return { ...platformResult(runs.ok ? 'no-op' : runs.error.retryable ? 'blocked' : 'degraded'), platform: context.platform, release, workflows: runs.ok ? runs.value : [], error: runs.ok ? null : runs.error };
23
23
  }
24
24
  function upsertPlatformRelease(input, options = {}) {
@@ -1,10 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execFileSync } from "node:child_process";
4
+ import { hasPlatformCapability } from "./adapters.js";
4
5
  import { inspectRequiredChecks } from "./pr-checks.js";
5
6
  import { resolvePlatformContext } from "./context.js";
6
7
  import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.js";
7
- import { loadPostReviewConfig, resolvePostReviewGlobs } from "../task/review-fingerprint.js";
8
8
  const CHECK_TYPE = "required-checks";
9
9
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
10
10
  function validPrNumber(value) {
@@ -50,10 +50,9 @@ export function evaluateRequiredChecks(context, shared) {
50
50
  const relation = context.relation || (context.gitRoot && inspection.pullRequest?.head
51
51
  ? resolveReviewedHeadRelation({
52
52
  gitRoot: context.gitRoot,
53
- localHead,
53
+ comparisonHead: localHead,
54
54
  lastReviewedCommit: reviewedHead,
55
- pullRequest: inspection.pullRequest,
56
- pathspecs: context.pathspecs || [":/"]
55
+ pullRequest: inspection.pullRequest
57
56
  })
58
57
  : localHead === reviewedHead && localHead === prHead
59
58
  ? { status: "strict" }
@@ -78,14 +77,14 @@ export function check({ taskDir }, shared) {
78
77
  const task = shared.loadTask(taskDir);
79
78
  if (!task.ok)
80
79
  return shared.failResult(CHECK_TYPE, task.message);
81
- const platform = resolvePlatformContext({ cwd: shared.repoRoot });
82
- if (platform.platform.type !== "github") {
83
- return shared.passResult(CHECK_TYPE, "Skipped: this code platform does not provide required checks");
84
- }
85
80
  const prFlow = readPrFlow(shared.repoRoot);
86
81
  if (prFlow === "disabled" || task.metadata.pr_status === "skipped" || !validPrNumber(task.metadata.pr_number)) {
87
82
  return evaluateRequiredChecks({ metadata: task.metadata, localHead: null, inspection: null, prFlow }, shared);
88
83
  }
84
+ const platform = resolvePlatformContext({ cwd: shared.repoRoot });
85
+ if (!hasPlatformCapability(platform.platform.type, "required-checks")) {
86
+ return shared.blockedResult(CHECK_TYPE, `Platform '${platform.platform.type || "none"}' does not provide required-checks inspection`, "dependency_error");
87
+ }
89
88
  const localHead = readHead(shared.repoRoot);
90
89
  if (!localHead)
91
90
  return evaluateRequiredChecks({ metadata: task.metadata, localHead, inspection: null, prFlow }, shared);
@@ -95,8 +94,7 @@ export function check({ taskDir }, shared) {
95
94
  localHead,
96
95
  inspection,
97
96
  prFlow,
98
- gitRoot: shared.repoRoot,
99
- pathspecs: resolvePostReviewGlobs({}, loadPostReviewConfig(shared.repoRoot))
97
+ gitRoot: shared.repoRoot
100
98
  }, shared);
101
99
  }
102
100
  //# sourceMappingURL=verification-required.js.map
@@ -74,18 +74,30 @@ async function select(question, choices, defaultValue) {
74
74
  }
75
75
  return trimmed;
76
76
  }
77
- async function multiSelect(question, choices) {
77
+ async function multiSelect(question, choices, defaultIds = choices.map((choice) => choice.id)) {
78
+ const choiceIds = new Set(choices.map((choice) => choice.id));
79
+ if (defaultIds.some((id) => !choiceIds.has(id))) {
80
+ throw new Error('Default selection contains an unknown id');
81
+ }
82
+ const normalizedDefaults = choices
83
+ .map((choice) => choice.id)
84
+ .filter((id) => defaultIds.includes(id));
78
85
  process.stdout.write(` ${question}:\n`);
79
86
  const idWidth = Math.max(...choices.map((c) => c.id.length));
80
87
  choices.forEach((c, i) => {
81
88
  process.stdout.write(` ${i + 1}) ${c.id.padEnd(idWidth)} (${c.label})\n`);
82
89
  });
83
- ask('Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: all]: ');
90
+ const defaultLabel = normalizedDefaults.length === choices.length
91
+ ? 'all'
92
+ : normalizedDefaults.length === 0
93
+ ? 'none'
94
+ : normalizedDefaults.join(',');
95
+ ask(`Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: ${defaultLabel}]: `);
84
96
  setupInterface();
85
97
  const line = await nextLine();
86
98
  // Strictly distinguish bare Enter (null/empty string) from whitespace input.
87
99
  if (line === null || line === '')
88
- return choices.map((c) => c.id);
100
+ return normalizedDefaults;
89
101
  // Explicit empty selection: "none" means deliberately zero built-in choices.
90
102
  if (line.trim().toLowerCase() === 'none')
91
103
  return [];
@@ -21,7 +21,10 @@ async function inspectHomebrewChannel(url, version, fetcher = fetch) {
21
21
  return { status: 'blocked', published: null, version, error: { code: 'HOMEBREW_UNAVAILABLE', message: `formula endpoint returned ${response.status}` } };
22
22
  const formula = await response.text();
23
23
  const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
- return { status: 'no-op', published: new RegExp(`(?:version\\s+["']${escaped}["']|/v?${escaped}\\.)`).test(formula), version, error: null };
24
+ const formulaUrl = /^\s*url\s+["']([^"']+)["']\s*$/m.exec(formula)?.[1] ?? '';
25
+ const npmTarball = new RegExp(`^https://registry\\.npmjs\\.org/.+/-/[^/]+-${escaped}\\.tgz$`).test(formulaUrl);
26
+ const bottle = /^\s*bottle do\s*$/m.test(formula);
27
+ return { status: 'no-op', published: npmTarball && bottle, version, error: null };
25
28
  }
26
29
  catch (error) {
27
30
  return { status: 'blocked', published: null, version, error: { code: 'HOMEBREW_UNAVAILABLE', message: error instanceof Error ? error.message : String(error) } };
@@ -1,5 +1,5 @@
1
1
  function deriveReleasePhase(facts) {
2
- if (!facts.localTag)
2
+ if (!facts.localTag && !facts.localTagAncestor)
3
3
  return 'unprepared';
4
4
  if (!facts.remoteBranch && !facts.remoteTag)
5
5
  return 'prepared';
@@ -18,8 +18,10 @@ async function runReleaseAction(input) {
18
18
  const before = releaseSnapshot(input.version, await input.inspect());
19
19
  if (input.action === 'inspect')
20
20
  return { status: 'no-op', changed: false, before, snapshot: before, error: null };
21
+ if (before.facts.localTagConflict)
22
+ return { status: 'failed', changed: false, before, snapshot: before, error: { code: 'GIT_TAG_CONFLICT', message: `Tag ${before.tag} is not reachable from HEAD` } };
21
23
  const allowed = input.action === 'prepare' ? before.phase === 'unprepared'
22
- : input.action === 'publish' ? ['prepared', 'partially-published'].includes(before.phase)
24
+ : input.action === 'publish' ? before.facts.localTag && ['prepared', 'partially-published'].includes(before.phase)
23
25
  : ['published', 'post-pending'].includes(before.phase);
24
26
  if (!allowed)
25
27
  return { status: before.phase === 'complete' ? 'no-op' : 'failed', changed: false, before, snapshot: before, error: before.phase === 'complete' ? null : { code: 'RELEASE_PHASE_INVALID', message: `Cannot ${input.action} from ${before.phase}` } };
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function removeDirRecursive(dir) {
4
+ if (!fs.existsSync(dir)) {
5
+ return;
6
+ }
7
+ const pending = [dir];
8
+ const directories = [];
9
+ while (pending.length > 0) {
10
+ const current = pending.pop();
11
+ const stat = fs.lstatSync(current);
12
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
13
+ fs.unlinkSync(current);
14
+ continue;
15
+ }
16
+ directories.push(current);
17
+ for (const entry of fs.readdirSync(current)) {
18
+ pending.push(path.join(current, entry));
19
+ }
20
+ }
21
+ for (const directory of directories.reverse()) {
22
+ fs.rmdirSync(directory);
23
+ }
24
+ }
25
+ //# sourceMappingURL=remove-dir.js.map
@@ -1,3 +1,5 @@
1
+ import { renderAgentClientInvocation } from '../agent-clients/invocation.js';
2
+ import { getAgentClientAdapter } from '../agent-clients/registry.js';
1
3
  const TUI_NAMES = new Set(['claude', 'codex', 'gemini', 'opencode']);
2
4
  function isTuiName(value) {
3
5
  return typeof value === 'string' && TUI_NAMES.has(value);
@@ -17,8 +19,12 @@ export function selectTui(skill, options) {
17
19
  }
18
20
  export function renderPrompt(params) {
19
21
  const suffix = [params.skill, ...params.args].join(' ').trim();
20
- if (params.tui === 'codex')
21
- return `$${suffix}`;
22
+ if (params.tui === 'codex') {
23
+ return renderAgentClientInvocation(getAgentClientAdapter('codex').invocation, {
24
+ skillName: params.skill,
25
+ args: params.args
26
+ });
27
+ }
22
28
  if (params.tui === 'gemini')
23
29
  return `/agent-infra:${suffix}`;
24
30
  return `/${suffix}`;
@@ -0,0 +1,234 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { execFile } from 'node:child_process';
3
+ import { isAgentClientId } from '../agent-clients/types.js';
4
+ import { listAgentClientAdapters, listInstalledAgentClientAdapters } from '../agent-clients/registry.js';
5
+ import { hostJoin } from './engines/wsl2-paths.js';
6
+ const DEFAULT_SANDBOX_HOOK_TIMEOUT_MS = 30_000;
7
+ function agentInfraTool(home) {
8
+ return {
9
+ id: 'agent-infra',
10
+ name: 'agent-infra CLI',
11
+ install: { type: 'npm', cmd: '@fitlab-ai/agent-infra@latest' },
12
+ sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'agent-infra'),
13
+ containerMount: '/home/devuser/.agent-infra-cli',
14
+ versionCmd: 'ai version --raw',
15
+ setupHint: 'Provides the ai and agent-infra CLI commands inside the sandbox.'
16
+ };
17
+ }
18
+ function compatAgentClientState(tools) {
19
+ return Object.fromEntries(listAgentClientAdapters().map((adapter) => [
20
+ adapter.id,
21
+ {
22
+ enabled: true,
23
+ installInSandbox: tools.includes(adapter.id)
24
+ }
25
+ ]));
26
+ }
27
+ function stableValue(value) {
28
+ if (Array.isArray(value)) {
29
+ return value.map(stableValue);
30
+ }
31
+ if (value && typeof value === 'object') {
32
+ return Object.fromEntries(Object.entries(value)
33
+ .sort(([left], [right]) => left.localeCompare(right))
34
+ .map(([key, nested]) => [key, stableValue(nested)]));
35
+ }
36
+ return value;
37
+ }
38
+ function runtimeToolProjection(tool) {
39
+ return stableValue({
40
+ id: tool.id,
41
+ install: tool.install,
42
+ containerMount: tool.containerMount,
43
+ versionCmd: tool.versionCmd,
44
+ envVars: tool.envVars ?? {},
45
+ hostPreSeedFiles: (tool.hostPreSeedFiles ?? []).map(({ sandboxName }) => ({ sandboxName })),
46
+ hostPreSeedDirs: (tool.hostPreSeedDirs ?? []).map(({ sandboxSubdir }) => ({ sandboxSubdir })),
47
+ pathRewriteFiles: tool.pathRewriteFiles ?? [],
48
+ hostLiveMounts: (tool.hostLiveMounts ?? []).map(({ containerSubpath }) => ({ containerSubpath })),
49
+ postSetupCmds: tool.postSetupCmds ?? [],
50
+ tmpfs: tool.tmpfs ?? null
51
+ });
52
+ }
53
+ function hash(value) {
54
+ return createHash('sha256').update(JSON.stringify(stableValue(value))).digest('hex');
55
+ }
56
+ function assertUniqueTools(tools) {
57
+ const seen = new Set();
58
+ for (const tool of tools) {
59
+ if (seen.has(tool.id)) {
60
+ throw new Error(`Duplicate sandbox tool id "${tool.id}"`);
61
+ }
62
+ seen.add(tool.id);
63
+ }
64
+ }
65
+ function hooksByPhase(adapters) {
66
+ const phases = [
67
+ 'prepare',
68
+ 'before-container-create',
69
+ 'after-container-start',
70
+ 'before-enter'
71
+ ];
72
+ return Object.freeze(Object.fromEntries(phases.map((phase) => [
73
+ phase,
74
+ Object.freeze(adapters.flatMap((adapter) => adapter.sandbox.hooks.filter((hook) => hook.phase === phase)))
75
+ ])));
76
+ }
77
+ function createSandboxCapabilityPlan(config) {
78
+ const state = config.agentClientState ?? compatAgentClientState(config.tools);
79
+ const selectedAgentClients = listInstalledAgentClientAdapters(state);
80
+ const context = { home: config.home, project: config.project };
81
+ const selectedTools = selectedAgentClients.map((adapter) => adapter.sandbox.createTool(context));
82
+ const allClientTools = listAgentClientAdapters().map((adapter) => adapter.sandbox.createTool(context));
83
+ const customById = new Map((config.customTools ?? []).map((tool) => [tool.id, tool]));
84
+ if (customById.size !== (config.customTools ?? []).length) {
85
+ throw new Error('Duplicate sandbox tool id in customTools');
86
+ }
87
+ for (const id of customById.keys()) {
88
+ if (id === 'agent-infra' || isAgentClientId(id)) {
89
+ throw new Error(`Custom sandbox tool id "${id}" collides with a built-in tool`);
90
+ }
91
+ }
92
+ const nonClientTools = [];
93
+ const selectedCustomTools = [];
94
+ for (const id of config.tools) {
95
+ if (isAgentClientId(id))
96
+ continue;
97
+ if (id === 'agent-infra') {
98
+ if (!nonClientTools.some((tool) => tool.id === id)) {
99
+ nonClientTools.push(agentInfraTool(config.home));
100
+ }
101
+ continue;
102
+ }
103
+ const custom = customById.get(id);
104
+ if (!custom) {
105
+ throw new Error(`Unknown sandbox tool: ${id}`);
106
+ }
107
+ selectedCustomTools.push(custom);
108
+ }
109
+ const tools = Object.freeze([...nonClientTools, ...selectedTools, ...selectedCustomTools]);
110
+ assertUniqueTools(tools);
111
+ const phaseHooks = hooksByPhase(selectedAgentClients);
112
+ const hookProjection = selectedAgentClients.flatMap((adapter) => adapter.sandbox.hooks.map((hook) => ({
113
+ adapterId: adapter.id,
114
+ id: hook.id,
115
+ phase: hook.phase,
116
+ timeoutMs: hook.timeoutMs ?? DEFAULT_SANDBOX_HOOK_TIMEOUT_MS
117
+ })));
118
+ const recoveryChecks = Object.freeze(selectedAgentClients.flatMap((adapter) => (adapter.sandbox.recoveryChecks ?? []).map((check) => Object.freeze({
119
+ adapterId: adapter.id,
120
+ check
121
+ }))));
122
+ const recoveryCheckProjection = Object.freeze(recoveryChecks.map(({ adapterId, check }) => Object.freeze({
123
+ adapterId,
124
+ id: check.id,
125
+ ...(check.when === undefined ? {} : { when: check.when }),
126
+ probe: check.probe,
127
+ finding: Object.freeze({
128
+ repairKind: check.finding.repairKind,
129
+ ...(check.finding.path === undefined ? {} : { path: check.finding.path })
130
+ }),
131
+ ...(check.repair === undefined ? {} : { repair: check.repair })
132
+ })));
133
+ const runtimeProjection = Object.freeze({
134
+ tools: Object.freeze(tools.map(runtimeToolProjection)),
135
+ selectedAgentClients: Object.freeze(selectedAgentClients.map((adapter) => adapter.id)),
136
+ hooks: Object.freeze(hookProjection),
137
+ recoveryChecks: recoveryCheckProjection
138
+ });
139
+ const cleanupInventory = Object.freeze([
140
+ agentInfraTool(config.home),
141
+ ...allClientTools,
142
+ ...customById.values()
143
+ ]);
144
+ assertUniqueTools(cleanupInventory);
145
+ return Object.freeze({
146
+ tools,
147
+ selectedAgentClients,
148
+ hooksByPhase: phaseHooks,
149
+ recoveryChecks,
150
+ aliases: Object.freeze(listAgentClientAdapters().flatMap((adapter) => adapter.sandbox.aliases)),
151
+ imageSignature: hash(tools.map(({ id, install }) => ({ id, install }))),
152
+ runtimeSignature: hash(runtimeProjection),
153
+ runtimeProjection,
154
+ cleanupInventory
155
+ });
156
+ }
157
+ function timeoutStatus(phase) {
158
+ if (phase === 'before-enter')
159
+ return 'warning';
160
+ return 'fatal';
161
+ }
162
+ async function runSandboxHooks({ hooks, phase, context, runCommand = async () => {
163
+ throw new Error('Sandbox hook command runner is unavailable');
164
+ }, scheduleTimeout = (callback, timeoutMs) => setTimeout(callback, timeoutMs), clearScheduledTimeout = (handle) => clearTimeout(handle) }) {
165
+ const results = [];
166
+ for (const hook of hooks) {
167
+ if (hook.phase !== phase)
168
+ continue;
169
+ const timeoutMs = hook.timeoutMs ?? DEFAULT_SANDBOX_HOOK_TIMEOUT_MS;
170
+ const controller = new AbortController();
171
+ let timedOut = false;
172
+ let timer;
173
+ const timeout = new Promise((resolve) => {
174
+ timer = scheduleTimeout(() => {
175
+ timedOut = true;
176
+ controller.abort();
177
+ resolve({
178
+ status: timeoutStatus(phase),
179
+ message: `Sandbox hook '${hook.id}' timed out after ${timeoutMs}ms.`
180
+ });
181
+ }, timeoutMs);
182
+ });
183
+ let result;
184
+ try {
185
+ const execution = Promise.resolve().then(() => hook.run({
186
+ ...context,
187
+ signal: controller.signal,
188
+ runCommand: (command) => runCommand(command, {
189
+ signal: controller.signal,
190
+ timeoutMs
191
+ })
192
+ }));
193
+ result = await Promise.race([execution, timeout]);
194
+ }
195
+ catch (error) {
196
+ result = {
197
+ status: timeoutStatus(phase),
198
+ message: error instanceof Error ? error.message : String(error)
199
+ };
200
+ }
201
+ finally {
202
+ clearScheduledTimeout(timer);
203
+ }
204
+ results.push({
205
+ hookId: hook.id,
206
+ phase,
207
+ status: result.status,
208
+ ...(result.message ? { message: result.message } : {})
209
+ });
210
+ if (timedOut || result.status === 'fatal' || result.status === 'hard-failure') {
211
+ break;
212
+ }
213
+ }
214
+ return results;
215
+ }
216
+ function runBoundedSandboxHookCommand(command, options) {
217
+ return new Promise((resolve, reject) => {
218
+ execFile(command.command, [...(command.args ?? [])], {
219
+ cwd: command.cwd,
220
+ env: command.env,
221
+ encoding: 'utf8',
222
+ signal: options.signal,
223
+ timeout: options.timeoutMs
224
+ }, (error, stdout) => {
225
+ if (error) {
226
+ reject(error);
227
+ return;
228
+ }
229
+ resolve({ stdout: stdout.trim() });
230
+ });
231
+ });
232
+ }
233
+ export { DEFAULT_SANDBOX_HOOK_TIMEOUT_MS, createSandboxCapabilityPlan, runBoundedSandboxHookCommand, runSandboxHooks };
234
+ //# sourceMappingURL=agent-client-reconciler.js.map
@@ -2,10 +2,11 @@ import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { removeDirRecursive } from '../../remove-dir.js';
5
6
  const PROBE_TIMEOUT_MS = 2_000;
6
7
  const READ_IMAGE_TIMEOUT_MS = 5_000;
7
8
  const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
8
- export function createLinuxClipboardAdapter({ env = process.env, execFn = execFileSync, execToFileFn = execToFile, mkdtempFn = fs.mkdtempSync, readFileFn = fs.readFileSync, rmFn = fs.rmSync } = {}) {
9
+ export function createLinuxClipboardAdapter({ env = process.env, execFn = execFileSync, execToFileFn = execToFile, mkdtempFn = fs.mkdtempSync, readFileFn = fs.readFileSync, rmFn = removeDirRecursive } = {}) {
9
10
  return {
10
11
  available() {
11
12
  const backend = selectBackend(env);