@fitlab-ai/agent-infra 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +16 -4
  2. package/README.zh-CN.md +16 -4
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +15 -0
  6. package/dist/lib/agent-client.js +163 -0
  7. package/dist/lib/agent-clients/adapter.js +294 -0
  8. package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
  9. package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
  10. package/dist/lib/agent-clients/adapters/codex.js +54 -0
  11. package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
  12. package/dist/lib/agent-clients/adapters/index.js +20 -0
  13. package/dist/lib/agent-clients/adapters/opencode.js +64 -0
  14. package/dist/lib/agent-clients/config.js +177 -0
  15. package/dist/lib/agent-clients/custom-tuis.js +99 -0
  16. package/dist/lib/agent-clients/invocation.js +9 -0
  17. package/dist/lib/agent-clients/next-steps.js +38 -0
  18. package/dist/lib/agent-clients/project-assets.js +45 -0
  19. package/dist/lib/agent-clients/reconcile.js +235 -0
  20. package/dist/lib/agent-clients/registry.js +72 -0
  21. package/dist/lib/agent-clients/schema.js +57 -0
  22. package/dist/lib/agent-clients/types.js +29 -0
  23. package/dist/lib/builtin-tuis.js +9 -14
  24. package/dist/lib/defaults.json +85 -73
  25. package/dist/lib/git/review-snapshot.js +25 -10
  26. package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
  27. package/dist/lib/init.js +47 -91
  28. package/dist/lib/internal/agent-client.js +117 -0
  29. package/dist/lib/internal/git-workflow.js +1 -1
  30. package/dist/lib/internal/release-workflow.js +71 -16
  31. package/dist/lib/internal/task-orchestration.js +151 -0
  32. package/dist/lib/internal/task-review.js +65 -0
  33. package/dist/lib/merge.js +2 -1
  34. package/dist/lib/platform/adapters.js +38 -1
  35. package/dist/lib/platform/change-request-git-evidence.js +113 -0
  36. package/dist/lib/platform/merged-pr-equivalence.js +41 -11
  37. package/dist/lib/platform/pr-checks.js +38 -10
  38. package/dist/lib/platform/pull-requests.js +110 -6
  39. package/dist/lib/platform/releases.js +1 -1
  40. package/dist/lib/platform/verification-required.js +8 -10
  41. package/dist/lib/prompt.js +15 -3
  42. package/dist/lib/release/channels.js +4 -1
  43. package/dist/lib/release/workflow.js +4 -2
  44. package/dist/lib/remove-dir.js +25 -0
  45. package/dist/lib/run/tui.js +8 -2
  46. package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
  47. package/dist/lib/sandbox/clipboard/linux.js +2 -1
  48. package/dist/lib/sandbox/commands/create.js +76 -180
  49. package/dist/lib/sandbox/commands/enter.js +17 -1
  50. package/dist/lib/sandbox/commands/prune.js +2 -2
  51. package/dist/lib/sandbox/commands/rm.js +3 -2
  52. package/dist/lib/sandbox/config.js +21 -4
  53. package/dist/lib/sandbox/constants.js +3 -0
  54. package/dist/lib/sandbox/dockerfile.js +2 -1
  55. package/dist/lib/sandbox/managed-fs.js +10 -5
  56. package/dist/lib/sandbox/recovery.js +52 -36
  57. package/dist/lib/sandbox/shell.js +1 -1
  58. package/dist/lib/sandbox/tool-types.js +8 -0
  59. package/dist/lib/sandbox/tools.js +2 -126
  60. package/dist/lib/task/artifact-lifecycle.js +1 -49
  61. package/dist/lib/task/commands/decisions.js +50 -20
  62. package/dist/lib/task/delegation-receipts.js +107 -0
  63. package/dist/lib/task/events.js +100 -1
  64. package/dist/lib/task/orchestration.js +420 -0
  65. package/dist/lib/task/review-artifacts.js +143 -59
  66. package/dist/lib/task/review-finalization.js +166 -0
  67. package/dist/lib/task/review-fingerprint.js +4 -0
  68. package/dist/lib/task/verification-checks.js +2 -1
  69. package/dist/lib/task/verification-engine.js +156 -28
  70. package/dist/lib/task/verification-review.js +1 -1
  71. package/dist/lib/task/verification.js +3 -1
  72. package/dist/lib/task/workspace-snapshot.js +34 -0
  73. package/dist/lib/update.js +42 -161
  74. package/lib/agent-client.ts +214 -0
  75. package/lib/agent-clients/adapter.ts +461 -0
  76. package/lib/agent-clients/adapters/claude-code.ts +70 -0
  77. package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
  78. package/lib/agent-clients/adapters/codex.ts +58 -0
  79. package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
  80. package/lib/agent-clients/adapters/index.ts +27 -0
  81. package/lib/agent-clients/adapters/opencode.ts +66 -0
  82. package/lib/agent-clients/config.ts +278 -0
  83. package/lib/agent-clients/custom-tuis.ts +149 -0
  84. package/lib/agent-clients/invocation.ts +19 -0
  85. package/lib/agent-clients/next-steps.ts +72 -0
  86. package/lib/agent-clients/project-assets.ts +96 -0
  87. package/lib/agent-clients/reconcile.ts +324 -0
  88. package/lib/agent-clients/registry.ts +144 -0
  89. package/lib/agent-clients/schema.ts +62 -0
  90. package/lib/agent-clients/types.ts +78 -0
  91. package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
  92. package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
  93. package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
  94. package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
  95. package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
  96. package/lib/builtin-tuis.ts +16 -15
  97. package/lib/defaults.json +24 -12
  98. package/lib/git/review-snapshot.ts +39 -9
  99. package/lib/git/reviewed-commit-equivalence.ts +135 -0
  100. package/lib/init.ts +53 -121
  101. package/lib/internal/agent-client.ts +131 -0
  102. package/lib/internal/git-workflow.ts +1 -1
  103. package/lib/internal/release-workflow.ts +68 -16
  104. package/lib/internal/task-orchestration.ts +132 -0
  105. package/lib/internal/task-review.ts +65 -0
  106. package/lib/merge.ts +2 -1
  107. package/lib/platform/adapters.ts +142 -3
  108. package/lib/platform/change-request-git-evidence.ts +159 -0
  109. package/lib/platform/merged-pr-equivalence.ts +51 -14
  110. package/lib/platform/pr-checks.ts +50 -22
  111. package/lib/platform/pull-requests.ts +134 -25
  112. package/lib/platform/releases.ts +1 -1
  113. package/lib/platform/verification-required.ts +12 -10
  114. package/lib/prompt.ts +16 -3
  115. package/lib/release/channels.ts +4 -1
  116. package/lib/release/workflow.ts +4 -3
  117. package/lib/remove-dir.ts +28 -0
  118. package/lib/run/tui.ts +9 -1
  119. package/lib/sandbox/agent-client-reconciler.ts +378 -0
  120. package/lib/sandbox/clipboard/linux.ts +2 -1
  121. package/lib/sandbox/commands/create.ts +111 -192
  122. package/lib/sandbox/commands/enter.ts +23 -1
  123. package/lib/sandbox/commands/prune.ts +2 -2
  124. package/lib/sandbox/commands/rm.ts +3 -2
  125. package/lib/sandbox/config.ts +31 -4
  126. package/lib/sandbox/constants.ts +6 -0
  127. package/lib/sandbox/dockerfile.ts +2 -1
  128. package/lib/sandbox/managed-fs.ts +18 -5
  129. package/lib/sandbox/recovery.ts +98 -67
  130. package/lib/sandbox/shell.ts +1 -1
  131. package/lib/sandbox/tool-types.ts +143 -0
  132. package/lib/sandbox/tools.ts +5 -157
  133. package/lib/task/artifact-lifecycle.ts +1 -38
  134. package/lib/task/commands/decisions.ts +53 -20
  135. package/lib/task/delegation-receipts.ts +170 -0
  136. package/lib/task/events.ts +110 -2
  137. package/lib/task/orchestration.ts +543 -0
  138. package/lib/task/review-artifacts.ts +201 -69
  139. package/lib/task/review-finalization.ts +275 -0
  140. package/lib/task/review-fingerprint.ts +5 -0
  141. package/lib/task/verification-checks.ts +2 -1
  142. package/lib/task/verification-engine.ts +177 -28
  143. package/lib/task/verification-review.ts +1 -0
  144. package/lib/task/verification.ts +5 -2
  145. package/lib/task/workspace-snapshot.ts +36 -0
  146. package/lib/update.ts +48 -184
  147. package/package.json +2 -2
  148. package/templates/.agents/README.en.md +21 -20
  149. package/templates/.agents/README.zh-CN.md +21 -20
  150. package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
  151. package/templates/.agents/rules/README.en.md +2 -0
  152. package/templates/.agents/rules/README.zh-CN.md +2 -0
  153. package/templates/.agents/rules/commit-and-pr.en.md +1 -0
  154. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
  155. package/templates/.agents/rules/human-decision-context.en.md +14 -12
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
  157. package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
  158. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
  159. package/templates/.agents/rules/next-step-output.en.md +16 -0
  160. package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
  161. package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
  162. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
  163. package/templates/.agents/rules/release-commands.github.en.md +11 -5
  164. package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -5
  165. package/templates/.agents/rules/review-handshake.en.md +4 -4
  166. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  167. package/templates/.agents/rules/review-method.en.md +113 -0
  168. package/templates/.agents/rules/review-method.zh-CN.md +98 -0
  169. package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
  170. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
  171. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  172. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
  173. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
  174. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
  175. package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
  176. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
  177. package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
  178. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
  179. package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
  180. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
  181. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  182. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  183. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
  184. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
  185. package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
  186. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
  187. package/templates/.agents/skills/commit/SKILL.en.md +11 -1
  188. package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
  189. package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
  190. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
  191. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  192. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  193. package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
  194. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
  195. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
  196. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
  197. package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
  198. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
  199. package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
  200. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
  201. package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  204. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  205. package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
  206. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
  207. package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
  208. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
  209. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  210. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  211. package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
  213. package/templates/.agents/skills/release/SKILL.en.md +1 -1
  214. package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
  215. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
  216. package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
  217. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
  218. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
  219. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
  220. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
  221. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
  222. package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
  223. package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
  224. package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
  225. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
  226. package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
  227. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
  228. package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
  229. package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
  230. package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
  231. package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
  232. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
  233. package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
  234. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
  235. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
  236. package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
  237. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
  238. package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
  239. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
  240. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
  241. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
  242. package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
  243. package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
  244. package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
  245. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
  246. package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
  247. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
  248. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
  249. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
  250. package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
  251. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
  252. package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
  253. package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
  254. package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
  255. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
  256. package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
  257. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
  258. package/templates/.agents/skills/test/SKILL.en.md +4 -4
  259. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
  260. package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
  261. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
  262. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
  263. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
  264. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
  265. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
  266. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
  267. package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
  268. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
  269. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
  270. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
  271. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
  272. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
  273. package/templates/.claude/commands/run-task.en.md +8 -0
  274. package/templates/.claude/commands/run-task.zh-CN.md +8 -0
  275. package/templates/.claude/settings.json +24 -0
  276. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
  277. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
  278. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
  279. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
  280. package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
  281. package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
  282. package/templates/.opencode/commands/run-task.en.md +11 -0
  283. package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
