@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,278 @@
1
+ import {
2
+ AGENT_CLIENT_IDS,
3
+ isAgentClientId
4
+ } from './types.ts';
5
+ import type {
6
+ AgentClientConfig,
7
+ AgentClientId,
8
+ AgentClientsConfig,
9
+ AgentClientState
10
+ } from './types.ts';
11
+
12
+ type AgentClientConfigInput = Readonly<{
13
+ agentClients?: unknown;
14
+ tuis?: unknown;
15
+ sandbox?: unknown;
16
+ customTUIs?: unknown;
17
+ }>;
18
+
19
+ type AgentClientDiagnosticCode =
20
+ | 'INVALID_AGENT_CLIENTS'
21
+ | 'DUPLICATE_AGENT_CLIENT'
22
+ | 'MISSING_AGENT_CLIENT'
23
+ | 'UNKNOWN_AGENT_CLIENT'
24
+ | 'LEGACY_CONFLICT'
25
+ | 'LEGACY_VALUE_IGNORED';
26
+
27
+ type AgentClientDiagnostic = Readonly<{
28
+ code: AgentClientDiagnosticCode;
29
+ path: string;
30
+ }>;
31
+
32
+ type NormalizeAgentClientsResult = Readonly<{
33
+ source: 'canonical' | 'legacy';
34
+ state: AgentClientState;
35
+ canonical: AgentClientsConfig;
36
+ remainingSandboxTools: readonly string[] | undefined;
37
+ removeLegacyTuis: boolean;
38
+ changed: boolean;
39
+ diagnostics: readonly AgentClientDiagnostic[];
40
+ }>;
41
+
42
+ class AgentClientConfigError extends Error {
43
+ readonly code: AgentClientDiagnosticCode;
44
+ readonly path: string;
45
+ readonly diagnostics: readonly AgentClientDiagnostic[];
46
+
47
+ constructor(diagnostic: AgentClientDiagnostic) {
48
+ super(`${diagnostic.code} at ${diagnostic.path}`);
49
+ this.name = 'AgentClientConfigError';
50
+ this.code = diagnostic.code;
51
+ this.path = diagnostic.path;
52
+ this.diagnostics = [diagnostic];
53
+ }
54
+ }
55
+
56
+ function hasOwn(value: object, key: PropertyKey): boolean {
57
+ return Object.prototype.hasOwnProperty.call(value, key);
58
+ }
59
+
60
+ function isRecord(value: unknown): value is Record<string, unknown> {
61
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function fail(code: AgentClientDiagnosticCode, path: string): never {
65
+ throw new AgentClientConfigError({ code, path });
66
+ }
67
+
68
+ function parseCanonical(value: unknown): AgentClientState {
69
+ if (!Array.isArray(value)) fail('INVALID_AGENT_CLIENTS', 'agentClients');
70
+
71
+ const entries = new Map<AgentClientId, Readonly<{
72
+ enabled: boolean;
73
+ installInSandbox: boolean;
74
+ }>>();
75
+
76
+ for (const [index, candidate] of value.entries()) {
77
+ const path = `agentClients[${index}]`;
78
+ if (!isRecord(candidate)) fail('INVALID_AGENT_CLIENTS', path);
79
+
80
+ const keys = Object.keys(candidate);
81
+ if (
82
+ keys.length !== 3
83
+ || !hasOwn(candidate, 'id')
84
+ || !hasOwn(candidate, 'enabled')
85
+ || !hasOwn(candidate, 'installInSandbox')
86
+ ) {
87
+ fail('INVALID_AGENT_CLIENTS', path);
88
+ }
89
+ if (!isAgentClientId(candidate.id)) {
90
+ fail('UNKNOWN_AGENT_CLIENT', `${path}.id`);
91
+ }
92
+ if (entries.has(candidate.id)) {
93
+ fail('DUPLICATE_AGENT_CLIENT', `${path}.id`);
94
+ }
95
+ if (
96
+ typeof candidate.enabled !== 'boolean'
97
+ || typeof candidate.installInSandbox !== 'boolean'
98
+ ) {
99
+ fail('INVALID_AGENT_CLIENTS', path);
100
+ }
101
+ entries.set(candidate.id, {
102
+ enabled: candidate.enabled,
103
+ installInSandbox: candidate.installInSandbox
104
+ });
105
+ }
106
+
107
+ for (const id of AGENT_CLIENT_IDS) {
108
+ if (!entries.has(id)) fail('MISSING_AGENT_CLIENT', 'agentClients');
109
+ }
110
+
111
+ return Object.fromEntries(
112
+ AGENT_CLIENT_IDS.map((id) => [id, entries.get(id)!])
113
+ ) as AgentClientState;
114
+ }
115
+
116
+ function serializeAgentClients(state: AgentClientState): AgentClientsConfig {
117
+ return AGENT_CLIENT_IDS.map((id): AgentClientConfig => ({
118
+ id,
119
+ enabled: state[id].enabled,
120
+ installInSandbox: state[id].installInSandbox
121
+ }));
122
+ }
123
+
124
+ function projectLegacyTuis(
125
+ value: unknown,
126
+ diagnostics: AgentClientDiagnostic[]
127
+ ): Readonly<Record<AgentClientId, boolean>> {
128
+ if (!Array.isArray(value)) {
129
+ return Object.fromEntries(
130
+ AGENT_CLIENT_IDS.map((id) => [id, true])
131
+ ) as Readonly<Record<AgentClientId, boolean>>;
132
+ }
133
+
134
+ const enabled = new Set<AgentClientId>();
135
+ for (const [index, candidate] of value.entries()) {
136
+ if (isAgentClientId(candidate)) {
137
+ enabled.add(candidate);
138
+ } else {
139
+ diagnostics.push({
140
+ code: 'LEGACY_VALUE_IGNORED',
141
+ path: `tuis[${index}]`
142
+ });
143
+ }
144
+ }
145
+ return Object.fromEntries(
146
+ AGENT_CLIENT_IDS.map((id) => [id, enabled.has(id)])
147
+ ) as Readonly<Record<AgentClientId, boolean>>;
148
+ }
149
+
150
+ type SandboxProjection = Readonly<{
151
+ installed: Readonly<Record<AgentClientId, boolean>>;
152
+ clientSignals: ReadonlySet<AgentClientId>;
153
+ remainingTools: readonly string[] | undefined;
154
+ }>;
155
+
156
+ function projectLegacySandbox(
157
+ value: unknown,
158
+ diagnostics: AgentClientDiagnostic[]
159
+ ): SandboxProjection {
160
+ if (!Array.isArray(value) || value.length === 0) {
161
+ return {
162
+ installed: Object.fromEntries(
163
+ AGENT_CLIENT_IDS.map((id) => [id, true])
164
+ ) as Readonly<Record<AgentClientId, boolean>>,
165
+ clientSignals: new Set<AgentClientId>(),
166
+ remainingTools: Array.isArray(value) ? [] : undefined
167
+ };
168
+ }
169
+
170
+ const installed = new Set<AgentClientId>();
171
+ const remainingTools: string[] = [];
172
+ for (const [index, candidate] of value.entries()) {
173
+ if (isAgentClientId(candidate)) {
174
+ installed.add(candidate);
175
+ } else if (typeof candidate === 'string') {
176
+ remainingTools.push(candidate);
177
+ } else {
178
+ diagnostics.push({
179
+ code: 'LEGACY_VALUE_IGNORED',
180
+ path: `sandbox.tools[${index}]`
181
+ });
182
+ }
183
+ }
184
+ return {
185
+ installed: Object.fromEntries(
186
+ AGENT_CLIENT_IDS.map((id) => [id, installed.has(id)])
187
+ ) as Readonly<Record<AgentClientId, boolean>>,
188
+ clientSignals: installed,
189
+ remainingTools
190
+ };
191
+ }
192
+
193
+ function stateFromLegacy(
194
+ enabled: Readonly<Record<AgentClientId, boolean>>,
195
+ installed: Readonly<Record<AgentClientId, boolean>>
196
+ ): AgentClientState {
197
+ return Object.fromEntries(
198
+ AGENT_CLIENT_IDS.map((id) => [
199
+ id,
200
+ {
201
+ enabled: enabled[id],
202
+ installInSandbox: installed[id]
203
+ }
204
+ ])
205
+ ) as AgentClientState;
206
+ }
207
+
208
+ function sameCanonicalOrder(value: unknown): boolean {
209
+ return Array.isArray(value)
210
+ && value.every((entry, index) => isRecord(entry) && entry.id === AGENT_CLIENT_IDS[index]);
211
+ }
212
+
213
+ function normalizeAgentClients(
214
+ input: AgentClientConfigInput
215
+ ): NormalizeAgentClientsResult {
216
+ const diagnostics: AgentClientDiagnostic[] = [];
217
+ const hasCanonical = hasOwn(input, 'agentClients');
218
+ const hasLegacyTuis = hasOwn(input, 'tuis');
219
+ const sandbox = isRecord(input.sandbox) ? input.sandbox : undefined;
220
+ const hasLegacySandboxTools = sandbox !== undefined && hasOwn(sandbox, 'tools');
221
+ const tuiProjection = projectLegacyTuis(input.tuis, diagnostics);
222
+ const sandboxProjection = projectLegacySandbox(sandbox?.tools, diagnostics);
223
+
224
+ if (!hasCanonical) {
225
+ const state = stateFromLegacy(tuiProjection, sandboxProjection.installed);
226
+ return {
227
+ source: 'legacy',
228
+ state,
229
+ canonical: serializeAgentClients(state),
230
+ remainingSandboxTools: sandboxProjection.remainingTools,
231
+ removeLegacyTuis: hasLegacyTuis,
232
+ changed: true,
233
+ diagnostics
234
+ };
235
+ }
236
+
237
+ const state = parseCanonical(input.agentClients);
238
+ if (hasLegacyTuis) {
239
+ for (const id of AGENT_CLIENT_IDS) {
240
+ if (state[id].enabled !== tuiProjection[id]) {
241
+ fail('LEGACY_CONFLICT', 'tuis');
242
+ }
243
+ }
244
+ }
245
+ if (hasLegacySandboxTools) {
246
+ for (const id of sandboxProjection.clientSignals) {
247
+ if (!state[id].installInSandbox) {
248
+ fail('LEGACY_CONFLICT', 'sandbox.tools');
249
+ }
250
+ }
251
+ }
252
+
253
+ return {
254
+ source: 'canonical',
255
+ state,
256
+ canonical: serializeAgentClients(state),
257
+ remainingSandboxTools: hasLegacySandboxTools
258
+ ? sandboxProjection.remainingTools
259
+ : undefined,
260
+ removeLegacyTuis: hasLegacyTuis,
261
+ changed: hasLegacyTuis
262
+ || sandboxProjection.clientSignals.size > 0
263
+ || !sameCanonicalOrder(input.agentClients),
264
+ diagnostics
265
+ };
266
+ }
267
+
268
+ export {
269
+ AgentClientConfigError,
270
+ normalizeAgentClients,
271
+ serializeAgentClients
272
+ };
273
+ export type {
274
+ AgentClientConfigInput,
275
+ AgentClientDiagnostic,
276
+ AgentClientDiagnosticCode,
277
+ NormalizeAgentClientsResult
278
+ };
@@ -0,0 +1,149 @@
1
+ import path from 'node:path';
2
+
3
+ type CustomTUI = Readonly<{
4
+ name: string;
5
+ dir: string;
6
+ invocation: string;
7
+ }>;
8
+
9
+ type CustomTUIDiagnosticCode =
10
+ | 'INVALID_CUSTOM_TUIS'
11
+ | 'INVALID_CUSTOM_TUI'
12
+ | 'INVALID_CUSTOM_TUI_PLACEHOLDER';
13
+
14
+ type CustomTUIDiagnostic = Readonly<{
15
+ code: CustomTUIDiagnosticCode;
16
+ path: string;
17
+ }>;
18
+
19
+ type NormalizeCustomTUIsResult = Readonly<{
20
+ items: readonly CustomTUI[];
21
+ diagnostics: readonly CustomTUIDiagnostic[];
22
+ }>;
23
+
24
+ const CUSTOM_TUI_CONTRACT = Object.freeze({
25
+ requiredFields: Object.freeze(['name', 'dir', 'invoke']),
26
+ allowedPlaceholders: Object.freeze(['skillName', 'projectName'])
27
+ });
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ function isNonEmptySingleLine(value: unknown): value is string {
34
+ return typeof value === 'string'
35
+ && value.trim() !== ''
36
+ && !/[\r\n]/.test(value);
37
+ }
38
+
39
+ function normalizeDir(projectRoot: string, value: unknown): string | null {
40
+ if (
41
+ !isNonEmptySingleLine(value)
42
+ || value.includes('\\')
43
+ || path.isAbsolute(value)
44
+ || /^[a-zA-Z]:/.test(value)
45
+ ) {
46
+ return null;
47
+ }
48
+ const normalized = path.posix.normalize(value);
49
+ if (
50
+ normalized === '.'
51
+ || normalized === '..'
52
+ || normalized.startsWith('../')
53
+ || normalized.split('/').includes('..')
54
+ ) {
55
+ return null;
56
+ }
57
+ const relative = path.relative(
58
+ path.resolve(projectRoot),
59
+ path.resolve(projectRoot, normalized)
60
+ );
61
+ return relative === ''
62
+ || path.isAbsolute(relative)
63
+ || relative === '..'
64
+ || relative.startsWith(`..${path.sep}`)
65
+ ? null
66
+ : normalized;
67
+ }
68
+
69
+ function hasValidPlaceholders(invocation: string): boolean {
70
+ const placeholders = [
71
+ ...invocation.matchAll(/\$\{([^}]+)\}/g)
72
+ ].map((match) => match[1]!);
73
+ return placeholders.includes('skillName')
74
+ && placeholders.every((placeholder) =>
75
+ CUSTOM_TUI_CONTRACT.allowedPlaceholders.includes(placeholder)
76
+ )
77
+ && !invocation
78
+ .replaceAll('${skillName}', '')
79
+ .replaceAll('${projectName}', '')
80
+ .includes('${');
81
+ }
82
+
83
+ function normalizeCustomTUIs(
84
+ projectRoot: string,
85
+ input: unknown
86
+ ): NormalizeCustomTUIsResult {
87
+ const items: CustomTUI[] = [];
88
+ const diagnostics: CustomTUIDiagnostic[] = [];
89
+
90
+ if (!Array.isArray(input)) {
91
+ return Object.freeze({
92
+ items: Object.freeze([]),
93
+ diagnostics: Object.freeze([
94
+ Object.freeze({
95
+ code: 'INVALID_CUSTOM_TUIS' as const,
96
+ path: 'customTUIs'
97
+ })
98
+ ])
99
+ });
100
+ }
101
+
102
+ for (const [index, candidate] of input.entries()) {
103
+ const base = `customTUIs[${index}]`;
104
+ if (!isRecord(candidate)) {
105
+ diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: base });
106
+ continue;
107
+ }
108
+ if (!isNonEmptySingleLine(candidate.name)) {
109
+ diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.name` });
110
+ continue;
111
+ }
112
+ const dir = normalizeDir(projectRoot, candidate.dir);
113
+ if (!dir) {
114
+ diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.dir` });
115
+ continue;
116
+ }
117
+ if (!isNonEmptySingleLine(candidate.invoke)) {
118
+ diagnostics.push({ code: 'INVALID_CUSTOM_TUI', path: `${base}.invoke` });
119
+ continue;
120
+ }
121
+ if (!hasValidPlaceholders(candidate.invoke)) {
122
+ diagnostics.push({
123
+ code: 'INVALID_CUSTOM_TUI_PLACEHOLDER',
124
+ path: `${base}.invoke`
125
+ });
126
+ continue;
127
+ }
128
+ items.push(Object.freeze({
129
+ name: candidate.name,
130
+ dir,
131
+ invocation: candidate.invoke
132
+ }));
133
+ }
134
+
135
+ return Object.freeze({
136
+ items: Object.freeze(items),
137
+ diagnostics: Object.freeze(
138
+ diagnostics.map((diagnostic) => Object.freeze(diagnostic))
139
+ )
140
+ });
141
+ }
142
+
143
+ export { CUSTOM_TUI_CONTRACT, normalizeCustomTUIs };
144
+ export type {
145
+ CustomTUI,
146
+ CustomTUIDiagnostic,
147
+ CustomTUIDiagnosticCode,
148
+ NormalizeCustomTUIsResult
149
+ };
@@ -0,0 +1,19 @@
1
+ type AgentClientInvocationInput = Readonly<{
2
+ skillName: string;
3
+ projectName?: string;
4
+ args?: readonly string[];
5
+ }>;
6
+
7
+ function renderAgentClientInvocation(
8
+ template: string,
9
+ input: AgentClientInvocationInput
10
+ ): string {
11
+ let command = template.replaceAll('${skillName}', input.skillName);
12
+ if (input.projectName !== undefined) {
13
+ command = command.replaceAll('${projectName}', input.projectName);
14
+ }
15
+ return [command, ...(input.args ?? [])].join(' ').trim();
16
+ }
17
+
18
+ export { renderAgentClientInvocation };
19
+ export type { AgentClientInvocationInput };
@@ -0,0 +1,72 @@
1
+ import { listEnabledAgentClientAdapters } from './registry.ts';
2
+ import { renderAgentClientInvocation } from './invocation.ts';
3
+ import type { CustomTUI } from './custom-tuis.ts';
4
+ import type { AgentClientId, AgentClientState } from './types.ts';
5
+
6
+ type NextStepCommand = Readonly<{
7
+ source: 'builtin' | 'custom';
8
+ clientId?: AgentClientId;
9
+ displayName: string;
10
+ command: string;
11
+ }>;
12
+
13
+ type RenderNextStepsInput = Readonly<{
14
+ projectName: string;
15
+ state: AgentClientState;
16
+ customTUIs: readonly CustomTUI[];
17
+ skillName: string;
18
+ taskRef?: string;
19
+ }>;
20
+
21
+ const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
22
+ const SAFE_TASK_REF = /^(?:\d+|TASK-\d{8}-\d{6})$/;
23
+
24
+ function requireSafeName(value: string, field: string): void {
25
+ if (!SAFE_NAME.test(value)) {
26
+ throw new Error(`Invalid ${field}`);
27
+ }
28
+ }
29
+
30
+ function renderNextStepCommands(
31
+ input: RenderNextStepsInput
32
+ ): readonly NextStepCommand[] {
33
+ requireSafeName(input.projectName, 'project name');
34
+ requireSafeName(input.skillName, 'skill name');
35
+ if (input.taskRef !== undefined && !SAFE_TASK_REF.test(input.taskRef)) {
36
+ throw new Error('Invalid task ref');
37
+ }
38
+
39
+ const builtins = listEnabledAgentClientAdapters(input.state).map(
40
+ (adapter): NextStepCommand => Object.freeze({
41
+ source: 'builtin',
42
+ clientId: adapter.id,
43
+ displayName: adapter.displayName,
44
+ command: renderAgentClientInvocation(
45
+ adapter.invocation,
46
+ {
47
+ projectName: input.projectName,
48
+ skillName: input.skillName,
49
+ args: input.taskRef === undefined ? [] : [input.taskRef]
50
+ }
51
+ )
52
+ })
53
+ );
54
+ const custom = input.customTUIs.map(
55
+ (tool): NextStepCommand => Object.freeze({
56
+ source: 'custom',
57
+ displayName: tool.name,
58
+ command: renderAgentClientInvocation(
59
+ tool.invocation,
60
+ {
61
+ projectName: input.projectName,
62
+ skillName: input.skillName,
63
+ args: input.taskRef === undefined ? [] : [input.taskRef]
64
+ }
65
+ )
66
+ })
67
+ );
68
+ return Object.freeze([...builtins, ...custom]);
69
+ }
70
+
71
+ export { renderNextStepCommands };
72
+ export type { NextStepCommand, RenderNextStepsInput };
@@ -0,0 +1,96 @@
1
+ import type { AgentClientAdapter } from './adapter.ts';
2
+
3
+ type ProjectFileRegistry = Readonly<{
4
+ managed: readonly string[];
5
+ merged: readonly string[];
6
+ ejected: readonly string[];
7
+ }>;
8
+
9
+ type AgentClientProjectAssetPlan = Readonly<{
10
+ registry: ProjectFileRegistry;
11
+ enabledManaged: readonly string[];
12
+ enabledMerged: readonly string[];
13
+ enabledEjected: readonly string[];
14
+ disabledManaged: readonly string[];
15
+ }>;
16
+
17
+ function unique(values: readonly string[]): readonly string[] {
18
+ return Object.freeze([...new Set(values)]);
19
+ }
20
+
21
+ function planAgentClientProjectAssets(input: Readonly<{
22
+ current: ProjectFileRegistry;
23
+ sharedDefaults: ProjectFileRegistry;
24
+ enabledAdapters: readonly AgentClientAdapter[];
25
+ allAdapters: readonly AgentClientAdapter[];
26
+ }>): AgentClientProjectAssetPlan {
27
+ const enabledIds = new Set(input.enabledAdapters.map((adapter) => adapter.id));
28
+ const enabledAdapters = input.allAdapters.filter((adapter) =>
29
+ enabledIds.has(adapter.id)
30
+ );
31
+ const disabledAdapters = input.allAdapters.filter((adapter) =>
32
+ !enabledIds.has(adapter.id)
33
+ );
34
+
35
+ const enabledManaged = unique(
36
+ enabledAdapters.flatMap((adapter) => adapter.project.managed)
37
+ );
38
+ const enabledMerged = unique(
39
+ enabledAdapters.flatMap((adapter) => adapter.project.merged)
40
+ );
41
+ const enabledEjected = unique(
42
+ enabledAdapters.flatMap((adapter) => adapter.project.ejected)
43
+ );
44
+ const disabledManaged = unique(
45
+ disabledAdapters.flatMap((adapter) => adapter.project.managed)
46
+ );
47
+
48
+ const allAdapterAssets = new Set(
49
+ input.allAdapters.flatMap((adapter) => [
50
+ ...adapter.project.managed,
51
+ ...adapter.project.merged,
52
+ ...adapter.project.ejected
53
+ ])
54
+ );
55
+ const ejected = unique([
56
+ ...input.current.ejected,
57
+ ...enabledEjected,
58
+ ...input.sharedDefaults.ejected
59
+ ]);
60
+ const ejectedSet = new Set(ejected);
61
+
62
+ const preserveCurrent = (
63
+ values: readonly string[],
64
+ enabledValues: readonly string[]
65
+ ) => values.filter((entry) =>
66
+ !ejectedSet.has(entry)
67
+ && (!allAdapterAssets.has(entry) || enabledValues.includes(entry))
68
+ );
69
+
70
+ const managed = unique([
71
+ ...preserveCurrent(input.current.managed, enabledManaged),
72
+ ...enabledManaged.filter((entry) => !ejectedSet.has(entry)),
73
+ ...input.sharedDefaults.managed.filter((entry) => !ejectedSet.has(entry))
74
+ ]);
75
+ const managedSet = new Set(managed);
76
+ const merged = unique([
77
+ ...preserveCurrent(input.current.merged, enabledMerged),
78
+ ...enabledMerged.filter((entry) =>
79
+ !ejectedSet.has(entry) && !managedSet.has(entry)
80
+ ),
81
+ ...input.sharedDefaults.merged.filter((entry) =>
82
+ !ejectedSet.has(entry) && !managedSet.has(entry)
83
+ )
84
+ ]);
85
+
86
+ return Object.freeze({
87
+ registry: Object.freeze({ managed, merged, ejected }),
88
+ enabledManaged,
89
+ enabledMerged,
90
+ enabledEjected,
91
+ disabledManaged
92
+ });
93
+ }
94
+
95
+ export { planAgentClientProjectAssets };
96
+ export type { AgentClientProjectAssetPlan, ProjectFileRegistry };