@robota-sdk/agent-command 3.0.0-beta.75 → 3.0.0-beta.77

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 (55) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +12 -6
  3. package/dist/node/index.cjs +40 -35
  4. package/dist/node/index.d.ts +69 -7
  5. package/dist/node/index.d.ts.map +1 -1
  6. package/dist/node/index.js +40 -35
  7. package/dist/node/index.js.map +1 -1
  8. package/package.json +7 -7
  9. package/src/agent/agent-command-parser.ts +1 -1
  10. package/src/agent/agent-command.ts +2 -1
  11. package/src/background/__tests__/background-command-module.test.ts +2 -5
  12. package/src/context/context-command.ts +4 -4
  13. package/src/default/__tests__/default-command-modules.test.ts +5 -2
  14. package/src/default/default-command-modules.ts +6 -0
  15. package/src/editor/__tests__/editor-command-functional.test.ts +91 -0
  16. package/src/editor/editor-command-module.ts +47 -0
  17. package/src/editor/editor-command.ts +53 -0
  18. package/src/editor/index.ts +7 -0
  19. package/src/editor/resolve-editor.ts +21 -0
  20. package/src/exit/__tests__/exit-command-module.test.ts +21 -2
  21. package/src/exit/exit-command-module.ts +1 -10
  22. package/src/exit/exit-command.ts +15 -1
  23. package/src/goal/__tests__/goal-command.test.ts +75 -0
  24. package/src/goal/goal-command-module.ts +48 -0
  25. package/src/goal/goal-command.ts +70 -0
  26. package/src/goal/index.ts +6 -0
  27. package/src/index.ts +3 -0
  28. package/src/language/__tests__/language-command-module.test.ts +16 -0
  29. package/src/language/language-command-module.ts +1 -18
  30. package/src/language/language-command.ts +35 -9
  31. package/src/mode/__tests__/mode-command-module.test.ts +34 -0
  32. package/src/mode/mode-command-module.ts +1 -18
  33. package/src/mode/mode-command.ts +31 -8
  34. package/src/preset/__tests__/preset-command-module.test.ts +43 -7
  35. package/src/preset/preset-command-module.ts +1 -18
  36. package/src/preset/preset-command.ts +42 -10
  37. package/src/provider/__tests__/org-policy.test.ts +19 -13
  38. package/src/provider/__tests__/provider-command-module.test.ts +151 -80
  39. package/src/provider/__tests__/scripted-interaction.ts +28 -0
  40. package/src/provider/provider-command-execution.ts +67 -50
  41. package/src/provider/provider-command-module.ts +3 -19
  42. package/src/provider/provider-command-profile-lifecycle.ts +52 -72
  43. package/src/provider/provider-command-profile-operations.ts +15 -51
  44. package/src/provider/provider-command-profile.ts +44 -49
  45. package/src/provider/provider-command-setup.ts +56 -51
  46. package/src/session/__tests__/session-command-module.test.ts +38 -0
  47. package/src/session/model-pricing.ts +7 -60
  48. package/src/session/session-command-module.ts +1 -10
  49. package/src/session/session-command.ts +14 -1
  50. package/src/shell/__tests__/shell-command-functional.test.ts +96 -0
  51. package/src/shell/index.ts +8 -0
  52. package/src/shell/resolve-shell.ts +25 -0
  53. package/src/shell/shell-command-module.ts +47 -0
  54. package/src/shell/shell-command.ts +44 -0
  55. package/src/shell/spawn-inherited.ts +32 -0
@@ -0,0 +1,6 @@
1
+ export {
2
+ createGoalCommandModule,
3
+ createGoalCommandEntry,
4
+ GoalCommandSource,
5
+ } from './goal-command-module.js';
6
+ export { executeGoalCommand, GOAL_COMMAND_DESCRIPTION } from './goal-command.js';
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from './agent/index.js';
2
2
  export * from './default/index.js';
3
3
  export * from './background/index.js';
4
+ export * from './goal/index.js';
4
5
  export * from './compact/index.js';
5
6
  export * from './context/index.js';
7
+ export * from './editor/index.js';
6
8
  export * from './exit/index.js';
7
9
  export * from './help/index.js';
8
10
  export * from './language/index.js';
@@ -11,6 +13,7 @@ export * from './mode/index.js';
11
13
  export * from './permissions/index.js';
12
14
  export * from './plugin/index.js';
13
15
  export * from './preset/index.js';
16
+ export * from './shell/index.js';
14
17
  export * from './provider/index.js';