@@ -0,0 +1,214 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { AgentClientConfigError, normalizeAgentClients } from './agent-clients/config.ts';
5
+ import {
6
+ applyAgentClientReconciliation,
7
+ planAgentClientReconciliation
8
+ } from './agent-clients/reconcile.ts';
9
+ import { listAgentClientAdapters } from './agent-clients/registry.ts';
10
+ import { AGENT_CLIENT_IDS, isAgentClientId } from './agent-clients/types.ts';
11
+ import type { AgentClientState } from './agent-clients/types.ts';
12
+ import { closePrompt, multiSelect } from './prompt.ts';
13
+ import { resolveTemplateDir } from './paths.ts';
14
+
15
+ const USAGE = `Usage: ai agent-client <operation>
16
+
17
+ Operations:
18
+ list List built-in Agent Clients
19
+ status Show project integration and sandbox state
20
+ enable <client-id> Enable project integration only
21
+ disable <client-id> Disable project integration only
22
+ configure Configure integration and sandbox state interactively
23
+ `;
24
+
25
+ class AgentClientCommandError extends Error {
26
+ readonly code: string;
27
+
28
+ constructor(code: string, message: string) {
29
+ super(`${code}: ${message}`);
30
+ this.name = 'AgentClientCommandError';
31
+ this.code = code;
32
+ }
33
+ }
34
+
35
+ function readConfig(projectRoot: string): Record<string, unknown> {
36
+ const configPath = path.join(projectRoot, '.agents', '.airc.json');
37
+ if (!fs.existsSync(configPath)) {
38
+ throw new AgentClientCommandError(
39
+ 'AGENT_CLIENT_CONFIG_NOT_FOUND',
40
+ 'No .agents/.airc.json found. Run "ai init" first.'
41
+ );
42
+ }
43
+ let parsed: unknown;
44
+ try {
45
+ parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
46
+ } catch (error) {
47
+ throw new AgentClientCommandError(
48
+ 'AGENT_CLIENT_CONFIG_INVALID',
49
+ error instanceof Error ? error.message : String(error)
50
+ );
51
+ }
52
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
53
+ throw new AgentClientCommandError(
54
+ 'AGENT_CLIENT_CONFIG_INVALID',
55
+ 'configuration root must be an object'
56
+ );
57
+ }
58
+ return parsed as Record<string, unknown>;
59
+ }
60
+
61
+ function choices() {
62
+ return listAgentClientAdapters().map((adapter) => ({
63
+ id: adapter.id,
64
+ label: adapter.displayName
65
+ }));
66
+ }
67
+
68
+ function selected(state: AgentClientState, field: 'enabled' | 'installInSandbox'): string[] {
69
+ return AGENT_CLIENT_IDS.filter((id) => state[id][field]);
70
+ }
71
+
72
+ function workflowPlan(
73
+ projectRoot: string,
74
+ config: Record<string, unknown>,
75
+ mutation: Parameters<typeof planAgentClientReconciliation>[0]['mutation']
76
+ ) {
77
+ const templateRoot = resolveTemplateDir();
78
+ if (!templateRoot) {
79
+ throw new AgentClientCommandError('AGENT_CLIENT_TEMPLATE_NOT_FOUND', 'Template directory not found');
80
+ }
81
+ const platform = config.platform;
82
+ const platformType = typeof platform === 'object' && platform !== null && 'type' in platform
83
+ ? String(platform.type ?? 'github')
84
+ : 'github';
85
+ return planAgentClientReconciliation({
86
+ config,
87
+ mutation,
88
+ projectRoot,
89
+ templateRoot,
90
+ platformType,
91
+ language: String(config.language ?? 'en')
92
+ });
93
+ }
94
+
95
+ function printState(state: AgentClientState): void {
96
+ for (const adapter of listAgentClientAdapters()) {
97
+ const value = state[adapter.id];
98
+ process.stdout.write(
99
+ ` ${adapter.id}: enabled=${String(value.enabled)} installInSandbox=${String(value.installInSandbox)}\n`
100
+ );
101
+ }
102
+ }
103
+
104
+ async function cmdAgentClient(args: string[]): Promise<number> {
105
+ const operation = args[0];
106
+ if (!operation || operation === '--help' || operation === '-h' || operation === 'help') {
107
+ process.stdout.write(USAGE);
108
+ return 0;
109
+ }
110
+
111
+ if (operation === 'list') {
112
+ if (args.length !== 1) {
113
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'list accepts no arguments');
114
+ }
115
+ for (const adapter of listAgentClientAdapters()) {
116
+ process.stdout.write(
117
+ `${adapter.id}\t${adapter.displayName}\t${adapter.invocation}\tcommands=${adapter.capabilities.commands.level}\tsandbox=${adapter.capabilities.sandbox.level}\n`
118
+ );
119
+ }
120
+ return 0;
121
+ }
122
+
123
+ const projectRoot = process.cwd();
124
+ const config = readConfig(projectRoot);
125
+ try {
126
+ if (operation === 'status') {
127
+ if (args.length !== 1) {
128
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'status accepts no arguments');
129
+ }
130
+ const plan = workflowPlan(projectRoot, config, { type: 'none' });
131
+ process.stdout.write(`source: ${plan.source}\n`);
132
+ printState(plan.desired);
133
+ for (const seed of plan.seedOperations) {
134
+ process.stdout.write(` seed ${seed.target}: ${seed.kind}\n`);
135
+ }
136
+ for (const diagnostic of plan.diagnostics) {
137
+ process.stdout.write(` diagnostic ${diagnostic.code} at ${diagnostic.path}\n`);
138
+ }
139
+ return 0;
140
+ }
141
+
142
+ if (operation === 'enable' || operation === 'disable') {
143
+ if (args.length !== 2) {
144
+ throw new AgentClientCommandError(
145
+ 'AGENT_CLIENT_ARGUMENT_INVALID',
146
+ `${operation} requires exactly one client id`
147
+ );
148
+ }
149
+ const id = args[1];
150
+ if (!isAgentClientId(id)) {
151
+ throw new AgentClientCommandError('UNKNOWN_AGENT_CLIENT', `Unknown Agent Client '${String(id)}'`);
152
+ }
153
+ const plan = workflowPlan(projectRoot, config, {
154
+ type: 'set-enabled',
155
+ id,
156
+ value: operation === 'enable'
157
+ });
158
+ process.stdout.write('Before:\n');
159
+ printState(plan.before);
160
+ const result = applyAgentClientReconciliation(plan);
161
+ process.stdout.write('After:\n');
162
+ printState(plan.desired);
163
+ process.stdout.write(
164
+ `Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`
165
+ );
166
+ return 0;
167
+ }
168
+
169
+ if (operation === 'configure') {
170
+ if (args.length !== 1) {
171
+ throw new AgentClientCommandError('AGENT_CLIENT_ARGUMENT_INVALID', 'configure accepts no arguments');
172
+ }
173
+ const normalized = normalizeAgentClients(config);
174
+ let enabled: string[];
175
+ let installed: string[];
176
+ try {
177
+ enabled = await multiSelect(
178
+ 'Agent Client project integrations',
179
+ choices(),
180
+ selected(normalized.state, 'enabled')
181
+ );
182
+ installed = await multiSelect(
183
+ 'Agent Clients installed in sandbox',
184
+ choices(),
185
+ selected(normalized.state, 'installInSandbox')
186
+ );
187
+ } finally {
188
+ closePrompt();
189
+ }
190
+ const state = Object.fromEntries(AGENT_CLIENT_IDS.map((id) => [id, {
191
+ enabled: enabled.includes(id),
192
+ installInSandbox: installed.includes(id)
193
+ }])) as AgentClientState;
194
+ const plan = workflowPlan(projectRoot, config, { type: 'replace-state', state });
195
+ const result = applyAgentClientReconciliation(plan);
196
+ process.stdout.write(
197
+ `Result: ${result.status}; applied=${result.applied.length}; protected=${result.protected.length}\n`
198
+ );
199
+ return 0;
200
+ }
201
+
202
+ throw new AgentClientCommandError(
203
+ 'AGENT_CLIENT_OPERATION_INVALID',
204
+ `Unknown operation '${operation}'`
205
+ );
206
+ } catch (error) {
207
+ if (error instanceof AgentClientConfigError) {
208
+ throw new AgentClientCommandError(error.code, error.message);
209
+ }
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ export { AgentClientCommandError, cmdAgentClient };
@@ -0,0 +1,461 @@
1
+ import {
2
+ AGENT_CLIENT_CAPABILITY_IDS,
3
+ AGENT_CLIENT_SUPPORT_LEVELS,
4
+ isAgentClientId
5
+ } from './types.ts';
6
+ import type {
7
+ AgentClientCapabilityMap,
8
+ AgentClientId
9
+ } from './types.ts';
10
+ import {
11
+ SANDBOX_HOOK_PHASES
12
+ } from '../sandbox/tool-types.ts';
13
+ import type {
14
+ AgentClientSandboxRecoveryCheck,
15
+ AgentClientSandboxDescriptor,
16
+ SandboxAlias,
17
+ SandboxTool,
18
+ SandboxToolContext
19
+ } from '../sandbox/tool-types.ts';
20
+
21
+ type AgentClientCapabilities = AgentClientCapabilityMap;
22
+
23
+ type AgentClientSeedCommand = Readonly<{
24
+ templates: Readonly<{
25
+ en: string;
26
+ 'zh-CN': string;
27
+ }>;
28
+ target: string;
29
+ }>;
30
+
31
+ type AgentClientProjectDescriptor = Readonly<{
32
+ ownedPathPrefixes: readonly string[];
33
+ managed: readonly string[];
34
+ merged: readonly string[];
35
+ ejected: readonly string[];
36
+ seedCommands: readonly AgentClientSeedCommand[];
37
+ }>;
38
+
39
+ type AgentClientAdapter = Readonly<{
40
+ id: AgentClientId;
41
+ displayName: string;
42
+ invocation: string;
43
+ capabilities: AgentClientCapabilities;
44
+ project: AgentClientProjectDescriptor;
45
+ sandbox: AgentClientSandboxDescriptor;
46
+ }>;
47
+
48
+ type AgentClientRegistry = Readonly<
49
+ Record<AgentClientId, AgentClientAdapter>
50
+ >;
51
+
52
+ type AgentClientManifestEntry = Readonly<{
53
+ id: AgentClientId;
54
+ displayName: string;
55
+ invocation: string;
56
+ ownedPathPrefixes: readonly string[];
57
+ managed: readonly string[];
58
+ merged: readonly string[];
59
+ ejected: readonly string[];
60
+ }>;
61
+
62
+ const PROJECT_ASSET_CATEGORIES = ['managed', 'merged', 'ejected'] as const;
63
+ const MAX_SANDBOX_HOOK_TIMEOUT_MS = 300_000;
64
+ const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
65
+
66
+ function freezeRecoveryProbe(
67
+ id: AgentClientId,
68
+ checkId: string,
69
+ value: AgentClientSandboxRecoveryCheck['probe']
70
+ ): AgentClientSandboxRecoveryCheck['probe'] {
71
+ if (
72
+ !value
73
+ || typeof value.script !== 'string'
74
+ || value.script.trim() === ''
75
+ || !Array.isArray(value.args)
76
+ || value.args.some((arg) => typeof arg !== 'string')
77
+ || (value.user !== undefined && (typeof value.user !== 'string' || value.user === ''))
78
+ ) {
79
+ throw new Error(`Agent Client '${id}' recovery check '${checkId}' has an invalid probe`);
80
+ }
81
+ return Object.freeze({
82
+ script: value.script,
83
+ args: Object.freeze([...value.args]),
84
+ ...(value.user === undefined ? {} : { user: value.user })
85
+ });
86
+ }
87
+
88
+ function freezeTool(tool: SandboxTool): SandboxTool {
89
+ if (typeof tool.id !== 'string' || !TOOL_ID_PATTERN.test(tool.id)) {
90
+ throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
91
+ }
92
+ if (!tool.install?.cmd || !['npm', 'shell'].includes(tool.install.type)) {
93
+ throw new Error(`Sandbox tool '${tool.id}' has invalid install`);
94
+ }
95
+ if (!tool.containerMount.startsWith('/')) {
96
+ throw new Error(`Sandbox tool '${tool.id}' containerMount must be absolute`);
97
+ }
98
+ if (!tool.versionCmd) {
99
+ throw new Error(`Sandbox tool '${tool.id}' requires versionCmd`);
100
+ }
101
+ return Object.freeze({
102
+ ...tool,
103
+ install: Object.freeze({ ...tool.install }),
104
+ ...(tool.envVars ? { envVars: Object.freeze({ ...tool.envVars }) } : {}),
105
+ ...(tool.hostPreSeedFiles ? {
106
+ hostPreSeedFiles: Object.freeze(tool.hostPreSeedFiles.map((entry) => Object.freeze({ ...entry })))
107
+ } : {}),
108
+ ...(tool.hostPreSeedDirs ? {
109
+ hostPreSeedDirs: Object.freeze(tool.hostPreSeedDirs.map((entry) => Object.freeze({ ...entry })))
110
+ } : {}),
111
+ ...(tool.pathRewriteFiles ? { pathRewriteFiles: Object.freeze([...tool.pathRewriteFiles]) } : {}),
112
+ ...(tool.hostLiveMounts ? {
113
+ hostLiveMounts: Object.freeze(tool.hostLiveMounts.map((entry) => Object.freeze({ ...entry })))
114
+ } : {}),
115
+ ...(tool.postSetupCmds ? { postSetupCmds: Object.freeze([...tool.postSetupCmds]) } : {}),
116
+ ...(tool.tmpfs ? {
117
+ tmpfs: Object.freeze({
118
+ ...tool.tmpfs,
119
+ ...(tool.tmpfs.seed ? { seed: Object.freeze([...tool.tmpfs.seed]) } : {})
120
+ })
121
+ } : {})
122
+ }) as SandboxTool;
123
+ }
124
+
125
+ function freezeAliases(id: AgentClientId, aliases: readonly SandboxAlias[]): readonly SandboxAlias[] {
126
+ const names = new Set<string>();
127
+ return Object.freeze(aliases.map((alias) => {
128
+ if (!alias.name || !alias.command || names.has(alias.name)) {
129
+ throw new Error(`Agent Client '${id}' has invalid or duplicate sandbox alias '${alias.name}'`);
130
+ }
131
+ names.add(alias.name);
132
+ return Object.freeze({ ...alias });
133
+ }));
134
+ }
135
+
136
+ function isProjectRelativeLiteral(value: string): boolean {
137
+ if (
138
+ value.length === 0
139
+ || value.startsWith('/')
140
+ || /^[a-zA-Z]:/.test(value)
141
+ || value.includes('\\')
142
+ || /[*?[\]{}]/.test(value)
143
+ ) {
144
+ return false;
145
+ }
146
+ return !value.split('/').some((segment) => segment === '..');
147
+ }
148
+
149
+ function defineAgentClientAdapter(
150
+ candidate: AgentClientAdapter
151
+ ): AgentClientAdapter {
152
+ if (!isAgentClientId(candidate.id)) {
153
+ throw new Error(`Unknown Agent Client ID '${String(candidate.id)}'`);
154
+ }
155
+ if (typeof candidate.displayName !== 'string' || candidate.displayName.trim() === '') {
156
+ throw new Error(`Agent Client '${candidate.id}' requires a display name`);
157
+ }
158
+ if (
159
+ typeof candidate.invocation !== 'string'
160
+ || candidate.invocation.trim() === ''
161
+ || /[\r\n]/.test(candidate.invocation)
162
+ ) {
163
+ throw new Error(`Agent Client '${candidate.id}' requires a single-line invocation`);
164
+ }
165
+ const invocationPlaceholders = [
166
+ ...candidate.invocation.matchAll(/\$\{([^}]+)\}/g)
167
+ ].map((match) => match[1]);
168
+ if (
169
+ !invocationPlaceholders.includes('skillName')
170
+ || invocationPlaceholders.some((placeholder) =>
171
+ placeholder !== 'skillName' && placeholder !== 'projectName'
172
+ )
173
+ || candidate.invocation
174
+ .replaceAll('${skillName}', '')
175
+ .replaceAll('${projectName}', '')
176
+ .includes('${')
177
+ ) {
178
+ throw new Error(`Agent Client '${candidate.id}' has an invalid invocation`);
179
+ }
180
+
181
+ if (
182
+ typeof candidate.capabilities !== 'object'
183
+ || candidate.capabilities === null
184
+ || Array.isArray(candidate.capabilities)
185
+ ) {
186
+ throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
187
+ }
188
+ const capabilityKeys = Object.keys(candidate.capabilities);
189
+ if (
190
+ capabilityKeys.length !== AGENT_CLIENT_CAPABILITY_IDS.length
191
+ || AGENT_CLIENT_CAPABILITY_IDS.some((capability) =>
192
+ !Object.prototype.hasOwnProperty.call(candidate.capabilities, capability)
193
+ )
194
+ ) {
195
+ throw new Error(`Agent Client '${candidate.id}' has an invalid capability map`);
196
+ }
197
+
198
+ const capabilities = Object.fromEntries(
199
+ AGENT_CLIENT_CAPABILITY_IDS.map((capability) => {
200
+ const support = candidate.capabilities[capability];
201
+ if (
202
+ typeof support !== 'object'
203
+ || support === null
204
+ || !AGENT_CLIENT_SUPPORT_LEVELS.includes(support.level)
205
+ ) {
206
+ throw new Error(
207
+ `Agent Client '${candidate.id}' has invalid support for '${capability}'`
208
+ );
209
+ }
210
+ return [capability, Object.freeze({ level: support.level })];
211
+ })
212
+ ) as AgentClientCapabilities;
213
+
214
+ if (
215
+ typeof candidate.project !== 'object'
216
+ || candidate.project === null
217
+ || Array.isArray(candidate.project)
218
+ ) {
219
+ throw new Error(`Agent Client '${candidate.id}' has an invalid project descriptor`);
220
+ }
221
+ const ownedPathPrefixes = candidate.project.ownedPathPrefixes;
222
+ if (!Array.isArray(ownedPathPrefixes) || ownedPathPrefixes.length === 0) {
223
+ throw new Error(`Agent Client '${candidate.id}' requires owned path prefixes`);
224
+ }
225
+
226
+ const paths = ownedPathPrefixes.map((prefix) => {
227
+ if (
228
+ typeof prefix !== 'string'
229
+ || !isProjectRelativeLiteral(prefix)
230
+ || !prefix.endsWith('/')
231
+ ) {
232
+ throw new Error(
233
+ `Agent Client '${candidate.id}' has invalid owned path prefix '${String(prefix)}'`
234
+ );
235
+ }
236
+ return prefix;
237
+ });
238
+ if (new Set(paths).size !== paths.length) {
239
+ throw new Error(`Agent Client '${candidate.id}' has duplicate owned path prefixes`);
240
+ }
241
+
242
+ const projectAssets = Object.fromEntries(
243
+ PROJECT_ASSET_CATEGORIES.map((category) => {
244
+ const values = candidate.project[category];
245
+ if (!Array.isArray(values)) {
246
+ throw new Error(
247
+ `Agent Client '${candidate.id}' has invalid '${category}' project assets`
248
+ );
249
+ }
250
+ const assets = values.map((asset) => {
251
+ if (
252
+ typeof asset !== 'string'
253
+ || !isProjectRelativeLiteral(asset)
254
+ || !paths.some((prefix) =>
255
+ asset === prefix.slice(0, -1) || asset.startsWith(prefix)
256
+ )
257
+ ) {
258
+ throw new Error(
259
+ `Agent Client '${candidate.id}' has invalid '${category}' project asset '${String(asset)}'`
260
+ );
261
+ }
262
+ return asset;
263
+ });
264
+ if (new Set(assets).size !== assets.length) {
265
+ throw new Error(
266
+ `Agent Client '${candidate.id}' has duplicate '${category}' project assets`
267
+ );
268
+ }
269
+ return [category, Object.freeze(assets)];
270
+ })
271
+ ) as Pick<AgentClientProjectDescriptor, 'managed' | 'merged' | 'ejected'>;
272
+
273
+ const categorizedAssets = PROJECT_ASSET_CATEGORIES.flatMap((category) =>
274
+ projectAssets[category].map((asset) => ({ asset, category }))
275
+ );
276
+ const seenAssets = new Map<string, string>();
277
+ for (const { asset, category } of categorizedAssets) {
278
+ const previous = seenAssets.get(asset);
279
+ if (previous) {
280
+ throw new Error(
281
+ `Agent Client '${candidate.id}' project asset '${asset}' overlaps '${previous}' and '${category}'`
282
+ );
283
+ }
284
+ seenAssets.set(asset, category);
285
+ }
286
+
287
+ if (!Array.isArray(candidate.project.seedCommands)) {
288
+ throw new Error(`Agent Client '${candidate.id}' has invalid seed commands`);
289
+ }
290
+ const seedTargets = new Set<string>();
291
+ const seedCommands = candidate.project.seedCommands.map((seed) => {
292
+ if (!seed || typeof seed !== 'object') {
293
+ throw new Error(`Agent Client '${candidate.id}' has an invalid seed command`);
294
+ }
295
+ const placeholders = [
296
+ ...seed.target.matchAll(/\$\{([^}]+)\}/g)
297
+ ].map((match) => match[1]);
298
+ const targetProbe = seed.target.replaceAll('${projectName}', 'project');
299
+ if (
300
+ placeholders.some((placeholder) => placeholder !== 'projectName')
301
+ || targetProbe.includes('${')
302
+ || !isProjectRelativeLiteral(targetProbe)
303
+ || !paths.some((prefix) =>
304
+ targetProbe === prefix.slice(0, -1) || targetProbe.startsWith(prefix)
305
+ )
306
+ || seedTargets.has(seed.target)
307
+ ) {
308
+ throw new Error(
309
+ `Agent Client '${candidate.id}' has invalid seed target '${String(seed.target)}'`
310
+ );
311
+ }
312
+ if (
313
+ !seed.templates
314
+ || typeof seed.templates.en !== 'string'
315
+ || typeof seed.templates['zh-CN'] !== 'string'
316
+ || !isProjectRelativeLiteral(seed.templates.en)
317
+ || !isProjectRelativeLiteral(seed.templates['zh-CN'])
318
+ ) {
319
+ throw new Error(`Agent Client '${candidate.id}' has invalid seed templates`);
320
+ }
321
+ seedTargets.add(seed.target);
322
+ return Object.freeze({
323
+ templates: Object.freeze({ ...seed.templates }),
324
+ target: seed.target
325
+ });
326
+ });
327
+
328
+ if (!candidate.sandbox || typeof candidate.sandbox.createTool !== 'function') {
329
+ throw new Error(`Agent Client '${candidate.id}' requires a sandbox descriptor`);
330
+ }
331
+ const hookIds = new Set<string>();
332
+ const hooks = Object.freeze(candidate.sandbox.hooks.map((hook) => {
333
+ if (
334
+ !hook.id
335
+ || hookIds.has(hook.id)
336
+ || !SANDBOX_HOOK_PHASES.includes(hook.phase)
337
+ || typeof hook.run !== 'function'
338
+ ) {
339
+ throw new Error(`Agent Client '${candidate.id}' has an invalid sandbox hook`);
340
+ }
341
+ if (
342
+ hook.timeoutMs !== undefined
343
+ && (
344
+ !Number.isInteger(hook.timeoutMs)
345
+ || hook.timeoutMs <= 0
346
+ || hook.timeoutMs > MAX_SANDBOX_HOOK_TIMEOUT_MS
347
+ )
348
+ ) {
349
+ throw new Error(`Agent Client '${candidate.id}' hook '${hook.id}' has invalid timeoutMs`);
350
+ }
351
+ hookIds.add(hook.id);
352
+ return Object.freeze({ ...hook });
353
+ }));
354
+ const recoveryCheckIds = new Set<string>();
355
+ if (
356
+ candidate.sandbox.recoveryChecks !== undefined
357
+ && !Array.isArray(candidate.sandbox.recoveryChecks)
358
+ ) {
359
+ throw new Error(`Agent Client '${candidate.id}' has invalid recovery checks`);
360
+ }
361
+ const recoveryChecks = Object.freeze(
362
+ (candidate.sandbox.recoveryChecks ?? []).map((check) => {
363
+ if (
364
+ !check
365
+ || typeof check.id !== 'string'
366
+ || !TOOL_ID_PATTERN.test(check.id)
367
+ || recoveryCheckIds.has(check.id)
368
+ ) {
369
+ throw new Error(`Agent Client '${candidate.id}' has an invalid recovery check`);
370
+ }
371
+ const probe = freezeRecoveryProbe(candidate.id, check.id, check.probe);
372
+ const when = check.when === undefined
373
+ ? undefined
374
+ : freezeRecoveryProbe(candidate.id, check.id, check.when);
375
+ if (
376
+ !check.finding
377
+ || !['permissions', 'builtin-link', 'hard-failure'].includes(check.finding.repairKind)
378
+ || typeof check.finding.message !== 'string'
379
+ || check.finding.message.trim() === ''
380
+ || (
381
+ check.finding.path !== undefined
382
+ && (typeof check.finding.path !== 'string' || !check.finding.path.startsWith('/'))
383
+ )
384
+ || (
385
+ check.finding.repairKind === 'permissions'
386
+ && check.finding.path === undefined
387
+ )
388
+ ) {
389
+ throw new Error(
390
+ `Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid finding`
391
+ );
392
+ }
393
+ let repair: AgentClientSandboxRecoveryCheck['repair'];
394
+ if (check.repair !== undefined) {
395
+ if (
396
+ typeof check.repair.user !== 'string'
397
+ || check.repair.user === ''
398
+ || typeof check.repair.command !== 'string'
399
+ || check.repair.command === ''
400
+ || !Array.isArray(check.repair.args)
401
+ || check.repair.args.some((arg: unknown) => typeof arg !== 'string')
402
+ ) {
403
+ throw new Error(
404
+ `Agent Client '${candidate.id}' recovery check '${check.id}' has an invalid repair`
405
+ );
406
+ }
407
+ repair = Object.freeze({
408
+ user: check.repair.user,
409
+ command: check.repair.command,
410
+ args: Object.freeze([...check.repair.args])
411
+ });
412
+ }
413
+ if (check.finding.repairKind === 'builtin-link' && repair === undefined) {
414
+ throw new Error(
415
+ `Agent Client '${candidate.id}' recovery check '${check.id}' requires a repair`
416
+ );
417
+ }
418
+ recoveryCheckIds.add(check.id);
419
+ return Object.freeze({
420
+ id: check.id,
421
+ ...(when === undefined ? {} : { when }),
422
+ probe,
423
+ finding: Object.freeze({ ...check.finding }),
424
+ ...(repair === undefined ? {} : { repair })
425
+ });
426
+ })
427
+ );
428
+ const aliases = freezeAliases(candidate.id, candidate.sandbox.aliases);
429
+ const createTool = (context: SandboxToolContext): SandboxTool => {
430
+ const tool = freezeTool(candidate.sandbox.createTool(context));
431
+ if (tool.id !== candidate.id) {
432
+ throw new Error(
433
+ `Agent Client '${candidate.id}' sandbox tool id must match the adapter id`
434
+ );
435
+ }
436
+ return tool;
437
+ };
438
+
439
+ return Object.freeze({
440
+ id: candidate.id,
441
+ displayName: candidate.displayName,
442
+ invocation: candidate.invocation,
443
+ capabilities: Object.freeze(capabilities),
444
+ project: Object.freeze({
445
+ ownedPathPrefixes: Object.freeze(paths),
446
+ ...projectAssets,
447
+ seedCommands: Object.freeze(seedCommands)
448
+ }),
449
+ sandbox: Object.freeze({ createTool, aliases, hooks, recoveryChecks })
450
+ });
451
+ }
452
+
453
+ export { defineAgentClientAdapter };
454
+ export type {
455
+ AgentClientAdapter,
456
+ AgentClientCapabilities,
457
+ AgentClientManifestEntry,
458
+ AgentClientProjectDescriptor,
459
+ AgentClientSeedCommand,
460
+ AgentClientRegistry
461
+ };