@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
package/README.md CHANGED
@@ -56,9 +56,9 @@ Once initialized, open the project in your AI TUI and install the latest skills:
56
56
  ```bash
57
57
  /import-issue 42 # AI reads the issue, creates a task, extracts requirements
58
58
  /analyze-task <task-id> # AI scans the codebase, finds the root cause, writes analysis.md
59
- /review-analysis <task-id> # AI self-reviews: "Approved. 0 blockers — proceed to design."
59
+ /review-analysis <task-id> # An isolated reviewer checks the analysis
60
60
  /plan-task <task-id> # AI proposes a fix plan
61
- /review-plan <task-id> # AI self-reviews the plan: "Approved. Ready for implementation."
61
+ /review-plan <task-id> # A fresh isolated reviewer checks the plan
62
62
  ```
63
63
 
64
64
  > **You review the plan and reply in natural language:**
@@ -72,7 +72,7 @@ Just fix it at the application layer in LoginService.
72
72
 
73
73
  ```bash
74
74
  /code-task <task-id> # AI writes the fix, adds a test for user+tag@example.com — green
75
- /review-code <task-id> # AI reviews its own code: "0 blockers, 1 minor (missing JSDoc)."
75
+ /review-code <task-id> # A fresh isolated reviewer reports one minor finding
76
76
  /code-task <task-id> # AI fixes the minor issue and re-validates
77
77
  /commit
78
78
  /create-pr <task-id> # PR opened, auto-linked to issue #42
@@ -141,7 +141,7 @@ ai init
141
141
  # or: agent-infra init
142
142
  ```
143
143
 
144
- The CLI collects project metadata, installs the `update-agent-infra` seed command for all supported AI TUIs, and generates `.agents/.airc.json`.
144
+ The CLI collects project metadata, lets you select the enabled Agent Clients, installs their `update-agent-infra` seed commands, and generates `.agents/.airc.json`.
145
145
 
146
146
  > `ai` is a shorthand for `agent-infra`. Both commands are equivalent.
147
147
 
@@ -160,11 +160,22 @@ This detects the packaged template version and renders all managed files. The sa
160
160
 
161
161
  ## Core Commands
162
162
 
163
+ Manage the four built-in Agent Clients independently from the project lifecycle:
164
+
165
+ ```bash
166
+ ai agent-client list
167
+ ai agent-client status
168
+ ai agent-client enable codex
169
+ ai agent-client disable gemini-cli
170
+ ai agent-client configure
171
+ ```
172
+
163
173
  The most-used lifecycle commands, in delivery order. The command prefix varies by TUI (`/skill` in Claude Code/OpenCode, `$skill` in Codex, `/{{project}}:skill` in Gemini CLI); the workflow semantics stay the same.
164
174
 
165
175
  | Command | Purpose |
166
176
  |---------|---------|
167
177
  | `create-task` / `import-issue` | Start a task from a description or a GitHub Issue |
178
+ | `run-task` | Resume and orchestrate the lifecycle in Claude Code with fresh isolated executors/reviewers until a safe commit or stable pause; Codex currently fails closed because its multi-agent runtime does not expose the required lifecycle events |
168
179
  | `analyze-task` → `review-analysis` | Capture scope and risks, then review the analysis |
169
180
  | `plan-task` → `review-plan` | Design the approach, then review the plan |
170
181
  | `code-task` → `review-code` | Implement and test, then run a structured code review |
@@ -197,6 +208,7 @@ In-depth guides live under [`docs/en/`](./docs/en/README.md):
197
208
  - [Architecture Overview](./docs/en/architecture.md) — bootstrap CLI, end-to-end flow, layered architecture
198
209
  - [Platform Support](./docs/en/platform-support.md) — macOS, Linux, Windows; sandbox engines and resources
199
210
  - [Sandbox](./docs/en/sandbox.md) — sandbox aliases, host-sandbox file exchange, user-level dotfiles channel
211
+ - [Benchmark Contract and Threat Model](./docs/en/benchmark.md) — versioned benchmark schemas, trust boundaries, and security guarantees
200
212
  - [Feishu Bridge](./docs/en/feishu-bridge.md) — configure the Feishu long-connection adapter, command routing, permissions, and TUI selection
201
213
  - [Built-in AI Skills](./docs/en/skills.md) — the full skill catalog by use case
202
214
  - [Custom Skills](./docs/en/custom-skills.md) — create and sync project-specific skills