15
18
  export * from './reset/index.js';
16
19
  export * from './rewind/index.js';
@@ -102,4 +102,20 @@ describe('createLanguageCommandModule', () => {
102
102
  expect(result?.success).toBe(false);
103
103
  expect(result?.message).toBe('Usage: language <code> (e.g., ko, en, ja, zh)');
104
104
  });
105
+
106
+ it('asks the user to pick a language when none is provided (CMD-004)', async () => {
107
+ const executor = new SystemCommandExecutor([
108
+ ...(createLanguageCommandModule().systemCommands ?? []),
109
+ ]);
110
+ const contextWithAsk: ICommandHostContext = {
111
+ ...commandHostContext,
112
+ getUserInteraction: () => ({ ask: async () => ({ type: 'answer', values: ['ko'] }) }),
113
+ };
114
+
115
+ const result = await executor.execute('language', contextWithAsk, '');
116
+
117
+ expect(result?.success).toBe(true);
118
+ expect(result?.data?.language).toBe('ko');
119
+ expect(result?.effects).toEqual([{ type: 'language-change-requested', language: 'ko' }]);
120
+ });
105
121
  });
@@ -7,11 +7,7 @@ import {
7
7
  import { executeLanguageCommand } from './language-command.js';
8
8
 
9
9
  import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
10
- import type {
11
- ICommand,
12
- ICommandInteractionHint,
13
- ICommandSource,
14
- } from '@robota-sdk/agent-interface-transport';
10
+ import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-transport';
15
11
 
16
12
  export function createLanguageCommandEntry(): ICommand {
17
13
  return {
@@ -49,23 +45,10 @@ export class LanguageCommandSource implements ICommandSource {
49
45
  }
50
46
  }
51
47
 
52
- const LANGUAGE_INTERACTION_HINTS: Record<string, ICommandInteractionHint> = {
53
- language: {
54
- type: 'pick',
55
- getItems: () =>
56
- buildLanguageCommandSubcommands().map((sub) => ({
57
- label: `${sub.name} ${sub.description ?? ''}`.trimEnd(),
58
- value: sub.name,
59
- description: sub.description,
60
- })),
61
- },
62
- };
63
-
64
48
  export function createLanguageCommandModule(): ICommandModule {
65
49
  return {
66
50
  name: 'agent-command-language',
67
51
  commandSources: [new LanguageCommandSource()],
68
52
  systemCommands: [createLanguageSystemCommand()],
69
- interactionHints: LANGUAGE_INTERACTION_HINTS,
70
53
  };
71
54
  }
@@ -1,18 +1,44 @@
1
- import { formatLanguageUsageMessage, parseLanguageArgument } from '@robota-sdk/agent-framework';
1
+ import { selectAction } from '@robota-sdk/agent-core';
2
+ import {
3
+ buildLanguageCommandSubcommands,
4
+ formatLanguageUsageMessage,
5
+ parseLanguageArgument,
6
+ } from '@robota-sdk/agent-framework';
2
7
 
3
8
  import type { ICommandHostContext } from '@robota-sdk/agent-framework';
4
9
  import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
5
10
 
6
- export function executeLanguageCommand(
7
- _context: ICommandHostContext,
11
+ /**
12
+ * Ask the user to pick a language (CMD-004 inline ask). Returns a validated language, or `undefined`
13
+ * when no interactive renderer is attached or the user cancelled — the caller then shows usage.
14
+ */
15
+ async function resolveLanguageViaAsk(context: ICommandHostContext): Promise<string | undefined> {
16
+ const ui = context.getUserInteraction?.();
17
+ if (!ui) return undefined;
18
+ const options = buildLanguageCommandSubcommands().map((sub) => ({
19
+ value: sub.name,
20
+ label: sub.name,
21
+ description: sub.description,
22
+ }));
23
+ const response = await ui.ask(selectAction('language', 'Select language', options));
24
+ const picked = response.type === 'answer' ? response.values[0] : undefined;
25
+ return picked === undefined ? undefined : parseLanguageArgument(picked);
26
+ }
27
+
28
+ export async function executeLanguageCommand(
29
+ context: ICommandHostContext,
8
30
  args: string,
9
- ): ICommandResult {
10
- const language = parseLanguageArgument(args);
31
+ ): Promise<ICommandResult> {
32
+ let language = parseLanguageArgument(args);
33
+
11
34
  if (language === undefined) {
12
- return {
13
- message: formatLanguageUsageMessage(),
14
- success: false,
15
- };
35
+ language = await resolveLanguageViaAsk(context);
36
+ if (language === undefined) {
37
+ return {
38
+ message: formatLanguageUsageMessage(),
39
+ success: false,
40
+ };
41
+ }
16
42
  }
17
43
 
18
44
  return {
@@ -140,4 +140,38 @@ describe('createModeCommandModule', () => {
140
140
  );
141
141
  expect(context.setPermissionMode).not.toHaveBeenCalled();
142
142
  });
143
+
144
+ it('asks the user to pick a mode when no arg is given and a renderer is attached (CMD-004)', async () => {
145
+ const executor = new SystemCommandExecutor([
146
+ ...(createModeCommandModule().systemCommands ?? []),
147
+ ]);
148
+ const context = createCommandHostContext();
149
+ const contextWithAsk: ICommandHostContext = {
150
+ ...context,
151
+ getUserInteraction: () => ({ ask: async () => ({ type: 'answer', values: ['plan'] }) }),
152
+ };
153
+
154
+ const result = await executor.execute('mode', contextWithAsk, '');
155
+
156
+ expect(result?.success).toBe(true);
157
+ expect(result?.message).toBe('Permission mode set to: plan');
158
+ expect(context.setPermissionMode).toHaveBeenCalledWith('plan');
159
+ });
160
+
161
+ it('reports the current mode when the user cancels the pick (CMD-004)', async () => {
162
+ const executor = new SystemCommandExecutor([
163
+ ...(createModeCommandModule().systemCommands ?? []),
164
+ ]);
165
+ const context = createCommandHostContext();
166
+ const contextWithAsk: ICommandHostContext = {
167
+ ...context,
168
+ getUserInteraction: () => ({ ask: async () => ({ type: 'cancelled' }) }),
169
+ };
170
+
171
+ const result = await executor.execute('mode', contextWithAsk, '');
172
+
173
+ expect(result?.success).toBe(true);
174
+ expect(result?.message).toBe('Current mode: default');
175
+ expect(context.setPermissionMode).not.toHaveBeenCalled();
176
+ });
143
177
  });
@@ -7,11 +7,7 @@ import {
7
7
  import { executeModeCommand } from './mode-command.js';
8
8
 
9
9
  import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
10
- import type {
11
- ICommand,
12
- ICommandInteractionHint,
13
- ICommandSource,
14
- } from '@robota-sdk/agent-interface-transport';
10
+ import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-transport';
15
11
 
16
12
  export function createModeCommandEntry(): ICommand {
17
13
  return {
@@ -49,23 +45,10 @@ export class ModeCommandSource implements ICommandSource {
49
45
  }
50
46
  }
51
47
 
52
- const MODE_INTERACTION_HINTS: Record<string, ICommandInteractionHint> = {
53
- mode: {
54
- type: 'pick',
55
- getItems: () =>
56
- buildPermissionModeSubcommands().map((sub) => ({
57
- label: sub.name,
58
- value: sub.name,
59
- description: sub.description,
60
- })),
61
- },
62
- };
63
-
64
48
  export function createModeCommandModule(): ICommandModule {
65
49
  return {
66
50
  name: 'agent-command-mode',
67
51
  commandSources: [new ModeCommandSource()],
68
52
  systemCommands: [createModeSystemCommand()],
69
- interactionHints: MODE_INTERACTION_HINTS,
70
53
  };
71
54
  }
@@ -1,4 +1,6 @@
1
+ import { selectAction } from '@robota-sdk/agent-core';
1
2
  import {
3
+ buildPermissionModeSubcommands,
2
4
  formatInvalidPermissionModeMessage,
3
5
  isPermissionMode,
4
6
  parsePermissionModeArgument,
@@ -9,15 +11,36 @@ import {
9
11
  import type { ICommandHostContext } from '@robota-sdk/agent-framework';
10
12
  import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
11
13
 
12
- export function executeModeCommand(context: ICommandHostContext, args: string): ICommandResult {
13
- const arg = parsePermissionModeArgument(args);
14
+ /**
15
+ * Ask the user to pick a permission mode (CMD-004 inline ask). Returns the chosen mode name, or
16
+ * `undefined` when no interactive renderer is attached or the user cancelled — the caller then reports
17
+ * the current mode instead of changing it (never a silent guess).
18
+ */
19
+ async function resolveModeViaAsk(context: ICommandHostContext): Promise<string | undefined> {
20
+ const ui = context.getUserInteraction?.();
21
+ if (!ui) return undefined;
22
+ const options = buildPermissionModeSubcommands().map((sub) => ({
23
+ value: sub.name,
24
+ label: sub.name,
25
+ description: sub.description,
26
+ }));
27
+ const response = await ui.ask(selectAction('mode', 'Select interaction mode', options));
28
+ return response.type === 'answer' ? response.values[0] : undefined;
29
+ }
30
+
31
+ export async function executeModeCommand(
32
+ context: ICommandHostContext,
33
+ args: string,
34
+ ): Promise<ICommandResult> {
35
+ let arg: string | undefined = parsePermissionModeArgument(args);
36
+
14
37
  if (arg === undefined) {
15
- const mode = readCommandPermissionMode(context);
16
- return {
17
- message: `Current mode: ${mode}`,
18
- success: true,
19
- data: { mode },
20
- };
38
+ arg = await resolveModeViaAsk(context);
39
+ if (arg === undefined) {
40
+ // No interactive answer — report the current mode without changing it.
41
+ const mode = readCommandPermissionMode(context);
42
+ return { message: `Current mode: ${mode}`, success: true, data: { mode } };
43
+ }
21
44
  }
22
45
 
23
46
  if (!isPermissionMode(arg)) {
@@ -78,16 +78,16 @@ function createCommandHostContext(
78
78
  }
79
79
 
80
80
  describe('preset command module', () => {
81
- it('exposes a single preset system command (structure)', () => {
81
+ it('exposes a single preset system command (structure)', async () => {
82
82
  const module = createPresetCommandModule();
83
83
  expect(module.systemCommands?.map((command) => command.name)).toContain('preset');
84
84
  });
85
85
 
86
- it('TC-01: lists every preset and marks the active one', () => {
86
+ it('TC-01: lists every preset and marks the active one', async () => {
87
87
  const runtime = createSessionRuntime({ getActivePresetId: () => 'autonomous-builder' });
88
88
  const context = createCommandHostContext(runtime);
89
89
 
90
- const result = executePresetCommand(context, '');
90
+ const result = await executePresetCommand(context, '');
91
91
 
92
92
  expect(result.success).toBe(true);
93
93
  for (const preset of listPresets()) {
@@ -99,7 +99,7 @@ describe('preset command module', () => {
99
99
  expect(result.data?.active).toBe('autonomous-builder');
100
100
  });
101
101
 
102
- it('TC-02: switches to a valid preset and drives the live re-apply seams', () => {
102
+ it('TC-02: switches to a valid preset and drives the live re-apply seams', async () => {
103
103
  const setActivePresetId = vi.fn();
104
104
  const setPermissionMode = vi.fn();
105
105
  const applyModelOptions = vi.fn();
@@ -110,7 +110,7 @@ describe('preset command module', () => {
110
110
  });
111
111
  const context = createCommandHostContext(runtime);
112
112
 
113
- const result = executePresetCommand(context, 'careful-reviewer');
113
+ const result = await executePresetCommand(context, 'careful-reviewer');
114
114
 
115
115
  expect(result.success).toBe(true);
116
116
  expect(result.message).toBe('Switched to preset: careful-reviewer');
@@ -122,12 +122,12 @@ describe('preset command module', () => {
122
122
  expect(applyModelOptions).toHaveBeenCalledWith(expect.objectContaining({ effort: 'high' }));
123
123
  });
124
124
 
125
- it('TC-04: rejects an unknown preset id without switching', () => {
125
+ it('TC-04: rejects an unknown preset id without switching', async () => {
126
126
  const setActivePresetId = vi.fn();
127
127
  const runtime = createSessionRuntime({ setActivePresetId });
128
128
  const context = createCommandHostContext(runtime);
129
129
 
130
- const result = executePresetCommand(context, '__nope__');
130
+ const result = await executePresetCommand(context, '__nope__');
131
131
 
132
132
  expect(result.success).toBe(false);
133
133
  for (const preset of listPresets()) {
@@ -135,4 +135,40 @@ describe('preset command module', () => {
135
135
  }
136
136
  expect(setActivePresetId).not.toHaveBeenCalled();
137
137
  });
138
+
139
+ it('CMD-004: asks the user to pick a preset when none is given, then switches', async () => {
140
+ const setActivePresetId = vi.fn();
141
+ const setPermissionMode = vi.fn();
142
+ const applyModelOptions = vi.fn();
143
+ const runtime = createSessionRuntime({
144
+ setActivePresetId,
145
+ setPermissionMode,
146
+ applyModelOptions,
147
+ });
148
+ const context = createCommandHostContext(runtime, {
149
+ getUserInteraction: () => ({
150
+ ask: async () => ({ type: 'answer', values: ['careful-reviewer'] }),
151
+ }),
152
+ });
153
+
154
+ const result = await executePresetCommand(context, '');
155
+
156
+ expect(result.success).toBe(true);
157
+ expect(result.message).toBe('Switched to preset: careful-reviewer');
158
+ expect(setActivePresetId).toHaveBeenCalledWith('careful-reviewer');
159
+ });
160
+
161
+ it('CMD-004: shows the preset list when the pick is cancelled', async () => {
162
+ const setActivePresetId = vi.fn();
163
+ const runtime = createSessionRuntime({ setActivePresetId });
164
+ const context = createCommandHostContext(runtime, {
165
+ getUserInteraction: () => ({ ask: async () => ({ type: 'cancelled' }) }),
166
+ });
167
+
168
+ const result = await executePresetCommand(context, '');
169
+
170
+ expect(result.success).toBe(true);
171
+ expect(result.message).toContain('Available presets:');
172
+ expect(setActivePresetId).not.toHaveBeenCalled();
173
+ });
138
174
  });
@@ -3,11 +3,7 @@ import { listPresets } from '@robota-sdk/agent-preset';
3
3
  import { executePresetCommand } from './preset-command.js';
4
4
 
5
5
  import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
6
- import type {
7
- ICommand,
8
- ICommandInteractionHint,
9
- ICommandSource,
10
- } from '@robota-sdk/agent-interface-transport';
6
+ import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-transport';
11
7
 
12
8
  const PRESET_COMMAND_DESCRIPTION = 'List presets or switch the active preset';
13
9
  const PRESET_ARGUMENT_HINT = 'list | <preset-id>';
@@ -57,23 +53,10 @@ export class PresetCommandSource implements ICommandSource {
57
53
  }
58
54
  }
59
55
 
60
- const PRESET_INTERACTION_HINTS: Record<string, ICommandInteractionHint> = {
61
- preset: {
62
- type: 'pick',
63
- getItems: () =>
64
- buildPresetSubcommands().map((sub) => ({
65
- label: sub.name,
66
- value: sub.name,
67
- description: sub.description,
68
- })),
69
- },
70
- };
71
-
72
56
  export function createPresetCommandModule(): ICommandModule {
73
57
  return {
74
58
  name: 'agent-command-preset',
75
59
  commandSources: [new PresetCommandSource()],
76
60
  systemCommands: [createPresetSystemCommand()],
77
- interactionHints: PRESET_INTERACTION_HINTS,
78
61
  };
79
62
  }
@@ -1,3 +1,4 @@
1
+ import { selectAction } from '@robota-sdk/agent-core';
1
2
  import { applyPresetToSession } from '@robota-sdk/agent-framework';
2
3
  import { getPreset, listPresets, resolvePreset } from '@robota-sdk/agent-preset';
3
4
 
@@ -29,16 +30,47 @@ function formatUnknownPresetMessage(id: string): string {
29
30
  return `Unknown preset: ${id}. Available: ${ids}`;
30
31
  }
31
32
 
32
- export function executePresetCommand(context: ICommandHostContext, args: string): ICommandResult {
33
- const id = args.trim().split(/\s+/)[0];
33
+ /** The `/preset` (or `/preset list`) listing result. */
34
+ function presetListResult(context: ICommandHostContext): ICommandResult {
35
+ const active = readActivePresetId(context);
36
+ return {
37
+ message: formatPresetList(active),
38
+ success: true,
39
+ data: { presets: listPresets(), active },
40
+ };
41
+ }
34
42
 
35
- if (id === undefined || id.length === 0 || id === 'list') {
36
- const active = readActivePresetId(context);
37
- return {
38
- message: formatPresetList(active),
39
- success: true,
40
- data: { presets: listPresets(), active },
41
- };
43
+ /**
44
+ * Ask the user to pick a preset (CMD-004 inline ask). Returns the chosen id, or `undefined` when no
45
+ * interactive renderer is attached or the user cancelled — the caller then shows the preset list.
46
+ */
47
+ async function resolvePresetViaAsk(context: ICommandHostContext): Promise<string | undefined> {
48
+ const ui = context.getUserInteraction?.();
49
+ if (!ui) return undefined;
50
+ const options = listPresets().map((preset) => ({
51
+ value: preset.id,
52
+ label: preset.id,
53
+ description: preset.description,
54
+ }));
55
+ const response = await ui.ask(selectAction('preset', 'Select a preset', options));
56
+ return response.type === 'answer' ? response.values[0] : undefined;
57
+ }
58
+
59
+ export async function executePresetCommand(
60
+ context: ICommandHostContext,
61
+ args: string,
62
+ ): Promise<ICommandResult> {
63
+ let id: string | undefined = args.trim().split(/\s+/)[0];
64
+
65
+ if (id === 'list') {
66
+ return presetListResult(context);
67
+ }
68
+
69
+ if (id === undefined || id.length === 0) {
70
+ id = await resolvePresetViaAsk(context);
71
+ if (id === undefined) {
72
+ return presetListResult(context);
73
+ }
42
74
  }
43
75
 
44
76
  if (getPreset(id) === undefined) {
@@ -49,7 +81,7 @@ export function executePresetCommand(context: ICommandHostContext, args: string)
49
81
  }
50
82
 
51
83
  const resolved = resolvePreset(id);
52
- applyPresetToSession(context, id, resolved);
84
+ await applyPresetToSession(context, id, resolved);
53
85
  return {
54
86
  message: `Switched to preset: ${id}`,
55
87
  success: true,
@@ -8,6 +8,7 @@ import type {
8
8
  TProviderSettingsDocument,
9
9
  } from '@robota-sdk/agent-framework';
10
10
  import { createProviderCommandModule } from '../provider-command-module.js';
11
+ import { scriptedContext } from './scripted-interaction.js';
11
12
 
12
13
  const providerDefinitions: readonly IProviderDefinition[] = [
13
14
  {
@@ -62,7 +63,8 @@ function createExecutor(
62
63
  return new SystemCommandExecutor([...(module.systemCommands ?? [])]);
63
64
  }
64
65
 
65
- const session = {} as ICommandHostContext;
66
+ /** Context with no interactive renderer attached (headless/automation). */
67
+ const headlessContext = {} as ICommandHostContext;
66
68
 
67
69
  describe('org policy enforcement in provider commands', () => {
68
70
  afterEach(() => {
@@ -83,7 +85,7 @@ describe('org policy enforcement in provider commands', () => {
83
85
 
84
86
  const result = await createExecutor(adapter, orgPolicy).execute(
85
87
  'provider',
86
- session,
88
+ headlessContext,
87
89
  'switch openai',
88
90
  );
89
91
 
@@ -105,7 +107,7 @@ describe('org policy enforcement in provider commands', () => {
105
107
 
106
108
  const result = await createExecutor(adapter, orgPolicy).execute(
107
109
  'provider',
108
- session,
110
+ headlessContext,
109
111
  'switch openai',
110
112
  );
111
113
 
@@ -128,12 +130,14 @@ describe('org policy enforcement in provider commands', () => {
128
130
  });
129
131
  const orgPolicy: IOrgPolicy = { requireApiKeyFromEnv: true, adminContact: 'sec@example.com' };
130
132
 
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
133
  // 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');
134
+ const { context } = scriptedContext([
135
+ { type: 'answer', values: ['anthropic'] },
136
+ { type: 'answer', values: ['edit'] },
137
+ { type: 'answer', values: [], text: 'sk-plaintext-key' },
138
+ { type: 'answer', values: [], text: 'claude-sonnet-4-6' },
139
+ ]);
140
+ const completed = await createExecutor(adapter, orgPolicy).execute('provider', context, 'list');
137
141
 
138
142
  expect(completed?.success).toBe(false);
139
143
  expect(completed?.message).toContain('environment variable references');
@@ -154,11 +158,13 @@ describe('org policy enforcement in provider commands', () => {
154
158
  });
155
159
  const orgPolicy: IOrgPolicy = { requireApiKeyFromEnv: true };
156
160
 
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');
161
+ const { context } = scriptedContext([
162
+ { type: 'answer', values: ['anthropic'] },
163
+ { type: 'answer', values: ['edit'] },
164
+ { type: 'answer', values: [], text: '$ENV:ORG_TEST_KEY' },
165
+ { type: 'answer', values: [], text: 'claude-opus-4-5' },
166
+ ]);
167
+ const completed = await createExecutor(adapter, orgPolicy).execute('provider', context, 'list');
162
168
 
163
169
  expect(completed?.success).toBe(true);
164
170
  });