@robota-sdk/agent-command 3.0.0-beta.100

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 (251) hide show
  1. package/CHANGELOG.md +1351 -0
  2. package/LICENSE +661 -0
  3. package/README.md +100 -0
  4. package/dist/node/index.cjs +121 -0
  5. package/dist/node/index.d.cts +1053 -0
  6. package/dist/node/index.d.cts.map +1 -0
  7. package/dist/node/index.d.ts +1053 -0
  8. package/dist/node/index.d.ts.map +1 -0
  9. package/dist/node/index.js +122 -0
  10. package/dist/node/index.js.map +1 -0
  11. package/package.json +83 -0
  12. package/src/advisor/advisor-command-module.ts +47 -0
  13. package/src/advisor/advisor-command.test.ts +102 -0
  14. package/src/advisor/advisor-command.ts +55 -0
  15. package/src/advisor/advisor-cost-functional.test.ts +92 -0
  16. package/src/advisor/index.ts +6 -0
  17. package/src/agent/__tests__/agent-command.test.ts +597 -0
  18. package/src/agent/agent-command-module.ts +89 -0
  19. package/src/agent/agent-command-parser.ts +180 -0
  20. package/src/agent/agent-command.ts +255 -0
  21. package/src/agent/index.ts +7 -0
  22. package/src/background/__tests__/background-command-module.test.ts +261 -0
  23. package/src/background/background-command-module.ts +52 -0
  24. package/src/background/background-command.ts +65 -0
  25. package/src/background/index.ts +6 -0
  26. package/src/command-module-utils.test.ts +37 -0
  27. package/src/command-module-utils.ts +35 -0
  28. package/src/compact/__tests__/compact-command-module.test.ts +181 -0
  29. package/src/compact/compact-command-module.ts +59 -0
  30. package/src/compact/compact-command.ts +33 -0
  31. package/src/compact/index.ts +6 -0
  32. package/src/context/__tests__/context-command-module.test.ts +502 -0
  33. package/src/context/auto-compact-format.ts +25 -0
  34. package/src/context/context-breakdown.ts +295 -0
  35. package/src/context/context-command-module.ts +82 -0
  36. package/src/context/context-command.ts +291 -0
  37. package/src/context/index.ts +6 -0
  38. package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
  39. package/src/default/__tests__/default-command-modules.test.ts +192 -0
  40. package/src/default/__tests__/model-exposure.test.ts +269 -0
  41. package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
  42. package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
  43. package/src/default/default-command-modules.ts +247 -0
  44. package/src/default/index.ts +5 -0
  45. package/src/devices/__tests__/devices-command-module.test.ts +299 -0
  46. package/src/devices/devices-command-module.ts +308 -0
  47. package/src/devices/devices-command-port.ts +143 -0
  48. package/src/devices/index.ts +14 -0
  49. package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
  50. package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
  51. package/src/doctor/__tests__/doctor-fixture.ts +146 -0
  52. package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
  53. package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
  54. package/src/doctor/doctor-command-module.ts +132 -0
  55. package/src/doctor/doctor-extensions-probe.ts +323 -0
  56. package/src/doctor/doctor-node-deps.ts +88 -0
  57. package/src/doctor/doctor-redaction.ts +99 -0
  58. package/src/doctor/doctor-render.ts +70 -0
  59. package/src/doctor/doctor-repair.ts +136 -0
  60. package/src/doctor/doctor-runner.ts +122 -0
  61. package/src/doctor/doctor-settings-probe.ts +239 -0
  62. package/src/doctor/doctor-storage-probe.ts +187 -0
  63. package/src/doctor/doctor-types.ts +109 -0
  64. package/src/doctor/index.ts +32 -0
  65. package/src/editor/__tests__/editor-command-functional.test.ts +124 -0
  66. package/src/editor/editor-command-module.ts +53 -0
  67. package/src/editor/editor-command.ts +58 -0
  68. package/src/editor/index.ts +7 -0
  69. package/src/editor/resolve-editor.ts +21 -0
  70. package/src/effort/effort-command-module.ts +51 -0
  71. package/src/effort/effort-command.test.ts +203 -0
  72. package/src/effort/effort-command.ts +136 -0
  73. package/src/effort/index.ts +6 -0
  74. package/src/events/__tests__/events-command.test.ts +73 -0
  75. package/src/events/events-command-module.ts +49 -0
  76. package/src/events/events-command.ts +69 -0
  77. package/src/events/index.ts +2 -0
  78. package/src/exit/__tests__/exit-command-module.test.ts +61 -0
  79. package/src/exit/exit-command-module.ts +47 -0
  80. package/src/exit/exit-command.ts +26 -0
  81. package/src/exit/index.ts +6 -0
  82. package/src/fork/__tests__/fork-command.test.ts +184 -0
  83. package/src/fork/fork-command-module.ts +61 -0
  84. package/src/fork/fork-command.ts +162 -0
  85. package/src/fork/index.ts +8 -0
  86. package/src/git/__tests__/fake-git-port.ts +35 -0
  87. package/src/git/__tests__/git-command-module.test.ts +111 -0
  88. package/src/git/__tests__/git-commit.test.ts +185 -0
  89. package/src/git/git-command-module.ts +138 -0
  90. package/src/git/git-commit.ts +254 -0
  91. package/src/git/index.ts +20 -0
  92. package/src/goal/__tests__/goal-command.test.ts +80 -0
  93. package/src/goal/goal-command-module.ts +49 -0
  94. package/src/goal/goal-command.ts +69 -0
  95. package/src/goal/index.ts +6 -0
  96. package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
  97. package/src/handoff/__tests__/handoff-command.test.ts +218 -0
  98. package/src/handoff/handoff-command-module.ts +49 -0
  99. package/src/handoff/handoff-command.ts +187 -0
  100. package/src/handoff/index.ts +6 -0
  101. package/src/help/__tests__/help-command-module.test.ts +134 -0
  102. package/src/help/__tests__/help-command.test.ts +130 -0
  103. package/src/help/help-command-module.ts +47 -0
  104. package/src/help/help-command.ts +11 -0
  105. package/src/help/index.ts +6 -0
  106. package/src/index.ts +163 -0
  107. package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
  108. package/src/keybindings/index.ts +6 -0
  109. package/src/keybindings/keybindings-command-module.ts +88 -0
  110. package/src/language/__tests__/language-command-module.test.ts +126 -0
  111. package/src/language/index.ts +6 -0
  112. package/src/language/language-command-module.ts +55 -0
  113. package/src/language/language-command.ts +52 -0
  114. package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
  115. package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
  116. package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
  117. package/src/mcp-activation/index.ts +12 -0
  118. package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
  119. package/src/mcp-activation/mcp-activation-command.ts +662 -0
  120. package/src/mcp-activation/mcp-model-notice.ts +99 -0
  121. package/src/mcp-activation/mcp-model-status.ts +104 -0
  122. package/src/memory/__tests__/memory-command-module.test.ts +476 -0
  123. package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
  124. package/src/memory/index.ts +6 -0
  125. package/src/memory/memory-command-module.ts +77 -0
  126. package/src/memory/memory-command.ts +326 -0
  127. package/src/mode/__tests__/mode-command-module.test.ts +230 -0
  128. package/src/mode/index.ts +6 -0
  129. package/src/mode/mode-command-module.ts +55 -0
  130. package/src/mode/mode-command.ts +67 -0
  131. package/src/model/__tests__/model-command-module.test.ts +80 -0
  132. package/src/model/__tests__/model-command.test.ts +265 -0
  133. package/src/model/index.ts +6 -0
  134. package/src/model/model-command-module.ts +56 -0
  135. package/src/model/model-command.ts +249 -0
  136. package/src/output-style/__tests__/output-style-command.test.ts +96 -0
  137. package/src/output-style/index.ts +6 -0
  138. package/src/output-style/output-style-command-module.ts +143 -0
  139. package/src/peers/__tests__/peers-command-module.test.ts +24 -0
  140. package/src/peers/__tests__/peers-command.test.ts +399 -0
  141. package/src/peers/index.ts +9 -0
  142. package/src/peers/peers-command-module.ts +49 -0
  143. package/src/peers/peers-command.ts +220 -0
  144. package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
  145. package/src/permissions/index.ts +6 -0
  146. package/src/permissions/permissions-command-module.ts +55 -0
  147. package/src/permissions/permissions-command.ts +89 -0
  148. package/src/plan/__tests__/plan-command.test.ts +129 -0
  149. package/src/plan/index.ts +9 -0
  150. package/src/plan/plan-command-module.ts +49 -0
  151. package/src/plan/plan-command.ts +92 -0
  152. package/src/plugin/__tests__/plugin-active-turn.test.ts +100 -0
  153. package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
  154. package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
  155. package/src/plugin/index.ts +7 -0
  156. package/src/plugin/plugin-command-module.ts +85 -0
  157. package/src/plugin/plugin-command.ts +271 -0
  158. package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
  159. package/src/preset/__tests__/preset-command-module.test.ts +220 -0
  160. package/src/preset/index.ts +6 -0
  161. package/src/preset/preset-command-module.ts +80 -0
  162. package/src/preset/preset-command.ts +148 -0
  163. package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
  164. package/src/provider/__tests__/org-policy.test.ts +174 -0
  165. package/src/provider/__tests__/provider-command-module.test.ts +884 -0
  166. package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
  167. package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
  168. package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
  169. package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
  170. package/src/provider/__tests__/scripted-interaction.ts +40 -0
  171. package/src/provider/index.ts +32 -0
  172. package/src/provider/provider-command-execution.ts +235 -0
  173. package/src/provider/provider-command-module.ts +82 -0
  174. package/src/provider/provider-command-profile-lifecycle.ts +239 -0
  175. package/src/provider/provider-command-profile-operations.ts +218 -0
  176. package/src/provider/provider-command-profile.ts +116 -0
  177. package/src/provider/provider-command-setup.ts +79 -0
  178. package/src/provider/provider-connection-setup.ts +287 -0
  179. package/src/provider/provider-connection-validation.ts +38 -0
  180. package/src/provider/provider-onboarding.ts +73 -0
  181. package/src/provider/provider-prompt-interaction.ts +28 -0
  182. package/src/provider/provider-setup-ask.ts +67 -0
  183. package/src/provider/provider-setup-flow.ts +326 -0
  184. package/src/provider/provider-startup.ts +225 -0
  185. package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
  186. package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
  187. package/src/remote-control/index.ts +8 -0
  188. package/src/remote-control/remote-control-command-module.ts +61 -0
  189. package/src/remote-control/remote-control-command.ts +138 -0
  190. package/src/reset/__tests__/reset-command-module.test.ts +63 -0
  191. package/src/reset/index.ts +2 -0
  192. package/src/reset/reset-command-module.ts +47 -0
  193. package/src/reset/reset-command.ts +10 -0
  194. package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
  195. package/src/rewind/index.ts +2 -0
  196. package/src/rewind/rewind-command-module.ts +56 -0
  197. package/src/rewind/rewind-command.ts +279 -0
  198. package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
  199. package/src/sandbox/index.ts +2 -0
  200. package/src/sandbox/sandbox-command-module.ts +56 -0
  201. package/src/sandbox/sandbox-command.ts +94 -0
  202. package/src/schedule/__tests__/loop-command-functional.test.ts +399 -0
  203. package/src/schedule/__tests__/loop-command.test.ts +594 -0
  204. package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
  205. package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
  206. package/src/schedule/__tests__/schedule-command.test.ts +247 -0
  207. package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
  208. package/src/schedule/index.ts +12 -0
  209. package/src/schedule/loop-command.ts +427 -0
  210. package/src/schedule/loop-jitter.ts +41 -0
  211. package/src/schedule/schedule-command-module.ts +184 -0
  212. package/src/schedule/schedule-command.ts +161 -0
  213. package/src/schedule/schedule-spec-parser.ts +70 -0
  214. package/src/session/__tests__/model-pricing.test.ts +66 -0
  215. package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
  216. package/src/session/__tests__/session-command-module.test.ts +589 -0
  217. package/src/session/index.ts +17 -0
  218. package/src/session/model-pricing.ts +26 -0
  219. package/src/session/session-command-module.ts +197 -0
  220. package/src/session/session-command.ts +272 -0
  221. package/src/settings/index.ts +7 -0
  222. package/src/settings/settings-command-module.ts +47 -0
  223. package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
  224. package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
  225. package/src/shell/index.ts +8 -0
  226. package/src/shell/resolve-shell.ts +25 -0
  227. package/src/shell/shell-command-module.ts +53 -0
  228. package/src/shell/shell-command.ts +48 -0
  229. package/src/shell/spawn-inherited.ts +32 -0
  230. package/src/skills/__tests__/skill-read-command.test.ts +64 -0
  231. package/src/skills/__tests__/skills-command-module.test.ts +200 -0
  232. package/src/skills/index.ts +7 -0
  233. package/src/skills/skill-read-command.ts +60 -0
  234. package/src/skills/skills-command-module.ts +76 -0
  235. package/src/skills/skills-command.ts +108 -0
  236. package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
  237. package/src/statusline/index.ts +6 -0
  238. package/src/statusline/statusline-command-module.ts +55 -0
  239. package/src/statusline/statusline-command.ts +80 -0
  240. package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
  241. package/src/terminal-client/index.ts +5 -0
  242. package/src/terminal-client/terminal-client-commands.ts +109 -0
  243. package/src/theme/__tests__/theme-command.test.ts +204 -0
  244. package/src/theme/index.ts +12 -0
  245. package/src/theme/theme-command-module.ts +221 -0
  246. package/src/user-local/__tests__/user-local-command.test.ts +180 -0
  247. package/src/user-local/index.ts +13 -0
  248. package/src/user-local/user-local-command-constants.ts +5 -0
  249. package/src/user-local/user-local-command-module.ts +65 -0
  250. package/src/user-local/user-local-command.ts +214 -0
  251. package/src/user-local/user-local-memory-command.ts +160 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * CLI-083 (issue #2287) — the default command modules forward the org policy.
3
+ *
4
+ * `createDefaultCommandModules` used to accept `orgPolicy` and hand it to the provider command
5
+ * module; `92596bc6f` removed the parameter along with its only caller. The consumer never changed:
6
+ * `provider-command-profile-operations.ts` still destructures `orgPolicy` and enforces
7
+ * `allowedProviders` on it.
8
+ *
9
+ * `org-policy.test.ts` already covers that enforcement — by constructing the provider module
10
+ * DIRECTLY with a policy. That test is green whether or not anything upstream forwards one, which
11
+ * is why the cut survived three months. This drives the same enforcement through the factory that
12
+ * lost the parameter, so it fails exactly when the forwarding is missing.
13
+ *
14
+ * There is deliberately no "the factory accepts an orgPolicy" case. At runtime JavaScript accepts an
15
+ * undeclared extra property silently, so that assertion passes whether or not the parameter exists —
16
+ * it was written, it passed against the broken tree, and it proved nothing. The signature is the
17
+ * type checker's job.
18
+ */
19
+
20
+ import { mkdtempSync, rmSync } from 'node:fs';
21
+ import { tmpdir } from 'node:os';
22
+ import { join } from 'node:path';
23
+
24
+ import { afterAll, describe, expect, it } from 'vitest';
25
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
26
+ import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
27
+
28
+ import { createDefaultCommandModules } from '../default-command-modules.js';
29
+
30
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
31
+ import type {
32
+ IOrgPolicy,
33
+ IProviderCommandSettingsAdapter,
34
+ TProviderSettingsDocument,
35
+ } from '@robota-sdk/agent-framework';
36
+
37
+ const providerDefinitions: readonly IProviderDefinition[] = ['anthropic', 'openai'].map((type) => ({
38
+ type,
39
+ displayName: type,
40
+ defaults: { model: 'm', apiKey: `$ENV:${type.toUpperCase()}_API_KEY` },
41
+ setupSteps: [],
42
+ requiresApiKey: true,
43
+ createProvider: () => {
44
+ throw new Error('not used');
45
+ },
46
+ }));
47
+
48
+ const SETTINGS: TProviderSettingsDocument = {
49
+ currentProvider: 'anthropic',
50
+ providers: {
51
+ anthropic: { type: 'anthropic', model: 'm' },
52
+ openai: { type: 'openai', model: 'm' },
53
+ },
54
+ };
55
+
56
+ const adapter: IProviderCommandSettingsAdapter = {
57
+ readMergedSettings: () => SETTINGS,
58
+ readTargetSettings: () => ({}),
59
+ writeTargetSettings: () => undefined,
60
+ };
61
+
62
+ /** User-local storage lives in a private per-run directory, never a fixed name under /tmp. */
63
+ const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'agent-test-test-'));
64
+ afterAll(() => rmSync(USER_LOCAL_STORAGE_ROOT, { recursive: true, force: true }));
65
+
66
+ const POLICY: IOrgPolicy = { allowedProviders: ['anthropic'], adminContact: 'ops@example.com' };
67
+
68
+ /** Build the provider executor the way the PRODUCT does — through the default-modules factory. */
69
+ function executorThroughFactory(orgPolicy?: IOrgPolicy): SystemCommandExecutor {
70
+ const { modules } = createDefaultCommandModules({
71
+ cwd: '/work',
72
+ userLocalStorageRoot: USER_LOCAL_STORAGE_ROOT,
73
+ providerDefinitions,
74
+ providerSettingsAdapter: adapter,
75
+ ...(orgPolicy === undefined ? {} : { orgPolicy }),
76
+ });
77
+ const provider = modules.find((module) => module.name === 'agent-command-provider');
78
+ if (provider === undefined) throw new Error('the provider command module was not built');
79
+ return new SystemCommandExecutor([...(provider.systemCommands ?? [])]);
80
+ }
81
+
82
+ describe('CLI-083: the default command modules forward the org policy', () => {
83
+ it('blocks a disallowed provider switch when a policy is supplied to the FACTORY', async () => {
84
+ const result = await executorThroughFactory(POLICY).execute(
85
+ 'provider',
86
+ createTestCommandHost(),
87
+ 'switch openai',
88
+ );
89
+
90
+ expect(result?.success).toBe(false);
91
+ expect(result?.message).toContain('"openai" is not allowed');
92
+ expect(result?.message).toContain('ops@example.com');
93
+ });
94
+
95
+ it('does not block when no policy is supplied — absence stays distinguishable', async () => {
96
+ // The control. A factory that attached a policy of its own would satisfy the case above and
97
+ // change behaviour for every user who has no org policy at all.
98
+ const result = await executorThroughFactory().execute(
99
+ 'provider',
100
+ createTestCommandHost(),
101
+ 'switch openai',
102
+ );
103
+
104
+ expect(result?.message ?? '').not.toContain('is not allowed');
105
+ });
106
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * #3459 — the default command modules hand `/provider` and `/model` the host's environment snapshot.
3
+ *
4
+ * A host that withholds its own credentials from `process.env` passes the snapshot it took first as
5
+ * `providerEnvironment`. This drives both commands through the factory, with the key gone from the
6
+ * live env, so it fails exactly when the factory stops forwarding the snapshot.
7
+ */
8
+
9
+ import { mkdtempSync, rmSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+
13
+ import { afterAll, describe, expect, it } from 'vitest';
14
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
15
+ import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
16
+
17
+ import { createDefaultCommandModules } from '../default-command-modules.js';
18
+
19
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
20
+ import type { IProviderCommandSettingsAdapter, TProviderSettingsDocument } from '@robota-sdk/agent-framework';
21
+
22
+ const KEY = 'OPENAI_API_KEY';
23
+ const providerDefinitions: readonly IProviderDefinition[] = [
24
+ {
25
+ type: 'anthropic',
26
+ displayName: 'anthropic',
27
+ defaults: { model: 'm' },
28
+ modelCatalog: { status: 'live', entries: [{ id: 'm', displayName: 'm' }] },
29
+ createProvider: () => {
30
+ throw new Error('not used');
31
+ },
32
+ },
33
+ {
34
+ type: 'openai',
35
+ displayName: 'openai',
36
+ defaults: { apiKey: `$ENV:${KEY}` },
37
+ modelCatalog: { status: 'unavailable' },
38
+ requiresApiKey: true,
39
+ createProvider: () => {
40
+ throw new Error('not used');
41
+ },
42
+ },
43
+ ];
44
+
45
+ const SETTINGS: TProviderSettingsDocument = {
46
+ currentProvider: 'anthropic',
47
+ providers: { anthropic: { type: 'anthropic', model: 'm' }, openai: { type: 'openai', model: 'gpt-x' } },
48
+ };
49
+ const adapter: IProviderCommandSettingsAdapter = {
50
+ readMergedSettings: () => SETTINGS,
51
+ readTargetSettings: () => ({}),
52
+ writeTargetSettings: () => undefined,
53
+ };
54
+
55
+ const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'agent-test-provider-env-'));
56
+ afterAll(() => rmSync(USER_LOCAL_STORAGE_ROOT, { recursive: true, force: true }));
57
+
58
+ function executorThroughFactory(moduleName: string): SystemCommandExecutor {
59
+ const { modules } = createDefaultCommandModules({
60
+ cwd: '/work',
61
+ userLocalStorageRoot: USER_LOCAL_STORAGE_ROOT,
62
+ providerDefinitions,
63
+ providerSettingsAdapter: adapter,
64
+ providerEnvironment: { [KEY]: 'snapshot-key' },
65
+ });
66
+ const found = modules.find((module) => module.name === moduleName);
67
+ if (found === undefined) throw new Error(`${moduleName} was not built`);
68
+ return new SystemCommandExecutor([...(found.systemCommands ?? [])]);
69
+ }
70
+
71
+ async function withoutLiveKey<T>(run: () => Promise<T>): Promise<T> {
72
+ const live = process.env[KEY];
73
+ delete process.env[KEY];
74
+ try {
75
+ return await run();
76
+ } finally {
77
+ if (live !== undefined) process.env[KEY] = live;
78
+ }
79
+ }
80
+
81
+ describe('#3459: the default command modules forward the provider environment', () => {
82
+ it('/provider switch checks the key against the snapshot', async () => {
83
+ const result = await withoutLiveKey(() =>
84
+ executorThroughFactory('agent-command-provider').execute('provider', createTestCommandHost(), 'switch openai'),
85
+ );
86
+
87
+ expect(result?.success).toBe(true);
88
+ });
89
+
90
+ it('a cross-profile /model checks the key against the snapshot', async () => {
91
+ const host = createTestCommandHost({ session: { getModelId: () => 'm' } });
92
+ const result = await withoutLiveKey(() =>
93
+ executorThroughFactory('agent-command-model').execute('model', host, 'gpt-x'),
94
+ );
95
+
96
+ expect(result?.success).toBe(true);
97
+ });
98
+ });
@@ -0,0 +1,247 @@
1
+ import { findUnknownModuleNames, selectCommandModules } from '@robota-sdk/agent-framework';
2
+
3
+ import { createAgentCommandModule } from '../agent/index.js';
4
+ import { createBackgroundCommandModule } from '../background/index.js';
5
+ import { createCompactCommandModule } from '../compact/index.js';
6
+ import { createContextCommandModule } from '../context/index.js';
7
+ import { createDevicesCommandModule } from '../devices/index.js';
8
+ import { createDoctorCommandModule } from '../doctor/index.js';
9
+ import { createEditorCommandModule } from '../editor/index.js';
10
+ import { createAdvisorCommandModule } from '../advisor/index.js';
11
+ import { createEffortCommandModule } from '../effort/index.js';
12
+ import { createExitCommandModule } from '../exit/index.js';
13
+ import { createForkCommandModule } from '../fork/index.js';
14
+ import { createGitCommandModule } from '../git/index.js';
15
+ import { createGoalCommandModule } from '../goal/index.js';
16
+ import { createHandoffCommandModule } from '../handoff/index.js';
17
+ import { createHelpCommandModule } from '../help/index.js';
18
+ import { createKeybindingsCommandModule } from '../keybindings/index.js';
19
+ import { createLanguageCommandModule } from '../language/index.js';
20
+ import { createMCPActivationCommandModule } from '../mcp-activation/index.js';
21
+ import { createMemoryCommandModule } from '../memory/index.js';
22
+ import { createModeCommandModule } from '../mode/index.js';
23
+ import { createModelCommandModule } from '../model/index.js';
24
+ import { createSandboxCommandModule } from '../sandbox/index.js';
25
+ import { createOutputStyleCommandModule } from '../output-style/index.js';
26
+ import { createEventsCommandModule } from '../events/index.js';
27
+ import { createPeersCommandModule } from '../peers/index.js';
28
+ import { createPermissionsCommandModule } from '../permissions/index.js';
29
+ import { createPlanCommandModule } from '../plan/index.js';
30
+ import { createPluginCommandModule } from '../plugin/index.js';
31
+ import { createPresetCommandModule } from '../preset/index.js';
32
+ import { createProviderCommandModule } from '../provider/index.js';
33
+ import { createRemoteControlCommandModule } from '../remote-control/index.js';
34
+ import { createResetCommandModule } from '../reset/index.js';
35
+ import { createRewindCommandModule } from '../rewind/index.js';
36
+ import { createScheduleCommandModule } from '../schedule/index.js';
37
+ import { createSessionCommandModule } from '../session/index.js';
38
+ import { createSettingsCommandModule } from '../settings/index.js';
39
+ import { createShellCommandModule } from '../shell/index.js';
40
+ import { createSkillsCommandModule } from '../skills/index.js';
41
+ import { createStatusLineCommandModule } from '../statusline/index.js';
42
+ import { createThemeCommandModule } from '../theme/index.js';
43
+ import { createUserLocalCommandModule } from '../user-local/index.js';
44
+
45
+ import type { IDevicesCommandPort } from '../devices/index.js';
46
+ import type { IDoctorDisplayVocabulary, IDoctorInputs } from '../doctor/index.js';
47
+ import type { IKeybindingsFilePort } from '../keybindings/index.js';
48
+ import type { IProviderDefinition, TProviderCredentialResolver } from '@robota-sdk/agent-core';
49
+ import type {
50
+ IOrgPolicy,
51
+ IContributionSource,
52
+ ISkillRootDescriptor,
53
+ ICommandModule,
54
+ IProviderCommandSettingsAdapter,
55
+ IProviderConnectionHost,
56
+ IUnknownCommandModuleName,
57
+ } from '@robota-sdk/agent-framework';
58
+ import type { IThemeCataloguePort } from '@robota-sdk/agent-interface-command';
59
+
60
+ export interface IDefaultCommandModulesOptions {
61
+ cwd: string;
62
+ userLocalStorageRoot: string;
63
+ /** Product-owned prefix for the interactive editor's temporary directory. */
64
+ editorTemporaryDirectoryPrefix?: string;
65
+ contributionSources?: readonly IContributionSource[];
66
+ skillRoots?: readonly ISkillRootDescriptor[];
67
+ providerDefinitions: readonly IProviderDefinition[];
68
+ providerSettingsAdapter: IProviderCommandSettingsAdapter;
69
+ /**
70
+ * CLI-083 (issue #2287) — the org policy, so `allowedProviders` and `requireApiKeyFromEnv` are
71
+ * reachable. This parameter existed, was removed by `92596bc6f` along with its only caller, and
72
+ * the consumer never stopped reading it: `provider-command-profile-operations.ts` still
73
+ * destructures `orgPolicy` from its options and enforces on it. Optional, so its absence stays
74
+ * distinguishable from a policy that allows everything — and that optionality is why nothing went
75
+ * red when the producer dropped it.
76
+ */
77
+ orgPolicy?: IOrgPolicy;
78
+ /**
79
+ * Where `/provider` and `/model` check a `$ENV:` or default credential variable: the host's startup
80
+ * snapshot when it withholds its own credentials from `process.env`. Absent, `process.env` is read.
81
+ */
82
+ providerEnvironment?: Readonly<Record<string, string | undefined>>;
83
+ /** Host-owned connection acquisition and transactional credential persistence. */
84
+ providerConnectionHost?: IProviderConnectionHost;
85
+ /** Host-owned resolution of an opaque saved credential reference. */
86
+ providerCredentialResolver?: TProviderCredentialResolver;
87
+ /** Optional TUI-owned file capability; absent, `/keybindings` says it belongs to an interactive terminal. */
88
+ keybindingsFilePort?: IKeybindingsFilePort;
89
+ /**
90
+ * SCREEN-2002: the surface's theme catalogue. Absent (print mode, `--serve`), `/theme` is still
91
+ * listed and says it belongs to an interactive terminal, which runs it when attached.
92
+ */
93
+ themeCataloguePort?: IThemeCataloguePort;
94
+ /**
95
+ * The host that keeps this device's identity (its terminal, credential store and state files).
96
+ * Absence means `/devices` is not registered: without it the command could do nothing.
97
+ */
98
+ devicesPort?: IDevicesCommandPort;
99
+ /** OBSERVABILITY-1991: host-composed doctor inputs; absence means `/doctor` is not registered. */
100
+ doctorInputs?: IDoctorInputs;
101
+ /** Product-owned diagnostic wording; absent keeps `/doctor` product-neutral. */
102
+ doctorDisplay?: IDoctorDisplayVocabulary;
103
+ /** Product-owned command to resume a saved fork; absent yields neutral host guidance. */
104
+ formatForkResumeCommand?: (sessionId: string) => string;
105
+ /** Host-owned fallback text and kill switch for session-local repeat. */
106
+ loopOptions?: { defaultPrompt?: string; resolveDefaultPrompt?: () => string; disabled?: boolean };
107
+ /**
108
+ * Whitelist of module `name`s to keep. When provided, only modules whose `name`
109
+ * appears here survive. Omitted → all modules kept (no-regression).
110
+ */
111
+ enabledCommandModules?: readonly string[];
112
+ /**
113
+ * Blacklist of module `name`s to remove. Applied after the whitelist, so a name
114
+ * present in both is removed (deny > allow). Omitted → no modules removed.
115
+ */
116
+ disabledCommandModules?: readonly string[];
117
+ }
118
+
119
+ /**
120
+ * Apply the preset module-selection delta to the default module set.
121
+ *
122
+ * Rules: if `enabled` is provided, keep only modules whose `name` is in it; then
123
+ * remove any module whose `name` is in `disabled` (deny > allow). Neither given →
124
+ * the full default set is returned unchanged (no-regression).
125
+ *
126
+ * INFRA-032: delegates to agent-framework's `selectCommandModules` (the allowed command→framework
127
+ * edge) so the filter body exists once — the previously byte-identical copy here is collapsed.
128
+ */
129
+ function applyModuleSelection(
130
+ modules: readonly ICommandModule[],
131
+ enabled: readonly string[] | undefined,
132
+ disabled: readonly string[] | undefined,
133
+ ): readonly ICommandModule[] {
134
+ return selectCommandModules(modules, enabled, disabled);
135
+ }
136
+
137
+ /**
138
+ * Result of {@link createDefaultCommandModules} (INFRA-032): the selected `modules` plus any preset
139
+ * `enabledCommandModules`/`disabledCommandModules` names that matched no built module. Unknown names
140
+ * are returned as data (not dropped silently) so the CLI startup path can surface a non-fatal notice.
141
+ */
142
+ export interface IDefaultCommandModulesResult {
143
+ readonly modules: readonly ICommandModule[];
144
+ readonly unknownModuleNames: readonly IUnknownCommandModuleName[];
145
+ }
146
+
147
+ export function createDefaultCommandModules({
148
+ cwd: _cwd,
149
+ userLocalStorageRoot,
150
+ editorTemporaryDirectoryPrefix,
151
+ contributionSources,
152
+ skillRoots,
153
+ providerDefinitions,
154
+ providerSettingsAdapter,
155
+ orgPolicy,
156
+ keybindingsFilePort,
157
+ themeCataloguePort,
158
+ devicesPort,
159
+ doctorInputs,
160
+ doctorDisplay,
161
+ formatForkResumeCommand,
162
+ loopOptions,
163
+ enabledCommandModules,
164
+ disabledCommandModules,
165
+ providerEnvironment,
166
+ providerConnectionHost,
167
+ providerCredentialResolver,
168
+ }: IDefaultCommandModulesOptions): IDefaultCommandModulesResult {
169
+ const modules: readonly ICommandModule[] = [
170
+ createSkillsCommandModule({ contributionSources: contributionSources ?? [], skillRoots }),
171
+ createHelpCommandModule(),
172
+ createAgentCommandModule(),
173
+ createEffortCommandModule(),
174
+ createAdvisorCommandModule(),
175
+ createPermissionsCommandModule(),
176
+ createModeCommandModule(),
177
+ createSandboxCommandModule(),
178
+ createPresetCommandModule(),
179
+ createOutputStyleCommandModule(),
180
+ createLanguageCommandModule(),
181
+ createBackgroundCommandModule(),
182
+ // CLI-1994: beside `/background`, because a fork IS a background job — the one it starts is
183
+ // listed, peeked at, stopped and attached to through that command and its panel.
184
+ createForkCommandModule(formatForkResumeCommand),
185
+ createGoalCommandModule(),
186
+ createPlanCommandModule(),
187
+ createShellCommandModule(),
188
+ createEditorCommandModule(editorTemporaryDirectoryPrefix),
189
+ createGitCommandModule(),
190
+ createKeybindingsCommandModule(keybindingsFilePort),
191
+ createThemeCommandModule(themeCataloguePort),
192
+ ...(doctorInputs === undefined
193
+ ? []
194
+ : [createDoctorCommandModule(doctorInputs, undefined, doctorDisplay)]),
195
+ createMemoryCommandModule(),
196
+ createMCPActivationCommandModule(),
197
+ createUserLocalCommandModule(userLocalStorageRoot),
198
+ createCompactCommandModule(),
199
+ createContextCommandModule(),
200
+ createExitCommandModule(),
201
+ createSessionCommandModule(),
202
+ createResetCommandModule(),
203
+ createRewindCommandModule(),
204
+ createScheduleCommandModule(loopOptions),
205
+ createStatusLineCommandModule(),
206
+ createPluginCommandModule(),
207
+ createSettingsCommandModule(),
208
+ createPeersCommandModule(),
209
+ createEventsCommandModule(),
210
+ // HANDOFF-001 (issue #1864). Registered even though no product wires the carrier adapter yet:
211
+ // the command's own answer to a host without one is to say so, which is a better state than a
212
+ // capability nobody can see. It is also what makes the carrier's arrival observable.
213
+ createHandoffCommandModule(),
214
+ createRemoteControlCommandModule(),
215
+ ...(devicesPort === undefined ? [] : [createDevicesCommandModule(devicesPort)]),
216
+ createProviderCommandModule({
217
+ providerDefinitions,
218
+ settings: providerSettingsAdapter,
219
+ ...(orgPolicy === undefined ? {} : { orgPolicy }),
220
+ ...(providerEnvironment === undefined ? {} : { env: providerEnvironment }),
221
+ ...(providerConnectionHost === undefined ? {} : { connectionHost: providerConnectionHost }),
222
+ ...(providerCredentialResolver === undefined
223
+ ? {}
224
+ : { resolveCredential: providerCredentialResolver }),
225
+ }),
226
+ // #3282 §2: shares the provider command's settings adapter and definitions — one settings write
227
+ // owner, never a second copy that could drift from what `/provider switch` reads and writes.
228
+ createModelCommandModule({
229
+ providerDefinitions,
230
+ settings: providerSettingsAdapter,
231
+ ...(orgPolicy === undefined ? {} : { orgPolicy }),
232
+ ...(providerEnvironment === undefined ? {} : { env: providerEnvironment }),
233
+ ...(providerCredentialResolver === undefined
234
+ ? {}
235
+ : { resolveCredential: providerCredentialResolver }),
236
+ }),
237
+ ];
238
+ const builtModuleNames = modules.map((module) => module.name);
239
+ return {
240
+ modules: applyModuleSelection(modules, enabledCommandModules, disabledCommandModules),
241
+ unknownModuleNames: findUnknownModuleNames(
242
+ builtModuleNames,
243
+ enabledCommandModules,
244
+ disabledCommandModules,
245
+ ),
246
+ };
247
+ }
@@ -0,0 +1,5 @@
1
+ export type {
2
+ IDefaultCommandModulesOptions,
3
+ IDefaultCommandModulesResult,
4
+ } from './default-command-modules.js';
5
+ export { createDefaultCommandModules } from './default-command-modules.js';