@robota-sdk/agent-command 3.0.0-beta.67 → 3.0.0-beta.69

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 (31) hide show
  1. package/README.md +0 -1
  2. package/dist/node/index.cjs +57 -28
  3. package/dist/node/index.d.ts +3 -16
  4. package/dist/node/index.d.ts.map +1 -1
  5. package/dist/node/index.js +57 -28
  6. package/dist/node/index.js.map +1 -1
  7. package/package.json +3 -3
  8. package/src/compact/__tests__/compact-command-module.test.ts +13 -4
  9. package/src/compact/compact-command-module.ts +2 -0
  10. package/src/compact/compact-command.ts +11 -4
  11. package/src/context/__tests__/context-command-module.test.ts +42 -0
  12. package/src/default/default-command-modules.ts +0 -5
  13. package/src/help/__tests__/help-command.test.ts +119 -0
  14. package/src/index.ts +0 -1
  15. package/src/provider/__tests__/org-policy.test.ts +165 -0
  16. package/src/provider/__tests__/provider-command-module.test.ts +18 -29
  17. package/src/provider/__tests__/provider-setup-flow.test.ts +2 -2
  18. package/src/provider/provider-command-execution.ts +2 -2
  19. package/src/provider/provider-command-module.ts +4 -2
  20. package/src/provider/provider-command-profile-operations.ts +37 -49
  21. package/src/provider/provider-onboarding.ts +64 -0
  22. package/src/provider/provider-setup-flow.ts +11 -2
  23. package/src/provider/provider-startup.ts +15 -3
  24. package/src/session/__tests__/model-pricing.test.ts +64 -0
  25. package/src/session/__tests__/session-command-module.test.ts +51 -7
  26. package/src/session/model-pricing.ts +79 -0
  27. package/src/session/session-command.ts +124 -8
  28. package/src/model/__tests__/model-command-module.test.ts +0 -273
  29. package/src/model/index.ts +0 -6
  30. package/src/model/model-command-module.ts +0 -70
  31. package/src/model/model-command.ts +0 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-command",
3
- "version": "3.0.0-beta.67",
3
+ "version": "3.0.0-beta.69",
4
4
  "description": "Consolidated command module implementations for Robota SDK CLI",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -24,8 +24,8 @@
24
24
  "src"
25
25
  ],
26
26
  "dependencies": {
27
- "@robota-sdk/agent-core": "3.0.0-beta.67",
28
- "@robota-sdk/agent-framework": "3.0.0-beta.67"
27
+ "@robota-sdk/agent-core": "3.0.0-beta.69",
28
+ "@robota-sdk/agent-framework": "3.0.0-beta.69"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.14.0",
@@ -20,8 +20,9 @@ const AFTER_CONTEXT: TContextWindowState = {
20
20
  remainingPercentage: 65,
21
21
  };
22
22
 
23
- function createRuntime(): ICommandSessionRuntime {
23
+ function createRuntime(beforeCount = 10, afterCount = 3): ICommandSessionRuntime {
24
24
  let mode: TPermissionMode = 'default';
25
+ const getMessageCount = vi.fn().mockReturnValueOnce(beforeCount).mockReturnValue(afterCount);
25
26
  return {
26
27
  clearHistory: vi.fn(),
27
28
  compact: vi.fn().mockResolvedValue(undefined),
@@ -31,7 +32,7 @@ function createRuntime(): ICommandSessionRuntime {
31
32
  mode = nextMode;
32
33
  },
33
34
  getSessionId: () => 'session_1',
34
- getMessageCount: () => 1,
35
+ getMessageCount,
35
36
  getSessionAllowedTools: () => [],
36
37
  getAutoCompactThreshold: () => 0.835,
37
38
  };
@@ -104,6 +105,7 @@ describe('createCompactCommandModule', () => {
104
105
  modelInvocable: true,
105
106
  argumentHint: '[instructions]',
106
107
  safety: 'write',
108
+ requiresPermission: false,
107
109
  },
108
110
  ]);
109
111
  });