package/README.zh-CN.md CHANGED
@@ -55,9 +55,9 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
55
55
  ```bash
56
56
  /import-issue 42 # AI 读取 Issue,创建任务,提取需求
57
57
  /analyze-task <task-id> # AI 扫描代码库,定位根因,输出 analysis.md
58
- /review-analysis <task-id> # AI 自审:“通过。0 阻塞项 —— 可进入方案设计。”
58
+ /review-analysis <task-id> # 隔离 reviewer 审查分析产物
59
59
  /plan-task <task-id> # AI 提出修复方案
60
- /review-plan <task-id> # AI 自审方案:“通过,可进入编码。”
60
+ /review-plan <task-id> # fresh 隔离 reviewer 审查方案
61
61
  ```
62
62
 
63
63
  > **你审查方案后用自然语言回复:**
@@ -71,7 +71,7 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
71
71
 
72
72
  ```bash
73
73
  /code-task <task-id> # AI 编写修复代码,添加 user+tag@example.com 测试 —— 通过
74
- /review-code <task-id> # AI 审查自己的实现:“0 阻塞项,1 次要(缺少 JSDoc)。”
74
+ /review-code <task-id> # fresh 隔离 reviewer 报告一个次要问题
75
75
  /code-task <task-id> # AI 修复次要问题并重新验证
76
76
  /commit
77
77
  /create-pr <task-id> # PR 已创建,自动关联 Issue #42
@@ -140,7 +140,7 @@ ai init
140
140
  # 或:agent-infra init
141
141
  ```
142
142
 
143
- CLI 会收集项目元数据,向所有支持的 AI TUI 安装 `update-agent-infra` 种子命令,并生成 `.agents/.airc.json`。
143
+ CLI 会收集项目元数据,让你选择启用的 Agent Client,为其安装 `update-agent-infra` 种子命令,并生成 `.agents/.airc.json`。
144
144
 
145
145
  > `ai` 是 `agent-infra` 的简写命令,两者等价。
146
146
 
@@ -159,11 +159,22 @@ CLI 会收集项目元数据,向所有支持的 AI TUI 安装 `update-agent-in
159
159
 
160
160
  ## 核心命令
161
161
 
162
+ 以下命令用于独立管理四个内建 Agent Client:
163
+
164
+ ```bash
165
+ ai agent-client list
166
+ ai agent-client status
167
+ ai agent-client enable codex
168
+ ai agent-client disable gemini-cli
169
+ ai agent-client configure
170
+ ```
171
+
162
172
  最常用的生命周期命令,按交付顺序排列。命令前缀因 TUI 而异(Claude Code/OpenCode 用 `/skill`,Codex 用 `$skill`,Gemini CLI 用 `/{{project}}:skill`),工作流语义保持一致。
163
173
 
164
174
  | 命令 | 用途 |
165
175
  |------|------|
166
176
  | `create-task` / `import-issue` | 从描述或 GitHub Issue 创建任务 |
177
+ | `run-task` | 在 Claude Code 中从当前状态续跑生命周期,用 fresh 隔离 executor/reviewer 推进到安全提交或稳定暂停;Codex 当前因多代理运行时未暴露所需生命周期事件而失败关闭 |
167
178
  | `analyze-task` → `review-analysis` | 明确范围与风险,再审查分析 |
168
179
  | `plan-task` → `review-plan` | 设计实现路径,再审查方案 |
169
180
  | `code-task` → `review-code` | 实现并测试,再执行结构化代码审查 |
@@ -196,6 +207,7 @@ my-project/
196
207
  - [架构概览](./docs/zh-CN/architecture.md) — 引导 CLI、端到端流程、分层架构
197
208
  - [平台支持](./docs/zh-CN/platform-support.md) — macOS、Linux、Windows;沙箱引擎与资源配置
198
209
  - [沙箱](./docs/zh-CN/sandbox.md) — 沙箱 aliases、宿主-沙箱文件交换、用户级 dotfiles 通道
210
+ - [Benchmark 契约与威胁模型](./docs/zh-CN/benchmark.md) — 版本化 benchmark schema、信任边界与安全保证
199
211
  - [飞书桥接](./docs/zh-CN/feishu-bridge.md) — 配置飞书长连接 adapter、命令路由、权限和 TUI 选择
200
212
  - [内置 AI Skills](./docs/zh-CN/skills.md) — 按使用场景分组的完整 skill 清单
201
213
  - [自定义 Skills](./docs/zh-CN/custom-skills.md) — 创建并同步项目专属 skill
package/bin/cli.ts CHANGED
@@ -15,6 +15,7 @@ const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructur
15
15
  Usage: ai <command> [options]
16
16
 
17
17
  Commands:
18
+ agent-client List, inspect, enable, disable, or configure Agent Clients
18
19
  cp <ssh-alias> Copy local clipboard image to a remote macOS clipboard or Linux sandbox over SSH
19
20
  decide Record a ruling; code-stage decisions require explicit implementation intent
20
21
  help Show this help message
@@ -71,6 +72,17 @@ async function importCommand(importPath: string) {
71
72
  }
72
73
 
73
74
  switch (command) {
75
+ case 'agent-client': {
76
+ const imported = await importCommand('../lib/agent-client.ts');
77
+ if (!imported) break;
78
+ const { cmdAgentClient } = imported;
79
+ const code = await cmdAgentClient(process.argv.slice(3)).catch((e: unknown) => {
80
+ process.stderr.write(`Error: ${errorMessage(e)}\n`);
81
+ return 1;
82
+ });
83
+ if (code) process.exitCode = code;
84
+ break;
85
+ }
74
86
  case 'cp': {
75
87
  const imported = await importCommand('../lib/cp.ts');
76
88
  if (!imported) break;
package/dist/bin/cli.js CHANGED
@@ -19,6 +19,7 @@ const USAGE = `agent-infra ${VERSION} - bootstrap AI collaboration infrastructur
19
19
  Usage: ai <command> [options]
