@robota-sdk/agent-command 3.0.0-beta.79 → 3.0.0-beta.82
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.
- package/README.md +11 -2
- package/dist/node/index.cjs +77 -38
- package/dist/node/index.d.cts +946 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +564 -51
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +77 -38
- package/dist/node/index.js.map +1 -1
- package/package.json +39 -16
- package/src/advisor/advisor-command-module.ts +47 -0
- package/src/advisor/advisor-command.test.ts +102 -0
- package/src/advisor/advisor-command.ts +55 -0
- package/src/advisor/advisor-cost-functional.test.ts +92 -0
- package/src/advisor/index.ts +6 -0
- package/src/agent/__tests__/agent-command.test.ts +39 -8
- package/src/agent/agent-command-module.ts +14 -7
- package/src/agent/agent-command-parser.ts +1 -1
- package/src/agent/agent-command.ts +3 -3
- package/src/background/__tests__/background-command-module.test.ts +59 -52
- package/src/background/background-command-module.ts +2 -1
- package/src/background/background-command.ts +3 -3
- package/src/command-module-utils.ts +33 -0
- package/src/compact/__tests__/compact-command-module.test.ts +31 -23
- package/src/compact/compact-command-module.ts +10 -1
- package/src/compact/compact-command.ts +6 -3
- package/src/context/__tests__/context-command-module.test.ts +214 -63
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +45 -14
- package/src/context/context-command.ts +34 -257
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +110 -0
- package/src/default/__tests__/default-command-modules.test.ts +59 -7
- package/src/default/__tests__/model-exposure.test.ts +241 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +130 -18
- package/src/default/index.ts +4 -1
- package/src/devices/__tests__/devices-command-module.test.ts +212 -0
- package/src/devices/devices-command-module.ts +224 -0
- package/src/devices/devices-command-port.ts +82 -0
- package/src/devices/index.ts +11 -0
- package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
- package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
- package/src/doctor/__tests__/doctor-fixture.ts +145 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +464 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +299 -0
- package/src/doctor/doctor-node-deps.ts +89 -0
- package/src/doctor/doctor-redaction.ts +99 -0
- package/src/doctor/doctor-render.ts +70 -0
- package/src/doctor/doctor-repair.ts +136 -0
- package/src/doctor/doctor-runner.ts +122 -0
- package/src/doctor/doctor-settings-probe.ts +239 -0
- package/src/doctor/doctor-storage-probe.ts +187 -0
- package/src/doctor/doctor-types.ts +109 -0
- package/src/doctor/index.ts +32 -0
- package/src/editor/__tests__/editor-command-functional.test.ts +49 -16
- package/src/editor/editor-command-module.ts +6 -5
- package/src/editor/editor-command.ts +10 -5
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +140 -0
- package/src/effort/effort-command.ts +122 -0
- package/src/effort/index.ts +6 -0
- package/src/exit/__tests__/exit-command-module.test.ts +16 -9
- package/src/exit/exit-command-module.ts +2 -1
- package/src/exit/exit-command.ts +6 -6
- package/src/fork/__tests__/fork-command.test.ts +184 -0
- package/src/fork/fork-command-module.ts +61 -0
- package/src/fork/fork-command.ts +162 -0
- package/src/fork/index.ts +8 -0
- package/src/git/__tests__/fake-git-port.ts +28 -0
- package/src/git/__tests__/git-command-module.test.ts +103 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/__tests__/git-diff.test.ts +125 -0
- package/src/git/__tests__/git-process.test.ts +221 -0
- package/src/git/__tests__/git-status.test.ts +117 -0
- package/src/git/git-command-module.ts +139 -0
- package/src/git/git-commit.ts +251 -0
- package/src/git/git-diff.ts +129 -0
- package/src/git/git-process.ts +169 -0
- package/src/git/git-status.ts +134 -0
- package/src/git/index.ts +17 -0
- package/src/goal/__tests__/goal-command.test.ts +9 -4
- package/src/goal/goal-command-module.ts +2 -1
- package/src/goal/goal-command.ts +6 -9
- package/src/handoff/__tests__/handoff-command-module.test.ts +24 -0
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +49 -0
- package/src/handoff/handoff-command.ts +187 -0
- package/src/handoff/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +63 -40
- package/src/help/__tests__/help-command.test.ts +46 -37
- package/src/help/help-command-module.ts +2 -1
- package/src/help/help-command.ts +3 -3
- package/src/index.ts +124 -2
- package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
- package/src/keybindings/index.ts +6 -0
- package/src/keybindings/keybindings-command-module.ts +74 -0
- package/src/language/__tests__/language-command-module.test.ts +33 -28
- package/src/language/language-command-module.ts +2 -1
- package/src/language/language-command.ts +8 -6
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +630 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +211 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +92 -0
- package/src/mcp-activation/mcp-activation-command.ts +448 -0
- package/src/mcp-activation/mcp-model-notice.ts +85 -0
- package/src/mcp-activation/mcp-model-status.ts +99 -0
- package/src/memory/__tests__/memory-command-module.test.ts +167 -74
- package/src/memory/memory-command-module.ts +18 -1
- package/src/memory/memory-command.ts +38 -38
- package/src/mode/__tests__/mode-command-module.test.ts +51 -38
- package/src/mode/mode-command-module.ts +2 -1
- package/src/mode/mode-command.ts +15 -7
- package/src/output-style/__tests__/output-style-command.test.ts +96 -0
- package/src/output-style/index.ts +6 -0
- package/src/output-style/output-style-command-module.ts +143 -0
- package/src/peers/__tests__/peers-command.test.ts +321 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +49 -0
- package/src/peers/peers-command.ts +191 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +149 -37
- package/src/permissions/permissions-command-module.ts +2 -1
- package/src/permissions/permissions-command.ts +46 -4
- package/src/plan/__tests__/plan-command.test.ts +129 -0
- package/src/plan/index.ts +9 -0
- package/src/plan/plan-command-module.ts +49 -0
- package/src/plan/plan-command.ts +92 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +46 -40
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/plugin-command-module.ts +3 -1
- package/src/plugin/plugin-command.ts +17 -16
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +102 -56
- package/src/preset/preset-command-module.ts +26 -8
- package/src/preset/preset-command.ts +85 -27
- package/src/provider/__tests__/org-policy.test.ts +4 -5
- package/src/provider/__tests__/provider-command-module.test.ts +11 -14
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +7 -5
- package/src/provider/provider-command-execution.ts +4 -4
- package/src/provider/provider-command-module.ts +2 -1
- package/src/provider/provider-command-profile-lifecycle.ts +4 -4
- package/src/provider/provider-command-profile-operations.ts +3 -3
- package/src/provider/provider-command-profile.ts +1 -1
- package/src/provider/provider-command-setup.ts +4 -4
- package/src/provider/provider-startup.ts +62 -35
- package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
- package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
- package/src/remote-control/index.ts +8 -0
- package/src/remote-control/remote-control-command-module.ts +61 -0
- package/src/remote-control/remote-control-command.ts +138 -0
- package/src/reset/__tests__/reset-command-module.test.ts +5 -5
- package/src/reset/reset-command-module.ts +2 -1
- package/src/reset/reset-command.ts +3 -4
- package/src/rewind/__tests__/rewind-command-module.test.ts +40 -0
- package/src/rewind/rewind-command-module.ts +2 -1
- package/src/rewind/rewind-command.ts +73 -6
- package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/sandbox-command-module.ts +56 -0
- package/src/sandbox/sandbox-command.ts +94 -0
- package/src/schedule/__tests__/loop-command-functional.test.ts +386 -0
- package/src/schedule/__tests__/loop-command.test.ts +360 -0
- package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
- package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
- package/src/schedule/__tests__/schedule-command.test.ts +143 -3
- package/src/schedule/__tests__/schedule-redos.test.ts +81 -0
- package/src/schedule/index.ts +3 -0
- package/src/schedule/loop-command.ts +333 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +92 -14
- package/src/schedule/schedule-command.ts +98 -9
- package/src/schedule/schedule-spec-parser.ts +5 -1
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +210 -45
- package/src/session/session-command-module.ts +68 -37
- package/src/session/session-command.ts +147 -80
- package/src/settings/settings-command-module.ts +3 -2
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +23 -1
- package/src/shell/resolve-shell.ts +2 -2
- package/src/shell/shell-command-module.ts +6 -5
- package/src/shell/shell-command.ts +9 -5
- package/src/skills/__tests__/skills-command-module.test.ts +69 -28
- package/src/skills/index.ts +1 -0
- package/src/skills/skills-command-module.ts +22 -5
- package/src/skills/skills-command.ts +14 -13
- package/src/statusline/__tests__/statusline-command-module.test.ts +5 -4
- package/src/statusline/statusline-command-module.ts +2 -1
- package/src/statusline/statusline-command.ts +4 -4
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +211 -0
- package/src/user-local/__tests__/user-local-command.test.ts +145 -110
- package/src/user-local/user-local-command-constants.ts +1 -1
- package/src/user-local/user-local-command-module.ts +6 -5
- package/src/user-local/user-local-command.ts +12 -6
- package/src/user-local/user-local-memory-command.ts +17 -7
- package/src/plugins/default-plugin-command-adapter.ts +0 -164
- package/src/plugins/default-plugin-command-source-loader.ts +0 -31
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from '@robota-sdk/agent-framework';
|
|
6
6
|
import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
7
7
|
import { createModeCommandModule } from '../mode-command-module.js';
|
|
8
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
8
9
|
|
|
9
10
|
type TPermissionModeName = 'plan' | 'default' | 'acceptEdits' | 'bypassPermissions';
|
|
10
11
|
type TSetPermissionModeSpy = ReturnType<typeof vi.fn<(nextMode: TPermissionModeName) => void>>;
|
|
@@ -25,7 +26,7 @@ function createCheckpointResult(): IEditCheckpointRestoreResult {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
function createCommandHostContext():
|
|
29
|
+
function createCommandHostContext(): ReturnType<typeof createTestCommandHost> & {
|
|
29
30
|
setPermissionMode: TSetPermissionModeSpy;
|
|
30
31
|
} {
|
|
31
32
|
let mode: TPermissionModeName = 'default';
|
|
@@ -34,34 +35,41 @@ function createCommandHostContext(): ICommandHostContext & {
|
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
...createTestCommandHost({
|
|
39
|
+
overrides: {
|
|
40
|
+
getSession: () => {
|
|
41
|
+
throw new Error('mode command should use the permission mode adapter');
|
|
42
|
+
},
|
|
43
|
+
getCommandHostAdapters: () => ({
|
|
44
|
+
permissionMode: {
|
|
45
|
+
getPermissionMode: () => mode,
|
|
46
|
+
setPermissionMode,
|
|
47
|
+
listSessionAllowedTools: () => [],
|
|
48
|
+
getPermissionRules: () => ({ allow: [], deny: [], ask: [] }),
|
|
49
|
+
listRecentDenials: () => [],
|
|
50
|
+
retryDenial: () => undefined,
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
getContextState: () => ({
|
|
54
|
+
usedTokens: 0,
|
|
55
|
+
maxTokens: 1,
|
|
56
|
+
usedPercentage: 0,
|
|
57
|
+
remainingPercentage: 100,
|
|
58
|
+
}),
|
|
59
|
+
getAutoCompactThreshold: () => 0.835,
|
|
60
|
+
compactContext: async () => undefined,
|
|
61
|
+
getCwd: () => '/workspace',
|
|
62
|
+
listEditCheckpoints: () => [],
|
|
63
|
+
restoreEditCheckpoint: async () => createCheckpointResult(),
|
|
64
|
+
rollbackEditCheckpoint: async () => createCheckpointResult(),
|
|
65
|
+
getUsedMemoryReferences: () => [],
|
|
66
|
+
recordMemoryEvent: () => undefined,
|
|
67
|
+
listBackgroundTasks: () => [],
|
|
68
|
+
readBackgroundTaskLog: async () => ({ taskId: 'task_1', lines: [] }),
|
|
69
|
+
cancelBackgroundTask: async () => undefined,
|
|
70
|
+
closeBackgroundTask: async () => undefined,
|
|
45
71
|
},
|
|
46
72
|
}),
|
|
47
|
-
getContextState: () => ({
|
|
48
|
-
usedTokens: 0,
|
|
49
|
-
maxTokens: 1,
|
|
50
|
-
usedPercentage: 0,
|
|
51
|
-
remainingPercentage: 100,
|
|
52
|
-
}),
|
|
53
|
-
getAutoCompactThreshold: () => 0.835,
|
|
54
|
-
compactContext: async () => undefined,
|
|
55
|
-
getCwd: () => '/workspace',
|
|
56
|
-
listEditCheckpoints: () => [],
|
|
57
|
-
restoreEditCheckpoint: async () => createCheckpointResult(),
|
|
58
|
-
rollbackEditCheckpoint: async () => createCheckpointResult(),
|
|
59
|
-
getUsedMemoryReferences: () => [],
|
|
60
|
-
recordMemoryEvent: () => undefined,
|
|
61
|
-
listBackgroundTasks: () => [],
|
|
62
|
-
readBackgroundTaskLog: async () => ({ taskId: 'task_1', lines: [] }),
|
|
63
|
-
cancelBackgroundTask: async () => undefined,
|
|
64
|
-
closeBackgroundTask: async () => undefined,
|
|
65
73
|
setPermissionMode,
|
|
66
74
|
};
|
|
67
75
|
}
|
|
@@ -77,7 +85,7 @@ describe('createModeCommandModule', () => {
|
|
|
77
85
|
expect.objectContaining({
|
|
78
86
|
name: 'mode',
|
|
79
87
|
description: 'Show/change permission mode',
|
|
80
|
-
argumentHint: 'plan | default | acceptEdits | bypassPermissions',
|
|
88
|
+
argumentHint: 'plan | default | acceptEdits | bypassPermissions | auto',
|
|
81
89
|
source: 'mode',
|
|
82
90
|
modelInvocable: false,
|
|
83
91
|
}),
|
|
@@ -87,12 +95,13 @@ describe('createModeCommandModule', () => {
|
|
|
87
95
|
'default',
|
|
88
96
|
'acceptEdits',
|
|
89
97
|
'bypassPermissions',
|
|
98
|
+
'auto',
|
|
90
99
|
]);
|
|
91
100
|
expect(command).toEqual(
|
|
92
101
|
expect.objectContaining({
|
|
93
102
|
name: 'mode',
|
|
94
103
|
description: 'Show/change permission mode',
|
|
95
|
-
argumentHint: 'plan | default | acceptEdits | bypassPermissions',
|
|
104
|
+
argumentHint: 'plan | default | acceptEdits | bypassPermissions | auto',
|
|
96
105
|
lifecycle: 'inline',
|
|
97
106
|
modelInvocable: false,
|
|
98
107
|
}),
|
|
@@ -136,7 +145,7 @@ describe('createModeCommandModule', () => {
|
|
|
136
145
|
|
|
137
146
|
expect(result?.success).toBe(false);
|
|
138
147
|
expect(result?.message).toBe(
|
|
139
|
-
'Invalid mode. Valid: plan | default | acceptEdits | bypassPermissions',
|
|
148
|
+
'Invalid mode. Valid: plan | default | acceptEdits | bypassPermissions | auto',
|
|
140
149
|
);
|
|
141
150
|
expect(context.setPermissionMode).not.toHaveBeenCalled();
|
|
142
151
|
});
|
|
@@ -146,10 +155,12 @@ describe('createModeCommandModule', () => {
|
|
|
146
155
|
...(createModeCommandModule().systemCommands ?? []),
|
|
147
156
|
]);
|
|
148
157
|
const context = createCommandHostContext();
|
|
149
|
-
const contextWithAsk
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
const contextWithAsk = createTestCommandHost({
|
|
159
|
+
overrides: {
|
|
160
|
+
...context,
|
|
161
|
+
getUserInteraction: () => ({ ask: async () => ({ type: 'answer', values: ['plan'] }) }),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
153
164
|
|
|
154
165
|
const result = await executor.execute('mode', contextWithAsk, '');
|
|
155
166
|
|
|
@@ -163,10 +174,12 @@ describe('createModeCommandModule', () => {
|
|
|
163
174
|
...(createModeCommandModule().systemCommands ?? []),
|
|
164
175
|
]);
|
|
165
176
|
const context = createCommandHostContext();
|
|
166
|
-
const contextWithAsk
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
const contextWithAsk = createTestCommandHost({
|
|
178
|
+
overrides: {
|
|
179
|
+
...context,
|
|
180
|
+
getUserInteraction: () => ({ ask: async () => ({ type: 'cancelled' }) }),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
170
183
|
|
|
171
184
|
const result = await executor.execute('mode', contextWithAsk, '');
|
|
172
185
|
|
|
@@ -7,7 +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 { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
10
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
11
11
|
|
|
12
12
|
export function createModeCommandEntry(): ICommand {
|
|
13
13
|
return {
|
|
@@ -17,6 +17,7 @@ export function createModeCommandEntry(): ICommand {
|
|
|
17
17
|
source: 'mode',
|
|
18
18
|
argumentHint: PERMISSION_MODE_ARGUMENT_HINT,
|
|
19
19
|
subcommands: buildPermissionModeSubcommands('mode'),
|
|
20
|
+
// User-only: changes the permission mode; permission widening is the user's.
|
|
20
21
|
modelInvocable: false,
|
|
21
22
|
};
|
|
22
23
|
}
|
package/src/mode/mode-command.ts
CHANGED
|
@@ -5,19 +5,26 @@ import {
|
|
|
5
5
|
isPermissionMode,
|
|
6
6
|
parsePermissionModeArgument,
|
|
7
7
|
readCommandPermissionMode,
|
|
8
|
-
writeCommandPermissionMode,
|
|
9
8
|
} from '@robota-sdk/agent-framework';
|
|
10
9
|
|
|
11
|
-
import
|
|
12
|
-
|
|
10
|
+
import { tryWritePermissionMode } from '../permissions/permissions-command.js';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ICommandHostAdapterAccess,
|
|
14
|
+
ICommandHostSessionAccess,
|
|
15
|
+
ICommandHostUserInteraction,
|
|
16
|
+
} from '@robota-sdk/agent-framework';
|
|
17
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Ask the user to pick a permission mode (CMD-004 inline ask). Returns the chosen mode name, or
|
|
16
21
|
* `undefined` when no interactive renderer is attached or the user cancelled — the caller then reports
|
|
17
22
|
* the current mode instead of changing it (never a silent guess).
|
|
18
23
|
*/
|
|
19
|
-
async function resolveModeViaAsk(
|
|
20
|
-
|
|
24
|
+
async function resolveModeViaAsk(
|
|
25
|
+
context: ICommandHostUserInteraction,
|
|
26
|
+
): Promise<string | undefined> {
|
|
27
|
+
const ui = context.getUserInteraction();
|
|
21
28
|
if (!ui) return undefined;
|
|
22
29
|
const options = buildPermissionModeSubcommands().map((sub) => ({
|
|
23
30
|
value: sub.name,
|
|
@@ -29,7 +36,7 @@ async function resolveModeViaAsk(context: ICommandHostContext): Promise<string |
|
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export async function executeModeCommand(
|
|
32
|
-
context:
|
|
39
|
+
context: ICommandHostAdapterAccess & ICommandHostSessionAccess & ICommandHostUserInteraction,
|
|
33
40
|
args: string,
|
|
34
41
|
): Promise<ICommandResult> {
|
|
35
42
|
let arg: string | undefined = parsePermissionModeArgument(args);
|
|
@@ -50,7 +57,8 @@ export async function executeModeCommand(
|
|
|
50
57
|
};
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
const refused = tryWritePermissionMode(context, arg);
|
|
61
|
+
if (refused !== undefined) return refused;
|
|
54
62
|
return {
|
|
55
63
|
message: `Permission mode set to: ${arg}`,
|
|
56
64
|
success: true,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createOutputStyleCommandEntry,
|
|
7
|
+
executeOutputStyleCommand,
|
|
8
|
+
} from '../output-style-command-module.js';
|
|
9
|
+
|
|
10
|
+
const styles = [
|
|
11
|
+
{
|
|
12
|
+
id: 'default',
|
|
13
|
+
name: 'Default',
|
|
14
|
+
description: 'Balanced responses.',
|
|
15
|
+
tokenCost: 'baseline',
|
|
16
|
+
source: 'built-in',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'concise',
|
|
20
|
+
name: 'Concise',
|
|
21
|
+
description: 'Lead with the answer.',
|
|
22
|
+
tokenCost: 'low',
|
|
23
|
+
source: 'built-in',
|
|
24
|
+
},
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
function context() {
|
|
28
|
+
const ask = vi.fn().mockResolvedValue({ type: 'answer', values: ['concise'] });
|
|
29
|
+
const host = createTestCommandHost({
|
|
30
|
+
overrides: {
|
|
31
|
+
getActiveOutputStyleId: () => 'default',
|
|
32
|
+
getUserInteraction: () => ({ ask }),
|
|
33
|
+
getCommandHostAdapters: () => ({
|
|
34
|
+
outputStyleRegistry: {
|
|
35
|
+
listOutputStyles: () => styles,
|
|
36
|
+
getOutputStyle: (id) =>
|
|
37
|
+
id === 'concise'
|
|
38
|
+
? {
|
|
39
|
+
id: 'concise',
|
|
40
|
+
name: 'Concise',
|
|
41
|
+
instructions: 'Lead with the answer.',
|
|
42
|
+
keepCodingInstructions: true,
|
|
43
|
+
tokenCost: 'low',
|
|
44
|
+
}
|
|
45
|
+
: undefined,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
return { host, ask };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('/output-style', () => {
|
|
54
|
+
it('lists styles with the active marker and token-cost metadata', async () => {
|
|
55
|
+
const { host } = context();
|
|
56
|
+
const result = await executeOutputStyleCommand(host, 'list');
|
|
57
|
+
|
|
58
|
+
expect(result.success).toBe(true);
|
|
59
|
+
expect(result.message).toContain(
|
|
60
|
+
'* default — Default: Balanced responses. [input cost: baseline]',
|
|
61
|
+
);
|
|
62
|
+
expect(result.message).toContain(
|
|
63
|
+
' concise — Concise: Lead with the answer. [input cost: low]',
|
|
64
|
+
);
|
|
65
|
+
expect(result.data).toEqual({ outputStyles: styles, active: 'default' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('uses the inline picker when no id is supplied and emits only a style id action', async () => {
|
|
69
|
+
const { host, ask } = context();
|
|
70
|
+
const result = await executeOutputStyleCommand(host, '');
|
|
71
|
+
|
|
72
|
+
expect(ask).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect(result).toMatchObject({
|
|
74
|
+
success: true,
|
|
75
|
+
hostActions: [{ type: 'output-style-change', styleId: 'concise' }],
|
|
76
|
+
data: { outputStyle: 'concise' },
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rejects unknown ids and never emits an action', async () => {
|
|
81
|
+
const { host } = context();
|
|
82
|
+
const result = await executeOutputStyleCommand(host, 'missing');
|
|
83
|
+
|
|
84
|
+
expect(result.success).toBe(false);
|
|
85
|
+
expect(result.message).toContain('Unknown output style');
|
|
86
|
+
expect(result.hostActions).toBeUndefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('is operator-only', () => {
|
|
90
|
+
expect(createOutputStyleCommandEntry()).toMatchObject({
|
|
91
|
+
name: 'output-style',
|
|
92
|
+
modelInvocable: false,
|
|
93
|
+
userInvocable: true,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { selectAction } from '@robota-sdk/agent-core';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ICommandHostAdapterAccess,
|
|
5
|
+
ICommandHostSessionAccess,
|
|
6
|
+
ICommandHostUserInteraction,
|
|
7
|
+
ICommandModule,
|
|
8
|
+
ISystemCommand,
|
|
9
|
+
ICommandOutputStyleRegistryAdapter,
|
|
10
|
+
} from '@robota-sdk/agent-framework';
|
|
11
|
+
import type { ICommand, ICommandResult, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
12
|
+
|
|
13
|
+
const OUTPUT_STYLE_DESCRIPTION = 'List or switch the active provider-neutral response style';
|
|
14
|
+
const OUTPUT_STYLE_ARGUMENT_HINT = 'list | <style-id>';
|
|
15
|
+
|
|
16
|
+
function getRegistry(
|
|
17
|
+
context: ICommandHostAdapterAccess,
|
|
18
|
+
): ICommandOutputStyleRegistryAdapter | undefined {
|
|
19
|
+
return context.getCommandHostAdapters?.().outputStyleRegistry;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatStyleList(active: string, registry: ICommandOutputStyleRegistryAdapter): string {
|
|
23
|
+
const lines = registry.listOutputStyles().map((style) => {
|
|
24
|
+
const marker = style.id === active ? '* ' : ' ';
|
|
25
|
+
const cost = style.tokenCost ? ` [input cost: ${style.tokenCost}]` : '';
|
|
26
|
+
return `${marker}${style.id} — ${style.name}: ${style.description}${cost}`;
|
|
27
|
+
});
|
|
28
|
+
return ['Available output styles:', ...lines].join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function listResult(
|
|
32
|
+
context: ICommandHostSessionAccess,
|
|
33
|
+
registry: ICommandOutputStyleRegistryAdapter,
|
|
34
|
+
): ICommandResult {
|
|
35
|
+
const active = context.getActiveOutputStyleId();
|
|
36
|
+
const outputStyles = registry.listOutputStyles();
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
message: formatStyleList(active, registry),
|
|
40
|
+
data: { outputStyles, active },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function chooseStyle(
|
|
45
|
+
context: ICommandHostUserInteraction,
|
|
46
|
+
registry: ICommandOutputStyleRegistryAdapter,
|
|
47
|
+
): Promise<string | undefined> {
|
|
48
|
+
const interaction = context.getUserInteraction();
|
|
49
|
+
if (!interaction) return undefined;
|
|
50
|
+
const options = registry.listOutputStyles().map((style) => ({
|
|
51
|
+
value: style.id,
|
|
52
|
+
label: style.id,
|
|
53
|
+
description: style.description,
|
|
54
|
+
}));
|
|
55
|
+
const response = await interaction.ask(
|
|
56
|
+
selectAction('output-style', 'Select an output style', options),
|
|
57
|
+
);
|
|
58
|
+
return response.type === 'answer' ? response.values[0] : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function executeOutputStyleCommand(
|
|
62
|
+
context: ICommandHostAdapterAccess & ICommandHostSessionAccess & ICommandHostUserInteraction,
|
|
63
|
+
args: string,
|
|
64
|
+
): Promise<ICommandResult> {
|
|
65
|
+
const registry = getRegistry(context);
|
|
66
|
+
if (!registry) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
message: 'Output styles are not available in this environment.',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let styleId = args.trim();
|
|
74
|
+
if (styleId === 'list') return listResult(context, registry);
|
|
75
|
+
if (!styleId) {
|
|
76
|
+
styleId = (await chooseStyle(context, registry)) ?? '';
|
|
77
|
+
if (!styleId) return listResult(context, registry);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const style = registry.getOutputStyle(styleId);
|
|
81
|
+
if (!style) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
message: `Unknown output style "${styleId}". Available: ${
|
|
85
|
+
registry
|
|
86
|
+
.listOutputStyles()
|
|
87
|
+
.map((entry) => entry.id)
|
|
88
|
+
.join(', ') || '(none)'
|
|
89
|
+
}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
message: `Switching output style to ${style.name}...`,
|
|
96
|
+
data: { outputStyle: style.id },
|
|
97
|
+
hostActions: [{ type: 'output-style-change', styleId: style.id }],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createOutputStyleCommandEntry(): ICommand {
|
|
102
|
+
return {
|
|
103
|
+
name: 'output-style',
|
|
104
|
+
displayName: 'Output Style',
|
|
105
|
+
description: OUTPUT_STYLE_DESCRIPTION,
|
|
106
|
+
argumentHint: OUTPUT_STYLE_ARGUMENT_HINT,
|
|
107
|
+
source: 'output-style',
|
|
108
|
+
userInvocable: true,
|
|
109
|
+
// User-only: the user's presentation preference.
|
|
110
|
+
modelInvocable: false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createOutputStyleSystemCommand(): ISystemCommand {
|
|
115
|
+
const entry = createOutputStyleCommandEntry();
|
|
116
|
+
return {
|
|
117
|
+
name: entry.name,
|
|
118
|
+
displayName: entry.displayName,
|
|
119
|
+
description: entry.description,
|
|
120
|
+
argumentHint: entry.argumentHint,
|
|
121
|
+
requiresPermission: false,
|
|
122
|
+
userInvocable: true,
|
|
123
|
+
modelInvocable: false,
|
|
124
|
+
lifecycle: 'inline',
|
|
125
|
+
execute: executeOutputStyleCommand,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class OutputStyleCommandSource implements ICommandSource {
|
|
130
|
+
readonly name = 'output-style';
|
|
131
|
+
|
|
132
|
+
getCommands(): ICommand[] {
|
|
133
|
+
return [createOutputStyleCommandEntry()];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createOutputStyleCommandModule(): ICommandModule {
|
|
138
|
+
return {
|
|
139
|
+
name: 'agent-command-output-style',
|
|
140
|
+
commandSources: [new OutputStyleCommandSource()],
|
|
141
|
+
systemCommands: [createOutputStyleSystemCommand()],
|
|
142
|
+
};
|
|
143
|
+
}
|