@@ -117,8 +119,15 @@ describe('createCompactCommandModule', () => {
117
119
  const result = await executor.execute('compact', context, ' focus on tests ');
118
120
 
119
121
  expect(result?.success).toBe(true);
120
- expect(result?.message).toBe('Context compacted: 80% -> 35%');
121
- expect(result?.data).toEqual({ before: 80, after: 35 });
122
+ expect(result?.message).toBe(
123
+ 'Context compacted.\n Removed messages: 7 (70% of total)\n Context: 80% → 35%',
124
+ );
125
+ expect(result?.data).toEqual({
126
+ before: BEFORE_CONTEXT,
127
+ after: AFTER_CONTEXT,
128
+ beforeMessageCount: 10,
129
+ afterMessageCount: 3,
130
+ });
122
131
  expect(context.compactContext).toHaveBeenCalledWith('focus on tests');
123
132
  });
124
133
 
@@ -16,6 +16,7 @@ export function createCompactCommandEntry(): ICommand {
16
16
  modelInvocable: true,
17
17
  argumentHint: '[instructions]',
18
18
  safety: 'write',
19
+ example: '/compact Summarize the current context',
19
20
  };
20
21
  }
21
22
 
@@ -25,6 +26,7 @@ function createCompactSystemCommand(): ISystemCommand {
25
26
  name: entry.name,
26
27
  displayName: entry.displayName,
27
28
  description: entry.description,
29
+ example: entry.example,
28
30
  requiresPermission: false,
29
31
  userInvocable: true,
30
32
  modelInvocable: entry.modelInvocable,
@@ -12,11 +12,18 @@ export async function executeCompactCommand(
12
12
  args: string,
13
13
  ): Promise<ICommandResult> {
14
14
  const result = await compactCommandContext(context, parseInstructions(args));
15
- const before = result.before.usedPercentage;
16
- const after = result.after.usedPercentage;
15
+ const { before, after, beforeMessageCount, afterMessageCount } = result;
16
+ const removedMessages = beforeMessageCount - afterMessageCount;
17
+ const removedPercent =
18
+ beforeMessageCount > 0 ? Math.round((removedMessages / beforeMessageCount) * 100) : 0;
19
+ const message = [
20
+ 'Context compacted.',
21
+ ` Removed messages: ${removedMessages} (${removedPercent}% of total)`,
22
+ ` Context: ${Math.round(before.usedPercentage)}% → ${Math.round(after.usedPercentage)}%`,
23
+ ].join('\n');
17
24
  return {
18
- message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
25
+ message,
19
26
  success: true,
20
- data: { before, after },
27
+ data: { before, after, beforeMessageCount, afterMessageCount },
21
28
  };
22
29
  }
@@ -45,6 +45,18 @@ const PROMPT_REFERENCE: IContextReferenceItem = {
45
45
  lastUsedAt: '2026-05-05T00:00:01.000Z',
46
46
  };
47
47
 
48
+ const SYSTEM_REFERENCE: IContextReferenceItem = {
49
+ id: 'system:AGENTS.md',
50
+ sourcePath: '/workspace/AGENTS.md',
51
+ relativePath: 'AGENTS.md',
52
+ originalReference: 'AGENTS.md',
53
+ loadType: 'system',
54
+ status: 'active',
55
+ byteLength: 1024,
56
+ loadedAt: '2026-05-05T00:00:00.000Z',
57
+ lastUsedAt: '2026-05-05T00:00:00.000Z',
58
+ };
59
+
48
60
  function createRuntime(state: { threshold: number | false }): ICommandSessionRuntime {
49
61
  let mode: TPermissionMode = 'default';
50
62
  return {
@@ -291,4 +303,34 @@ describe('createContextCommandModule', () => {
291
303
  expect(result?.success).toBe(false);
292
304
  expect(result?.message).toContain('greater than 0% and at most 100%');
293
305
  });
306
+
307
+ it('lists system context references with [system, active] label', async () => {
308
+ const context = createCommandHostContext();
309
+ context.references.push(SYSTEM_REFERENCE);
310
+
311
+ const result = await createExecutor().execute('context', context, 'list');
312
+
313
+ expect(result?.success).toBe(true);
314
+ expect(result?.message).toContain('AGENTS.md [system, active] 1,024 B');
315
+ expect(result?.data?.references).toEqual([SYSTEM_REFERENCE]);
316
+ });
317
+
318
+ it('includes system references in context summary active count', async () => {
319
+ const context = createCommandHostContext();
320
+ context.references.push(SYSTEM_REFERENCE, MANUAL_REFERENCE);
321
+
322
+ const result = await createExecutor().execute('context', context, '');
323
+
324
+ expect(result?.success).toBe(true);
325
+ expect(result?.message).toContain('References: 2 active, 0 observed');
326
+ });
327
+
328
+ it('shows no context references message when only system refs are absent', async () => {
329
+ const context = createCommandHostContext();
330
+
331
+ const result = await createExecutor().execute('context', context, 'list');
332
+
333
+ expect(result?.success).toBe(true);
334
+ expect(result?.message).toBe('No context references.');
335
+ });
294
336
  });
@@ -7,7 +7,6 @@ import { createHelpCommandModule } from '../help/index.js';
7
7
  import { createLanguageCommandModule } from '../language/index.js';
8
8
  import { createMemoryCommandModule } from '../memory/index.js';
9
9
  import { createModeCommandModule } from '../mode/index.js';
10
- import { createModelCommandModule } from '../model/index.js';
11
10
  import { createPermissionsCommandModule } from '../permissions/index.js';
12
11
  import { createPluginCommandModule } from '../plugin/index.js';
13
12
  import { createProviderCommandModule } from '../provider/index.js';
@@ -37,10 +36,6 @@ export function createDefaultCommandModules({
37
36
  createSkillsCommandModule({ cwd }),
38
37
  createHelpCommandModule(),
39
38
  createAgentCommandModule(),
40
- createModelCommandModule({
41
- providerDefinitions,
42
- settings: { readMergedSettings: () => providerSettingsAdapter.readMergedSettings() },
43
- }),
44
39
  createPermissionsCommandModule(),
45
40
  createModeCommandModule(),
46
41
  createLanguageCommandModule(),
@@ -0,0 +1,119 @@
1
+ import type {
2
+ ICommandHostContext,
3
+ ICommandListEntry,
4
+ ICommandSessionRuntime,
5
+ } from '@robota-sdk/agent-framework';
6
+ import { formatCommandHelpMessage } from '@robota-sdk/agent-framework';
7
+ import { describe, expect, it } from 'vitest';
8
+
9
+ function createCommandSessionRuntime(): ICommandSessionRuntime {
10
+ return {
11
+ clearHistory: () => undefined,
12
+ compact: async () => undefined,
13
+ getContextState: () => ({
14
+ maxTokens: 100,
15
+ usedTokens: 10,
16
+ usedPercentage: 10,
17
+ remainingPercentage: 90,
18
+ }),
19
+ getPermissionMode: () => 'default',
20
+ setPermissionMode: () => undefined,
21
+ getSessionId: () => 'session_1',
22
+ getMessageCount: () => 0,
23
+ getSessionAllowedTools: () => [],
24
+ getAutoCompactThreshold: () => false,
25
+ };
26
+ }
27
+
28
+ function createCheckpoint() {
29
+ return {
30
+ id: 'checkpoint_1',
31
+ sessionId: 'session_1',
32
+ sequence: 1,
33
+ prompt: 'prompt',
34
+ createdAt: '2026-05-25T00:00:00.000Z',
35
+ fileCount: 0,
36
+ };
37
+ }
38
+
39
+ function buildContext(commands: ICommandListEntry[]): ICommandHostContext {
40
+ const checkpoint = createCheckpoint();
41
+ return {
42
+ getSession: () => createCommandSessionRuntime(),
43
+ getContextState: () => ({
44
+ maxTokens: 100,
45
+ usedTokens: 10,
46
+ usedPercentage: 10,
47
+ remainingPercentage: 90,
48
+ }),
49
+ getAutoCompactThreshold: () => 0.8,
50
+ compactContext: async () => undefined,
51
+ getCwd: () => '/workspace',
52
+ listCommands: () => commands,
53
+ listEditCheckpoints: () => [],
54
+ restoreEditCheckpoint: async () => ({
55
+ target: checkpoint,
56
+ restoredCheckpointCount: 0,
57
+ restoredFileCount: 0,
58
+ removedCheckpointCount: 0,
59
+ }),
60
+ rollbackEditCheckpoint: async () => ({
61
+ target: checkpoint,
62
+ restoredCheckpointCount: 0,
63
+ restoredFileCount: 0,
64
+ removedCheckpointCount: 0,
65
+ }),
66
+ getUsedMemoryReferences: () => [],
67
+ recordMemoryEvent: () => undefined,
68
+ listBackgroundTasks: () => [],
69
+ readBackgroundTaskLog: async (taskId) => ({ taskId, lines: [] }),
70
+ cancelBackgroundTask: async () => undefined,
71
+ closeBackgroundTask: async () => undefined,
72
+ };
73
+ }
74
+
75
+ describe('formatCommandHelpMessage — example field', () => {
76
+ it('TC-01: includes Example line for compact command', () => {
77
+ const context = buildContext([
78
+ {
79
+ name: 'compact',
80
+ displayName: 'Compact Context',
81
+ description: 'Compress context window',
82
+ example: '/compact Summarize the current context',
83
+ },
84
+ ]);
85
+
86
+ const output = formatCommandHelpMessage(context);
87
+
88
+ expect(output).toContain('Example: /compact Summarize the current context');
89
+ });
90
+
91
+ it('TC-02: includes Example line for provider command', () => {
92
+ const context = buildContext([
93
+ {
94
+ name: 'provider',
95
+ displayName: 'Provider Setup',
96
+ description: 'Manage provider profiles',
97
+ example: '/provider switch production',
98
+ },
99
+ ]);
100
+
101
+ const output = formatCommandHelpMessage(context);
102
+
103
+ expect(output).toContain('Example: /provider switch production');
104
+ });
105
+
106
+ it('TC-03: omits Example line for commands without example', () => {
107
+ const context = buildContext([
108
+ {
109
+ name: 'help',
110
+ displayName: 'Help',
111
+ description: 'Show available commands',
112
+ },
113
+ ]);
114
+
115
+ const output = formatCommandHelpMessage(context);
116
+
117
+ expect(output).not.toContain('Example:');
118
+ });
119
+ });
package/src/index.ts CHANGED
@@ -8,7 +8,6 @@ export * from './help/index.js';
8
8
  export * from './language/index.js';
9
9
  export * from './memory/index.js';
10
10
  export * from './mode/index.js';
11
- export * from './model/index.js';
12
11
  export * from './permissions/index.js';
13
12
  export * from './plugin/index.js';
14
13
  export * from './provider/index.js';
@@ -0,0 +1,165 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
3
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
4
+ import type {
5
+ ICommandHostContext,
6
+ IOrgPolicy,
7
+ IProviderCommandSettingsAdapter,
8
+ TProviderSettingsDocument,
9
+ } from '@robota-sdk/agent-framework';
10
+ import { createProviderCommandModule } from '../provider-command-module.js';
11
+
12
+ const providerDefinitions: readonly IProviderDefinition[] = [
13
+ {
14
+ type: 'anthropic',
15
+ displayName: 'Anthropic',
16
+ defaults: { model: 'claude-sonnet-4-6', apiKey: '$ENV:ANTHROPIC_API_KEY' },
17
+ setupSteps: [
18
+ { key: 'apiKey', title: 'Anthropic API key', masked: true },
19
+ { key: 'model', title: 'Anthropic model', defaultValue: 'claude-sonnet-4-6' },
20
+ ],
21
+ requiresApiKey: true,
22
+ createProvider: () => {
23
+ throw new Error('not used');
24
+ },
25
+ },
26
+ {
27
+ type: 'openai',
28
+ displayName: 'OpenAI',
29
+ defaults: { model: 'gpt-4o', apiKey: '$ENV:OPENAI_API_KEY' },
30
+ setupSteps: [
31
+ { key: 'apiKey', title: 'OpenAI API key', masked: true },
32
+ { key: 'model', title: 'OpenAI model', defaultValue: 'gpt-4o' },
33
+ ],
34
+ requiresApiKey: true,
35
+ createProvider: () => {
36
+ throw new Error('not used');
37
+ },
38
+ },
39
+ ];
40
+
41
+ function createSettingsAdapter(
42
+ merged: TProviderSettingsDocument,
43
+ target: TProviderSettingsDocument = {},
44
+ ): { adapter: IProviderCommandSettingsAdapter } {
45
+ let writable = target;
46
+ return {
47
+ adapter: {
48
+ readMergedSettings: () => merged,
49
+ readTargetSettings: () => writable,
50
+ writeTargetSettings: (next) => {
51
+ writable = next;
52
+ },
53
+ },
54
+ };
55
+ }
56
+
57
+ function createExecutor(
58
+ adapter: IProviderCommandSettingsAdapter,
59
+ orgPolicy?: IOrgPolicy,
60
+ ): SystemCommandExecutor {
61
+ const module = createProviderCommandModule({ providerDefinitions, settings: adapter, orgPolicy });
62
+ return new SystemCommandExecutor([...(module.systemCommands ?? [])]);
63
+ }
64
+
65
+ const session = {} as ICommandHostContext;
66
+
67
+ describe('org policy enforcement in provider commands', () => {
68
+ afterEach(() => {
69
+ vi.unstubAllEnvs();
70
+ });
71
+ it('blocks /provider switch when target is not in allowedProviders', async () => {
72
+ const { adapter } = createSettingsAdapter({
73
+ currentProvider: 'anthropic',
74
+ providers: {
75
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
76
+ openai: { type: 'openai', model: 'gpt-4o' },
77
+ },
78
+ });
79
+ const orgPolicy: IOrgPolicy = {
80
+ allowedProviders: ['anthropic'],
81
+ adminContact: 'admin@example.com',
82
+ };
83
+
84
+ const result = await createExecutor(adapter, orgPolicy).execute(
85
+ 'provider',
86
+ session,
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('admin@example.com');
93
+ expect(result?.effects).toBeUndefined();
94
+ });
95
+
96
+ it('allows /provider switch when target is in allowedProviders', async () => {
97
+ const { adapter } = createSettingsAdapter({
98
+ currentProvider: 'anthropic',
99
+ providers: {
100
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
101
+ openai: { type: 'openai', model: 'gpt-4o' },
102
+ },
103
+ });
104
+ const orgPolicy: IOrgPolicy = { allowedProviders: ['anthropic', 'openai'] };
105
+
106
+ const result = await createExecutor(adapter, orgPolicy).execute(
107
+ 'provider',
108
+ session,
109
+ 'switch openai',
110
+ );
111
+
112
+ expect(result?.success).toBe(true);
113
+ expect(result?.effects).toEqual([
114
+ { type: 'provider-hot-swap-requested', profileName: 'openai' },
115
+ ]);
116
+ });
117
+
118
+ it('rejects plaintext API key during edit when requireApiKeyFromEnv is true', async () => {
119
+ const { adapter } = createSettingsAdapter({
120
+ currentProvider: 'anthropic',
121
+ providers: {
122
+ anthropic: {
123
+ type: 'anthropic',
124
+ model: 'claude-sonnet-4-6',
125
+ apiKey: 'sk-existing-key',
126
+ },
127
+ },
128
+ });
129
+ const orgPolicy: IOrgPolicy = { requireApiKeyFromEnv: true, adminContact: 'sec@example.com' };
130
+
131
+ const listed = await createExecutor(adapter, orgPolicy).execute('provider', session, 'list');
132
+ const selected = await listed?.interaction?.submit('anthropic');
133
+ const editRequested = await selected?.interaction?.submit('edit');
134
+ // Anthropic has 2 setup steps: apiKey then model
135
+ const modelPrompt = await editRequested?.interaction?.submit('sk-plaintext-key');
136
+ const completed = await modelPrompt?.interaction?.submit('claude-sonnet-4-6');
137
+
138
+ expect(completed?.success).toBe(false);
139
+ expect(completed?.message).toContain('environment variable references');
140
+ expect(completed?.message).toContain('sec@example.com');
141
+ });
142
+
143
+ it('allows env-ref API key during edit when requireApiKeyFromEnv is true', async () => {
144
+ vi.stubEnv('ORG_TEST_KEY', 'sk-test-resolved-value');
145
+ const { adapter } = createSettingsAdapter({
146
+ currentProvider: 'anthropic',
147
+ providers: {
148
+ anthropic: {
149
+ type: 'anthropic',
150
+ model: 'claude-sonnet-4-6',
151
+ apiKey: 'sk-existing-key',
152
+ },
153
+ },
154
+ });
155
+ const orgPolicy: IOrgPolicy = { requireApiKeyFromEnv: true };
156
+
157
+ const listed = await createExecutor(adapter, orgPolicy).execute('provider', session, 'list');
158
+ const selected = await listed?.interaction?.submit('anthropic');
159
+ const editRequested = await selected?.interaction?.submit('edit');
160
+ const modelPrompt = await editRequested?.interaction?.submit('$ENV:ORG_TEST_KEY');
161
+ const completed = await modelPrompt?.interaction?.submit('claude-opus-4-5');
162
+
163
+ expect(completed?.success).toBe(true);
164
+ });
165
+ });
@@ -103,7 +103,7 @@ describe('createProviderCommandModule', () => {
103
103
 
104
104
  expect(module.name).toBe('agent-command-provider');
105
105
  expect(commands.map((command) => command.name)).toEqual(['provider']);
106
- expect(commands[0]?.subcommands?.map((command) => command.name)).toContain('use');
106
+ expect(commands[0]?.subcommands?.map((command) => command.name)).toContain('switch');
107
107
  expect(module.systemCommands?.map((command) => command.name)).toEqual(['provider']);
108
108
  });
109
109
 
@@ -146,7 +146,7 @@ describe('createProviderCommandModule', () => {
146
146
  });
147
147
  });
148
148
 
149
- it('confirms provider switch through a generic command interaction', async () => {
149
+ it('switches provider immediately via /provider switch without confirmation dialog', async () => {
150
150
  const { adapter, readTarget } = createSettingsAdapter(
151
151
  {
152
152
  currentProvider: 'anthropic',
@@ -158,24 +158,19 @@ describe('createProviderCommandModule', () => {
158
158
  {},
159
159
  );
160
160
 
161
- const result = await createExecutor(adapter).execute('provider', session, 'use openai');
162
- const submitted = await result?.interaction?.submit('yes');
161
+ const result = await createExecutor(adapter).execute('provider', session, 'switch openai');
163
162
 
164
- expect(result?.interaction?.prompt).toMatchObject({
165
- kind: 'choice',
166
- title: 'Change provider to openai? This will restart the session.',
167
- });
163
+ expect(result?.interaction).toBeUndefined();
164
+ expect(result?.message).toBe(
165
+ 'Switched to openai (supergemma4-26b-uncensored-v2). History preserved.',
166
+ );
168
167
  expect(readTarget().currentProvider).toBe('openai');
169
- expect(submitted?.effects).toEqual([
170
- {
171
- type: 'session-restart-requested',
172
- reason: 'other',
173
- message: 'Provider change restart',
174
- },
168
+ expect(result?.effects).toEqual([
169
+ { type: 'provider-hot-swap-requested', profileName: 'openai' },
175
170
  ]);
176
171
  });
177
172
 
178
- it('switches from the provider profile action menu through the same restart interaction', async () => {
173
+ it('switches from the provider profile action menu immediately without confirmation', async () => {
179
174
  const { adapter, readTarget } = createSettingsAdapter(
180
175
  {
181
176
  currentProvider: 'anthropic',
@@ -190,16 +185,14 @@ describe('createProviderCommandModule', () => {
190
185
  const listed = await createExecutor(adapter).execute('provider', session, 'list');
191
186
  const selected = await listed?.interaction?.submit('openai');
192
187
  const switchRequested = await selected?.interaction?.submit('switch');
193
- const submitted = await switchRequested?.interaction?.submit('yes');
194
188
 
195
- expect(switchRequested?.message).toBe('Provider change requested: openai');
189
+ expect(switchRequested?.message).toBe(
190
+ 'Switched to openai (supergemma4-26b-uncensored-v2). History preserved.',
191
+ );
192
+ expect(switchRequested?.interaction).toBeUndefined();
196
193
  expect(readTarget().currentProvider).toBe('openai');
197
- expect(submitted?.effects).toEqual([
198
- {
199
- type: 'session-restart-requested',
200
- reason: 'other',
201
- message: 'Provider change restart',
202
- },
194
+ expect(switchRequested?.effects).toEqual([
195
+ { type: 'provider-hot-swap-requested', profileName: 'openai' },
203
196
  ]);
204
197
  });
205
198
 
@@ -241,13 +234,9 @@ describe('createProviderCommandModule', () => {
241
234
  },
242
235
  },
243
236
  });
244
- expect(completed?.message).toBe('Provider anthropic updated. Restarting...');
237
+ expect(completed?.message).toBe('Provider anthropic updated. Switching...');
245
238
  expect(completed?.effects).toEqual([
246
- {
247
- type: 'session-restart-requested',
248
- reason: 'other',
249
- message: 'Provider edit restart',
250
- },
239
+ { type: 'provider-hot-swap-requested', profileName: 'anthropic' },
251
240
  ]);
252
241
  });
253
242
 
@@ -82,8 +82,8 @@ describe('provider setup flow', () => {
82
82
  expect(formatProviderSetupSelectionPrompt(providerDefinitions)).toBe(
83
83
  [
84
84
  ' Select provider:',
85
- ' 1. OpenAI (openai) - Use OpenAI Responses API',
86
- ' 2. Anthropic (anthropic) - Use Claude models through Anthropic',
85
+ ' 1. OpenAI (openai) — Use OpenAI Responses API',
86
+ ' 2. Anthropic (anthropic) — Use Claude models through Anthropic',
87
87
  ' Provider [1-2] (default: 1): ',
88
88
  ].join('\n'),
89
89
  );
@@ -33,7 +33,7 @@ export async function executeProviderCommand(
33
33
  success: true,
34
34
  };
35
35
  }
36
- if (subcommand === 'use') {
36
+ if (subcommand === 'switch') {
37
37
  return buildProviderSwitch(settings.providers, profileArg, options);
38
38
  }
39
39
  if (subcommand === 'test') {
@@ -49,7 +49,7 @@ export async function executeProviderCommand(
49
49
  }
50
50
 
51
51
  return {
52
- message: 'Usage: provider [current|list|use <profile>|add <type>|test [profile]]',
52
+ message: 'Usage: provider [current|list|switch <profile>|add <type>|test [profile]]',
53
53
  success: false,
54
54
  };
55
55
  }
@@ -14,7 +14,7 @@ function buildProviderSubcommands(): ICommand[] {
14
14
  return [
15
15
  { name: 'current', description: 'Show current provider', source: 'provider' },
16
16
  { name: 'list', description: 'List provider profiles', source: 'provider' },
17
- { name: 'use', description: 'Switch provider profile', source: 'provider' },
17
+ { name: 'switch', description: 'Hot-swap to another provider profile', source: 'provider' },
18
18
  { name: 'add', description: 'Configure a provider profile', source: 'provider' },
19
19
  { name: 'test', description: 'Test provider profile', source: 'provider' },
20
20
  ];
@@ -27,8 +27,9 @@ export function createProviderCommandEntry(): ICommand {
27
27
  description: 'Manage provider profiles',
28
28
  source: 'provider',
29
29
  modelInvocable: false,
30
- argumentHint: 'current | list | use <profile> | add [type] | test [profile]',
30
+ argumentHint: 'current | list | switch <profile> | add [type] | test [profile]',
31
31
  subcommands: buildProviderSubcommands(),
32
+ example: '/provider switch production',
32
33
  };
33
34
  }
34
35
 
@@ -46,6 +47,7 @@ function createProviderSystemCommand(options: IProviderCommandModuleOptions): TS
46
47
  name: entry.name,
47
48
  displayName: entry.displayName,
48
49
  description: entry.description,
50
+ example: entry.example,
49
51
  requiresPermission: false,
50
52
  userInvocable: true,
51
53
  modelInvocable: false,