20
20
 
21
21
  Commands:
22
+ agent-client List, inspect, enable, disable, or configure Agent Clients
22
23
  cp <ssh-alias> Copy local clipboard image to a remote macOS clipboard or Linux sandbox over SSH
23
24
  decide Record a ruling; code-stage decisions require explicit implementation intent
24
25
  help Show this help message
@@ -69,6 +70,19 @@ async function importCommand(importPath) {
69
70
  }
70
71
  }
71
72
  switch (command) {
73
+ case 'agent-client': {
74
+ const imported = await importCommand('../lib/agent-client.ts');
75
+ if (!imported)
76
+ break;
77
+ const { cmdAgentClient } = imported;
78
+ const code = await cmdAgentClient(process.argv.slice(3)).catch((e) => {
79
+ process.stderr.write(`Error: ${errorMessage(e)}\n`);
80
+ return 1;
81
+ });
82
+ if (code)
83
+ process.exitCode = code;
84
+ break;
85
+ }
72
86
  case 'cp': {
73
87
  const imported = await importCommand('../lib/cp.ts');
74
88
  if (!imported)
@@ -6,6 +6,11 @@ if (major < 22 || (major === 22 && minor < 9)) {
6
6
  }
7
7
  const command = process.argv[2] || '';
8
8
  switch (command) {
9
+ case 'agent-client': {
10
+ const { agentClient } = await import('../lib/internal/agent-client.js');
11
+ agentClient(process.argv.slice(3));
12
+ break;
13
+ }
9
14
  case 'git-workflow': {
10
15
  const { gitWorkflow } = await import('../lib/internal/git-workflow.js');
11
16
  gitWorkflow(process.argv.slice(3));
@@ -66,6 +71,16 @@ switch (command) {
66
71
  taskArtifact(process.argv.slice(3));
67
72
  break;
68
73
  }
74
+ case 'task-orchestration': {
75
+ const { taskOrchestration } = await import('../lib/internal/task-orchestration.js');
76
+ taskOrchestration(process.argv.slice(3));
77
+ break;
78
+ }
79
+ case 'task-review': {
80
+ const { taskReview } = await import('../lib/internal/task-review.js');
81
+ taskReview(process.argv.slice(3));
82
+ break;
83
+ }
69
84
  case 'task-event': {
70
85
  const { taskEvent } = await import('../lib/internal/task-event.js');
71
86
  taskEvent(process.argv.slice(3));
@@ -0,0 +1,163 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { AgentClientConfigError, normalizeAgentClients } from './agent-clients/config.js';
4
+ import { applyAgentClientReconciliation, planAgentClientReconciliation } from './agent-clients/reconcile.js';
5
+ import { listAgentClientAdapters } from './agent-clients/registry.js';
6
+ import { AGENT_CLIENT_IDS, isAgentClientId } from './agent-clients/types.js';
7
+ import { closePrompt, multiSelect } from './prompt.js';
8
+ import { resolveTemplateDir } from './paths.js';
9
+ const USAGE = `Usage: ai agent-client <operation>
10
+
11
+ Operations:
12
+ list List built-in Agent Clients
13
+ status Show project integration and sandbox state
14
+ enable <client-id> Enable project integration only
15
+ disable <client-id> Disable project integration only
16
+ configure Configure integration and sandbox state interactively
17
+ `;
18
+ class AgentClientCommandError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(`${code}: ${message}`);
22
+ this.name = 'AgentClientCommandError';
23
+ this.code = code;
24
+ }
25
+ }
26
+ function readConfig(projectRoot) {
27
+ const configPath = path.join(projectRoot, '.agents', '.airc.json');
28
+ if (!fs.existsSync(configPath)) {
29
+ throw new AgentClientCommandError('AGENT_CLIENT_CONFIG_NOT_FOUND', 'No .agents/.airc.json found. Run "ai init" first.');
30
+ }
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
34
+ }
35
+ catch (error) {
36
+ throw new AgentClientCommandError('AGENT_CLIENT_CONFIG_INVALID', error instanceof Error ? error.message : String(error));
37
+ }
38
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
39
+ throw new AgentClientCommandError('AGENT_CLIENT_CONFIG_INVALID', 'configuration root must be an object');
40
+ }
41
+ return parsed;
42
+ }
43
+ function choices() {
44
+ return listAgentClientAdapters().map((adapter) => ({
45
+ id: adapter.id,
46
+ label: adapter.displayName
47
+ }));
48
+ }
49
+ function selected(state, field) {
50
+ return AGENT_CLIENT_IDS.filter((id) => state[id][field]);
51
+ }
52
+ function workflowPlan(projectRoot, config, mutation) {
53
+ const templateRoot = resolveTemplateDir();
54
+ if (!templateRoot) {
55
+ throw new AgentClientCommandError('AGENT_CLIENT_TEMPLATE_NOT_FOUND', 'Template directory not found');
56
+ }
57
+ const platform = config.platform;
58
+ const platformType = typeof platform === 'object' && platform !== null && 'type' in platform
59
+ ? String(platform.type ?? 'github')
60
+ : 'github';
61
+ return planAgentClientReconciliation({
62
+ config,
63
+ mutation,
64
+ projectRoot,
65
+ templateRoot,
66
+ platformType,
67
+ language: String(config.language ?? 'en')
68
+ });
69
+ }
70
+ function printState(state) {
71
+ for (const adapter of listAgentClientAdapters()) {
72
+ const value = state[adapter.id];
73
+ process.stdout.write(` ${adapter.id}: enabled=${String(value.enabled)} installInSandbox=${String(value.installInSandbox)}\n`);
74
+ }
75
+ }
76
+ async function cmdAgentClient(args) {
77
+ const operation = args[0];
78
+ if (!operation || operation === '--help' || operation === '-h' || operation === 'help') {
79
+ process.stdout.write(USAGE);
80
+ return 0;
81
+ }
82
+ if (operation === 'list') {
83
+ if (args.length !== 1) {
84
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'list accepts no arguments');
85
+ }
86
+ for (const adapter of listAgentClientAdapters()) {
87
+ process.stdout.write(`${adapter.id}\t${adapter.displayName}\t${adapter.invocation}\tcommands=${adapter.capabilities.commands.level}\tsandbox=${adapter.capabilities.sandbox.level}\n`);
88
+ }
89
+ return 0;
90
+ }
91
+ const projectRoot = process.cwd();
92
+ const config = readConfig(projectRoot);
93
+ try {
94
+ if (operation === 'status') {
95
+ if (args.length !== 1) {
96
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'status accepts no arguments');
97
+ }
98
+ const plan = workflowPlan(projectRoot, config, { type: 'none' });
99
+ process.stdout.write(`source: ${plan.source}\n`);
100
+ printState(plan.desired);
101
+ for (const seed of plan.seedOperations) {
102
+ process.stdout.write(` seed ${seed.target}: ${seed.kind}\n`);
103
+ }
104
+ for (const diagnostic of plan.diagnostics) {
105
+ process.stdout.write(` diagnostic ${diagnostic.code} at ${diagnostic.path}\n`);
106
+ }
107
+ return 0;
108
+ }
109
+ if (operation === 'enable' || operation === 'disable') {
110
+ if (args.length !== 2) {
111
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', `${operation} requires exactly one client id`);
112
+ }
113
+ const id = args[1];
114
+ if (!isAgentClientId(id)) {
115
+ throw new AgentClientCommandError('UNKNOWN_AGENT_CLIENT', `Unknown Agent Client '${String(id)}'`);
116
+ }
117
+ const plan = workflowPlan(projectRoot, config, {
118
+ type: 'set-enabled',
119
+ id,
120
+ value: operation === 'enable'
121
+ });
122
+ process.stdout.write('Before:\n');
123
+ printState(plan.before);
124
+ const result = applyAgentClientReconciliation(plan);
125
+ process.stdout.write('After:\n');
126
+ printState(plan.desired);
127
+ process.stdout.write(`Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`);
128
+ return 0;
129
+ }
130
+ if (operation === 'configure') {
131
+ if (args.length !== 1) {
132
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'configure accepts no arguments');
133
+ }
134
+ const normalized = normalizeAgentClients(config);
135
+ let enabled;
136
+ let installed;
137
+ try {
138
+ enabled = await multiSelect('Agent Client project integrations', choices(), selected(normalized.state, 'enabled'));
139
+ installed = await multiSelect('Agent Clients installed in sandbox', choices(), selected(normalized.state, 'installInSandbox'));
140
+ }
141
+ finally {
142
+ closePrompt();
143
+ }
144
+ const state = Object.fromEntries(AGENT_CLIENT_IDS.map((id) => [id, {
145
+ enabled: enabled.includes(id),
146
+ installInSandbox: installed.includes(id)
147
+ }]));
148
+ const plan = workflowPlan(projectRoot, config, { type: 'replace-state', state });
149
+ const result = applyAgentClientReconciliation(plan);
150
+ process.stdout.write(`Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`);
151
+ return 0;
152
+ }
153
+ throw new AgentClientCommandError('AGENT_CLIENT_OPERATION_INVALID', `Unknown operation '${operation}'`);
154
+ }
155
+ catch (error) {
156
+ if (error instanceof AgentClientConfigError) {
157
+ throw new AgentClientCommandError(error.code, error.message);
158
+ }
159
+ throw error;
160
+ }
161
+ }
162
+ export { AgentClientCommandError, cmdAgentClient };
163
+ //# sourceMappingURL=agent-client.js.map
@@ -0,0 +1,294 @@
1
+ import { AGENT_CLIENT_CAPABILITY_IDS, AGENT_CLIENT_SUPPORT_LEVELS, isAgentClientId } from './types.js';
2
+ import { SANDBOX_HOOK_PHASES } from '../sandbox/tool-types.js';
3
+ const PROJECT_ASSET_CATEGORIES = ['managed', 'merged', 'ejected'];
4
+ const MAX_SANDBOX_HOOK_TIMEOUT_MS = 300_000;
5
+ const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
6
+ function freezeRecoveryProbe(id, checkId, value) {
7
+ if (!value
8
+ || typeof value.script !== 'string'
9
+ || value.script.trim() === ''
10
+ || !Array.isArray(value.args)
11
+ || value.args.some((arg) => typeof arg !== 'string')
12
+ || (value.user !== undefined && (typeof value.user !== 'string' || value.user === ''))) {
13
+ throw new Error(`Agent Client '${id}' recovery check '${checkId}' has an invalid probe`);
14
+ }
15
+ return Object.freeze({
16
+ script: value.script,
17
+ args: Object.freeze([...value.args]),
18
+ ...(value.user === undefined ? {} : { user: value.user })
19
+ });
20
+ }
21
+ function freezeTool(tool) {
22
+ if (typeof tool.id !== 'string' || !TOOL_ID_PATTERN.test(tool.id)) {
23
+ throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
24
+ }
25
+ if (!tool.install?.cmd || !['npm', 'shell'].includes(tool.install.type)) {
26
+ throw new Error(`Sandbox tool '${tool.id}' has invalid install`);
27
+ }
28
+ if (!tool.containerMount.startsWith('/')) {
29
+ throw new Error(`Sandbox tool '${tool.id}' containerMount must be absolute`);
30
+ }
31
+ if (!tool.versionCmd) {
32
+ throw new Error(`Sandbox tool '${tool.id}' requires versionCmd`);
33
+ }
34
+ return Object.freeze({
35
+ ...tool,
36
+ install: Object.freeze({ ...tool.install }),
37
+ ...(tool.envVars ? { envVars: Object.freeze({ ...tool.envVars }) } : {}),
38
+ ...(tool.hostPreSeedFiles ? {
39
+ hostPreSeedFiles: Object.freeze(tool.hostPreSeedFiles.map((entry) => Object.freeze({ ...entry })))
40
+ } : {}),
41
+ ...(tool.hostPreSeedDirs ? {
42
+ hostPreSeedDirs: Object.freeze(tool.hostPreSeedDirs.map((entry) => Object.freeze({ ...entry })))
43
+ } : {}),
44
+ ...(tool.pathRewriteFiles ? { pathRewriteFiles: Object.freeze([...tool.pathRewriteFiles]) } : {}),
45
+ ...(tool.hostLiveMounts ? {
46
+ hostLiveMounts: Object.freeze(tool.hostLiveMounts.map((entry) => Object.freeze({ ...entry })))
47
+ } : {}),
48
+ ...(tool.postSetupCmds ? { postSetupCmds: Object.freeze([...tool.postSetupCmds]) } : {}),
49
+ ...(tool.tmpfs ? {
50
+ tmpfs: Object.freeze({
51
+ ...tool.tmpfs,
52
+ ...(tool.tmpfs.seed ? { seed: Object.freeze([...tool.tmpfs.seed]) } : {})
53
+ })
54
+ } : {})
55
+ });
56
+ }
57
+ function freezeAliases(id, aliases) {
58
+ const names = new Set();
59
+ return Object.freeze(aliases.map((alias) => {
60
+ if (!alias.name || !alias.command || names.has(alias.name)) {
61
+ throw new Error(`Agent Client '${id}' has invalid or duplicate sandbox alias '${alias.name}'`);
62
+ }
63
+ names.add(alias.name);
64
+ return Object.freeze({ ...alias });
65
+ }));
66
+ }
67
+ function isProjectRelativeLiteral(value) {
68
+ if (value.length === 0
69
+ || value.startsWith('/')
70
+ || /^[a-zA-Z]:/.test(value)
71
+ || value.includes('\\')
72
+ || /[*?[\]{}]/.test(value)) {
73
+ return false;
74
+ }
75
+ return !value.split('/').some((segment) => segment === '..');
76
+ }
77
+ function defineAgentClientAdapter(candidate) {
78
+ if (!isAgentClientId(candidate.id)) {
79
+ throw new Error(`Unknown Agent Client ID '${String(candidate.id)}'`);
80
+ }
81
+ if (typeof candidate.displayName !== 'string' || candidate.displayName.trim() === '') {
82
+ throw new Error(`Agent Client '${candidate.id}' requires a display name`);
83
+ }
84
+ if (typeof candidate.invocation !== 'string'
85
+ || candidate.invocation.trim() === ''
86
+ || /[\r\n]/.test(candidate.invocation)) {
87
+ throw new Error(`Agent Client '${candidate.id}' requires a single-line invocation`);
88
+ }
89
+ const invocationPlaceholders = [
90
+ ...candidate.invocation.matchAll(/\$\{([^}]+)\}/g)
91
+ ].map((match) => match[1]);
92
+ if (!invocationPlaceholders.includes('skillName')
93
+ || invocationPlaceholders.some((placeholder) => placeholder !== 'skillName' && placeholder !== 'projectName')
94
+ || candidate.invocation
95
+ .replaceAll('${skillName}', '')
96
+ .replaceAll('${projectName}', '')
97
+ .includes('${')) {
98
+ throw new Error(`Agent Client '${candidate.id}' has an invalid invocation`);
99
+ }
100
+ if (typeof candidate.capabilities !== 'object'
101
+ || candidate.capabilities === null
102
+ || Array.isArray(candidate.capabilities)) {
103
+ throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
104
+ }
105
+ const capabilityKeys = Object.keys(candidate.capabilities);
106
+ if (capabilityKeys.length !== AGENT_CLIENT_CAPABILITY_IDS.length
107
+ || AGENT_CLIENT_CAPABILITY_IDS.some((capability) => !Object.prototype.hasOwnProperty.call(candidate.capabilities, capability))) {
108
+ throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
109
+ }
110
+ const capabilities = Object.fromEntries(AGENT_CLIENT_CAPABILITY_IDS.map((capability) => {
111
+ const support = candidate.capabilities[capability];
112
+ if (typeof support !== 'object'
113
+ || support === null
114
+ || !AGENT_CLIENT_SUPPORT_LEVELS.includes(support.level)) {
115
+ throw new Error(`Agent Client '${candidate.id}' has invalid support for '${capability}'`);
116
+ }
117
+ return [capability, Object.freeze({ level: support.level })];
118
+ }));
119
+ if (typeof candidate.project !== 'object'
120
+ || candidate.project === null
121
+ || Array.isArray(candidate.project)) {
122
+ throw new Error(`Agent Client '${candidate.id}' has an invalid project descriptor`);
123
+ }
124
+ const ownedPathPrefixes = candidate.project.ownedPathPrefixes;
125
+ if (!Array.isArray(ownedPathPrefixes) || ownedPathPrefixes.length === 0) {
126
+ throw new Error(`Agent Client '${candidate.id}' requires owned path prefixes`);
127
+ }
128
+ const paths = ownedPathPrefixes.map((prefix) => {
129
+ if (typeof prefix !== 'string'
130
+ || !isProjectRelativeLiteral(prefix)
131
+ || !prefix.endsWith('/')) {
132
+ throw new Error(`Agent Client '${candidate.id}' has invalid owned path prefix '${String(prefix)}'`);
133
+ }
134
+ return prefix;
135
+ });
136
+ if (new Set(paths).size !== paths.length) {
137
+ throw new Error(`Agent Client '${candidate.id}' has duplicate owned path prefixes`);
138
+ }
139
+ const projectAssets = Object.fromEntries(PROJECT_ASSET_CATEGORIES.map((category) => {
140
+ const values = candidate.project[category];
141
+ if (!Array.isArray(values)) {
142
+ throw new Error(`Agent Client '${candidate.id}' has invalid '${category}' project assets`);
143
+ }
144
+ const assets = values.map((asset) => {
145
+ if (typeof asset !== 'string'
146
+ || !isProjectRelativeLiteral(asset)
147
+ || !paths.some((prefix) => asset === prefix.slice(0, -1) || asset.startsWith(prefix))) {
148
+ throw new Error(`Agent Client '${candidate.id}' has invalid '${category}' project asset '${String(asset)}'`);
149
+ }
150
+ return asset;
151
+ });
152
+ if (new Set(assets).size !== assets.length) {
153
+ throw new Error(`Agent Client '${candidate.id}' has duplicate '${category}' project assets`);
154
+ }
155
+ return [category, Object.freeze(assets)];
156
+ }));
157
+ const categorizedAssets = PROJECT_ASSET_CATEGORIES.flatMap((category) => projectAssets[category].map((asset) => ({ asset, category })));
158
+ const seenAssets = new Map();
159
+ for (const { asset, category } of categorizedAssets) {
160
+ const previous = seenAssets.get(asset);
161
+ if (previous) {
162
+ throw new Error(`Agent Client '${candidate.id}' project asset '${asset}' overlaps '${previous}' and '${category}'`);
163
+ }
164
+ seenAssets.set(asset, category);
165
+ }
166
+ if (!Array.isArray(candidate.project.seedCommands)) {
167
+ throw new Error(`Agent Client '${candidate.id}' has invalid seed commands`);
168
+ }
169
+ const seedTargets = new Set();
170
+ const seedCommands = candidate.project.seedCommands.map((seed) => {
171
+ if (!seed || typeof seed !== 'object') {
172
+ throw new Error(`Agent Client '${candidate.id}' has an invalid seed command`);
173
+ }
174
+ const placeholders = [
175
+ ...seed.target.matchAll(/\$\{([^}]+)\}/g)
176
+ ].map((match) => match[1]);
177
+ const targetProbe = seed.target.replaceAll('${projectName}', 'project');
178
+ if (placeholders.some((placeholder) => placeholder !== 'projectName')
179
+ || targetProbe.includes('${')
180
+ || !isProjectRelativeLiteral(targetProbe)
181
+ || !paths.some((prefix) => targetProbe === prefix.slice(0, -1) || targetProbe.startsWith(prefix))
182
+ || seedTargets.has(seed.target)) {
183
+ throw new Error(`Agent Client '${candidate.id}' has invalid seed target '${String(seed.target)}'`);
184
+ }
185
+ if (!seed.templates
186
+ || typeof seed.templates.en !== 'string'
187
+ || typeof seed.templates['zh-CN'] !== 'string'
188
+ || !isProjectRelativeLiteral(seed.templates.en)
189
+ || !isProjectRelativeLiteral(seed.templates['zh-CN'])) {
190
+ throw new Error(`Agent Client '${candidate.id}' has invalid seed templates`);
191
+ }
192
+ seedTargets.add(seed.target);
193
+ return Object.freeze({
194
+ templates: Object.freeze({ ...seed.templates }),
195
+ target: seed.target
196
+ });
197
+ });
198
+ if (!candidate.sandbox || typeof candidate.sandbox.createTool !== 'function') {
199
+ throw new Error(`Agent Client '${candidate.id}' requires a sandbox descriptor`);
200
+ }
201
+ const hookIds = new Set();
202
+ const hooks = Object.freeze(candidate.sandbox.hooks.map((hook) => {
203
+ if (!hook.id
204
+ || hookIds.has(hook.id)
205
+ || !SANDBOX_HOOK_PHASES.includes(hook.phase)
206
+ || typeof hook.run !== 'function') {
207
+ throw new Error(`Agent Client '${candidate.id}' has an invalid sandbox hook`);
208
+ }
209
+ if (hook.timeoutMs !== undefined
210
+ && (!Number.isInteger(hook.timeoutMs)
211
+ || hook.timeoutMs <= 0
212
+ || hook.timeoutMs > MAX_SANDBOX_HOOK_TIMEOUT_MS)) {
213
+ throw new Error(`Agent Client '${candidate.id}' hook '${hook.id}' has invalid timeoutMs`);
214
+ }
215
+ hookIds.add(hook.id);
216
+ return Object.freeze({ ...hook });
217
+ }));
218
+ const recoveryCheckIds = new Set();
219
+ if (candidate.sandbox.recoveryChecks !== undefined
220
+ && !Array.isArray(candidate.sandbox.recoveryChecks)) {
221
+ throw new Error(`Agent Client '${candidate.id}' has invalid recovery checks`);
222
+ }
223
+ const recoveryChecks = Object.freeze((candidate.sandbox.recoveryChecks ?? []).map((check) => {
224
+ if (!check
225
+ || typeof check.id !== 'string'
226
+ || !TOOL_ID_PATTERN.test(check.id)
227
+ || recoveryCheckIds.has(check.id)) {
228
+ throw new Error(`Agent Client '${candidate.id}' has an invalid recovery check`);
229
+ }
230
+ const probe = freezeRecoveryProbe(candidate.id, check.id, check.probe);
231
+ const when = check.when === undefined
232
+ ? undefined
233
+ : freezeRecoveryProbe(candidate.id, check.id, check.when);
234
+ if (!check.finding
235
+ || !['permissions', 'builtin-link', 'hard-failure'].includes(check.finding.repairKind)
236
+ || typeof check.finding.message !== 'string'
237
+ || check.finding.message.trim() === ''
238
+ || (check.finding.path !== undefined
239
+ && (typeof check.finding.path !== 'string' || !check.finding.path.startsWith('/')))
240
+ || (check.finding.repairKind === 'permissions'
241
+ && check.finding.path === undefined)) {
242
+ throw new Error(`Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid finding`);
243
+ }
244
+ let repair;
245
+ if (check.repair !== undefined) {
246
+ if (typeof check.repair.user !== 'string'
247
+ || check.repair.user === ''
248
+ || typeof check.repair.command !== 'string'
249
+ || check.repair.command === ''
250
+ || !Array.isArray(check.repair.args)
251
+ || check.repair.args.some((arg) => typeof arg !== 'string')) {
252
+ throw new Error(`Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid repair`);
253
+ }
254
+ repair = Object.freeze({
255
+ user: check.repair.user,
256
+ command: check.repair.command,
257
+ args: Object.freeze([...check.repair.args])
258
+ });
259
+ }
260
+ if (check.finding.repairKind === 'builtin-link' && repair === undefined) {
261
+ throw new Error(`Agent Client '${candidate.id}' recovery check '${check.id}' requires a repair`);
262
+ }
263
+ recoveryCheckIds.add(check.id);
264
+ return Object.freeze({
265
+ id: check.id,
266
+ ...(when === undefined ? {} : { when }),
267
+ probe,
268
+ finding: Object.freeze({ ...check.finding }),
269
+ ...(repair === undefined ? {} : { repair })
270
+ });
271
+ }));
272
+ const aliases = freezeAliases(candidate.id, candidate.sandbox.aliases);
273
+ const createTool = (context) => {
274
+ const tool = freezeTool(candidate.sandbox.createTool(context));
275
+ if (tool.id !== candidate.id) {
276
+ throw new Error(`Agent Client '${candidate.id}' sandbox tool id must match the adapter id`);
277
+ }
278
+ return tool;
279
+ };
280
+ return Object.freeze({
281
+ id: candidate.id,
282
+ displayName: candidate.displayName,
283
+ invocation: candidate.invocation,
284
+ capabilities: Object.freeze(capabilities),
285
+ project: Object.freeze({
286
+ ownedPathPrefixes: Object.freeze(paths),
287
+ ...projectAssets,
288
+ seedCommands: Object.freeze(seedCommands)
289
+ }),
290
+ sandbox: Object.freeze({ createTool, aliases, hooks, recoveryChecks })
291
+ });
292
+ }
293
+ export { defineAgentClientAdapter };
294
+ //# sourceMappingURL=adapter.js.map