@robota-sdk/agent-command 3.0.0-beta.79 → 3.0.0-beta.81
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 +76 -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 +76 -38
- package/dist/node/index.js.map +1 -1
- package/package.json +34 -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.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +48 -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 +241 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +47 -0
- package/src/peers/peers-command.ts +142 -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
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ICommandHostContext, ICommandSessionRuntime } from '@robota-sdk/agent-framework';
|
|
2
|
-
import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-
|
|
2
|
+
import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-command';
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { createPluginCommandModule } from '../plugin-command-module.js';
|
|
5
5
|
import { executePluginCommand, executeReloadPluginsCommand } from '../plugin-command.js';
|
|
6
|
+
import {
|
|
7
|
+
createTestCommandHost,
|
|
8
|
+
createTestSessionRuntime,
|
|
9
|
+
} from '@robota-sdk/agent-framework/testing';
|
|
6
10
|
|
|
7
11
|
function createPluginAdapter(overrides?: Partial<ICommandPluginAdapter>): ICommandPluginAdapter {
|
|
8
12
|
return {
|
|
@@ -21,8 +25,8 @@ function createPluginAdapter(overrides?: Partial<ICommandPluginAdapter>): IComma
|
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
function createCommandSessionRuntime()
|
|
25
|
-
return {
|
|
28
|
+
function createCommandSessionRuntime() {
|
|
29
|
+
return createTestSessionRuntime({
|
|
26
30
|
clearHistory: () => undefined,
|
|
27
31
|
compact: async () => undefined,
|
|
28
32
|
getContextState: () => ({
|
|
@@ -39,10 +43,10 @@ function createCommandSessionRuntime(): ICommandSessionRuntime {
|
|
|
39
43
|
getAutoCompactThreshold: () => false,
|
|
40
44
|
getFullHistory: () => [],
|
|
41
45
|
getHistory: () => [],
|
|
42
|
-
};
|
|
46
|
+
});
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
function createCommandHostContext(adapter?: ICommandPluginAdapter)
|
|
49
|
+
function createCommandHostContext(adapter?: ICommandPluginAdapter) {
|
|
46
50
|
const checkpoint = {
|
|
47
51
|
id: 'checkpoint_1',
|
|
48
52
|
sessionId: 'session_1',
|
|
@@ -51,39 +55,41 @@ function createCommandHostContext(adapter?: ICommandPluginAdapter): ICommandHost
|
|
|
51
55
|
createdAt: '2026-05-03T00:00:00.000Z',
|
|
52
56
|
fileCount: 0,
|
|
53
57
|
};
|
|
54
|
-
return {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
58
|
+
return createTestCommandHost({
|
|
59
|
+
overrides: {
|
|
60
|
+
getSession: () => createCommandSessionRuntime(),
|
|
61
|
+
getContextState: () => ({
|
|
62
|
+
maxTokens: 100,
|
|
63
|
+
usedTokens: 10,
|
|
64
|
+
usedPercentage: 10,
|
|
65
|
+
remainingPercentage: 90,
|
|
66
|
+
}),
|
|
67
|
+
getAutoCompactThreshold: () => 0.8,
|
|
68
|
+
getCommandHostAdapters: () => (adapter === undefined ? {} : { plugin: adapter }),
|
|
69
|
+
compactContext: async () => undefined,
|
|
70
|
+
getCwd: () => '/workspace',
|
|
71
|
+
listCommands: () => [],
|
|
72
|
+
listEditCheckpoints: () => [],
|
|
73
|
+
restoreEditCheckpoint: async () => ({
|
|
74
|
+
target: checkpoint,
|
|
75
|
+
restoredCheckpointCount: 0,
|
|
76
|
+
restoredFileCount: 0,
|
|
77
|
+
removedCheckpointCount: 0,
|
|
78
|
+
}),
|
|
79
|
+
rollbackEditCheckpoint: async () => ({
|
|
80
|
+
target: checkpoint,
|
|
81
|
+
restoredCheckpointCount: 0,
|
|
82
|
+
restoredFileCount: 0,
|
|
83
|
+
removedCheckpointCount: 0,
|
|
84
|
+
}),
|
|
85
|
+
getUsedMemoryReferences: () => [],
|
|
86
|
+
recordMemoryEvent: () => undefined,
|
|
87
|
+
listBackgroundTasks: () => [],
|
|
88
|
+
readBackgroundTaskLog: async (taskId) => ({ taskId, lines: [] }),
|
|
89
|
+
cancelBackgroundTask: async () => undefined,
|
|
90
|
+
closeBackgroundTask: async () => undefined,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
describe('createPluginCommandModule', () => {
|
|
@@ -119,7 +125,7 @@ describe('executePluginCommand', () => {
|
|
|
119
125
|
await expect(executePluginCommand(createCommandHostContext(), '')).resolves.toEqual({
|
|
120
126
|
success: true,
|
|
121
127
|
message: 'Opening plugin manager...',
|
|
122
|
-
|
|
128
|
+
uiIntents: [{ type: 'show-plugin-manager' }],
|
|
123
129
|
});
|
|
124
130
|
});
|
|
125
131
|
|
|
@@ -193,7 +199,7 @@ describe('executeReloadPluginsCommand', () => {
|
|
|
193
199
|
).resolves.toEqual({
|
|
194
200
|
success: true,
|
|
195
201
|
message: 'Reloaded 3 plugin resources.',
|
|
196
|
-
|
|
202
|
+
data: { pluginRegistryReloaded: true },
|
|
197
203
|
});
|
|
198
204
|
expect(adapter.reloadPlugins).toHaveBeenCalled();
|
|
199
205
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import * as commands from '../../index.js';
|
|
4
|
+
|
|
5
|
+
describe('plugin command host boundary', () => {
|
|
6
|
+
it('keeps the concrete plugin host adapter out of the command package entrypoint', () => {
|
|
7
|
+
expect(commands).not.toHaveProperty('createDefaultPluginCommandAdapter');
|
|
8
|
+
expect(commands).not.toHaveProperty('reloadPluginCommandSource');
|
|
9
|
+
expect(commands).not.toHaveProperty('pluginScopeDirs');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { executePluginCommand, executeReloadPluginsCommand } from './plugin-command.js';
|
|
9
9
|
|
|
10
10
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
11
|
-
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
11
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
12
12
|
|
|
13
13
|
export function createPluginCommandEntry(): ICommand {
|
|
14
14
|
return {
|
|
@@ -16,6 +16,7 @@ export function createPluginCommandEntry(): ICommand {
|
|
|
16
16
|
displayName: 'Plugins',
|
|
17
17
|
description: PLUGIN_COMMAND_DESCRIPTION,
|
|
18
18
|
source: 'plugin-manager',
|
|
19
|
+
// User-only: installs and enables code; a trust decision.
|
|
19
20
|
modelInvocable: false,
|
|
20
21
|
argumentHint: PLUGIN_COMMAND_ARGUMENT_HINT,
|
|
21
22
|
subcommands: buildPluginCommandSubcommands(),
|
|
@@ -28,6 +29,7 @@ export function createReloadPluginsCommandEntry(): ICommand {
|
|
|
28
29
|
displayName: 'Reload Plugins',
|
|
29
30
|
description: RELOAD_PLUGINS_COMMAND_DESCRIPTION,
|
|
30
31
|
source: 'plugin-manager',
|
|
32
|
+
// User-only: reloading activates whatever plugin code is installed now; a trust decision.
|
|
31
33
|
modelInvocable: false,
|
|
32
34
|
};
|
|
33
35
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
createPluginTuiRequestedEffect,
|
|
2
|
+
createShowPluginManagerIntent,
|
|
4
3
|
resolvePluginCommandAdapter,
|
|
5
4
|
} from '@robota-sdk/agent-framework';
|
|
6
5
|
|
|
7
|
-
import type {
|
|
8
|
-
import type { ICommandPluginAdapter, ICommandResult } from '@robota-sdk/agent-interface-
|
|
6
|
+
import type { ICommandHostAdapterAccess } from '@robota-sdk/agent-framework';
|
|
7
|
+
import type { ICommandPluginAdapter, ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
9
8
|
|
|
10
9
|
function getSubcommandParts(args: string): { subcommand: string; subArgs: string } {
|
|
11
10
|
const parts = args
|
|
@@ -25,12 +24,12 @@ function usage(message: string): ICommandResult {
|
|
|
25
24
|
};
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
function getPluginAdapter(context:
|
|
27
|
+
function getPluginAdapter(context: ICommandHostAdapterAccess): ICommandPluginAdapter | undefined {
|
|
29
28
|
return resolvePluginCommandAdapter(context);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
async function executePluginOperation(
|
|
33
|
-
context:
|
|
32
|
+
context: ICommandHostAdapterAccess,
|
|
34
33
|
operation: (adapter: ICommandPluginAdapter) => Promise<string>,
|
|
35
34
|
): Promise<ICommandResult> {
|
|
36
35
|
const adapter = getPluginAdapter(context);
|
|
@@ -56,7 +55,7 @@ async function executePluginOperation(
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
async function executeMarketplaceCommand(
|
|
59
|
-
context:
|
|
58
|
+
context: ICommandHostAdapterAccess,
|
|
60
59
|
subArgs: string,
|
|
61
60
|
): Promise<ICommandResult> {
|
|
62
61
|
const { subcommand, subArgs: marketplaceArgs } = getSubcommandParts(subArgs);
|
|
@@ -102,7 +101,7 @@ type TPluginIdOperation = (
|
|
|
102
101
|
) => Promise<string> | string;
|
|
103
102
|
|
|
104
103
|
function executePluginIdOperation(
|
|
105
|
-
context:
|
|
104
|
+
context: ICommandHostAdapterAccess,
|
|
106
105
|
pluginId: string,
|
|
107
106
|
usageMessage: string,
|
|
108
107
|
operation: TPluginIdOperation,
|
|
@@ -119,12 +118,12 @@ function executePluginManager(): ICommandResult {
|
|
|
119
118
|
return {
|
|
120
119
|
success: true,
|
|
121
120
|
message: 'Opening plugin manager...',
|
|
122
|
-
|
|
121
|
+
uiIntents: [createShowPluginManagerIntent()],
|
|
123
122
|
};
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
function executeInstallCommand(
|
|
127
|
-
context:
|
|
126
|
+
context: ICommandHostAdapterAccess,
|
|
128
127
|
pluginId: string,
|
|
129
128
|
): Promise<ICommandResult> {
|
|
130
129
|
return executePluginIdOperation(
|
|
@@ -139,7 +138,7 @@ function executeInstallCommand(
|
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
function executeUninstallCommand(
|
|
142
|
-
context:
|
|
141
|
+
context: ICommandHostAdapterAccess,
|
|
143
142
|
pluginId: string,
|
|
144
143
|
): Promise<ICommandResult> {
|
|
145
144
|
return executePluginIdOperation(
|
|
@@ -154,7 +153,7 @@ function executeUninstallCommand(
|
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
function executeEnableCommand(
|
|
157
|
-
context:
|
|
156
|
+
context: ICommandHostAdapterAccess,
|
|
158
157
|
pluginId: string,
|
|
159
158
|
): Promise<ICommandResult> {
|
|
160
159
|
return executePluginIdOperation(
|
|
@@ -169,7 +168,7 @@ function executeEnableCommand(
|
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
function executeDisableCommand(
|
|
172
|
-
context:
|
|
171
|
+
context: ICommandHostAdapterAccess,
|
|
173
172
|
pluginId: string,
|
|
174
173
|
): Promise<ICommandResult> {
|
|
175
174
|
return executePluginIdOperation(
|
|
@@ -184,7 +183,7 @@ function executeDisableCommand(
|
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
export async function executePluginCommand(
|
|
187
|
-
context:
|
|
186
|
+
context: ICommandHostAdapterAccess,
|
|
188
187
|
args: string,
|
|
189
188
|
): Promise<ICommandResult> {
|
|
190
189
|
const { subcommand, subArgs } = getSubcommandParts(args);
|
|
@@ -208,7 +207,7 @@ export async function executePluginCommand(
|
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
export async function executeReloadPluginsCommand(
|
|
211
|
-
context:
|
|
210
|
+
context: ICommandHostAdapterAccess,
|
|
212
211
|
_args: string,
|
|
213
212
|
): Promise<ICommandResult> {
|
|
214
213
|
return executePluginOperation(context, async (adapter) => {
|
|
@@ -220,9 +219,11 @@ export async function executeReloadPluginsCommand(
|
|
|
220
219
|
return `Reloaded ${suffix}.`;
|
|
221
220
|
}).then((result) => {
|
|
222
221
|
if (!result.success) return result;
|
|
222
|
+
// CMD-004 Stage E: the semantic reload already ran HOST-side above (`adapter.reloadPlugins()`);
|
|
223
|
+
// the requester-local registry/autocomplete refresh rides the result as a data hint.
|
|
223
224
|
return {
|
|
224
225
|
...result,
|
|
225
|
-
|
|
226
|
+
data: { ...result.data, pluginRegistryReloaded: true },
|
|
226
227
|
};
|
|
227
228
|
});
|
|
228
229
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { executePresetCommand } from '../preset-command.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ARCH-009 — `/preset` discovers through the registry the HOST resolved with.
|
|
7
|
+
*
|
|
8
|
+
* ARCH-008 moved startup preset resolution onto a per-call instance registry, but `/preset` kept
|
|
9
|
+
* reading `agent-preset`'s module-global one, because a command runs with only an
|
|
10
|
+
* `ICommandHostContext` and had no path to the instance the shell built. That left the last mutable
|
|
11
|
+
* process-global on this axis load-bearing, and it made ARCH-008's R8 property — two products in one
|
|
12
|
+
* process, each with its own presets — only half-true: both would have shared one registry.
|
|
13
|
+
*
|
|
14
|
+
* The registry arrives in the host-ADAPTER bag, the path `/permission-mode` and `/plugin` already take
|
|
15
|
+
* to their host capabilities. A capability the composition root supplies is what that bag is for, and
|
|
16
|
+
* reusing it means this seam has no shape of its own to get wrong.
|
|
17
|
+
*
|
|
18
|
+
* The decisive case is the last one. Everything else is a property of a single host; only "two hosts,
|
|
19
|
+
* one process" is impossible to satisfy with a global, which is why it is the one that proves the
|
|
20
|
+
* seam rather than merely exercising it.
|
|
21
|
+
*/
|
|
22
|
+
function presetSummary(id: string) {
|
|
23
|
+
return { id, title: `${id} title`, description: `${id} description` };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A host whose registry contains exactly the ids it was built with. */
|
|
27
|
+
function hostWithPresets(ids: string[]) {
|
|
28
|
+
const applied: string[] = [];
|
|
29
|
+
return {
|
|
30
|
+
applied,
|
|
31
|
+
context: {
|
|
32
|
+
getCommandHostAdapters: () => ({
|
|
33
|
+
presetRegistry: {
|
|
34
|
+
listPresets: () => ids.map(presetSummary),
|
|
35
|
+
getPreset: (id: string) => (ids.includes(id) ? presetSummary(id) : undefined),
|
|
36
|
+
resolvePreset: (id: string) => ({ id }),
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
getSession: () => ({ getActivePresetId: () => 'default' }),
|
|
40
|
+
getUserInteraction: () => undefined,
|
|
41
|
+
applyPersona: vi.fn(),
|
|
42
|
+
applySelfVerification: vi.fn(),
|
|
43
|
+
applyCommandModuleSelection: () => [],
|
|
44
|
+
} as never,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('ARCH-009 — /preset reads the host-supplied registry', () => {
|
|
49
|
+
it('lists the host registry, not the built-ins', async () => {
|
|
50
|
+
const { context } = hostWithPresets(['acme-reviewer']);
|
|
51
|
+
|
|
52
|
+
const result = await executePresetCommand(context, 'list');
|
|
53
|
+
|
|
54
|
+
expect(result.message).toContain('acme-reviewer');
|
|
55
|
+
// `careful-reviewer` is a shipped built-in and is what the no-registry fallback would list. Its
|
|
56
|
+
// absence here is what shows the fallback was not taken.
|
|
57
|
+
expect(result.message).not.toContain('careful-reviewer');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects an id the HOST does not have, even though a built-in would resolve it', async () => {
|
|
61
|
+
const { context } = hostWithPresets(['acme-reviewer']);
|
|
62
|
+
|
|
63
|
+
const result = await executePresetCommand(context, 'careful-reviewer');
|
|
64
|
+
|
|
65
|
+
expect(result.success).toBe(false);
|
|
66
|
+
expect(result.message).toContain('Unknown preset');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('falls back to the built-ins when the host supplies no registry', async () => {
|
|
70
|
+
// Deliberate, not transitional: a host that never loads external presets has nothing to hand
|
|
71
|
+
// over, and `/preset` should still list the built-ins rather than reporting an empty catalog.
|
|
72
|
+
const context = {
|
|
73
|
+
getSession: () => ({ getActivePresetId: () => 'default' }),
|
|
74
|
+
getUserInteraction: () => undefined,
|
|
75
|
+
applyPersona: vi.fn(),
|
|
76
|
+
applySelfVerification: vi.fn(),
|
|
77
|
+
applyCommandModuleSelection: () => [],
|
|
78
|
+
} as never;
|
|
79
|
+
|
|
80
|
+
const result = await executePresetCommand(context, 'list');
|
|
81
|
+
|
|
82
|
+
expect(result.success).toBe(true);
|
|
83
|
+
expect(result.message).toContain('Available presets:');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('gives two hosts in ONE process their own presets — the property a global makes impossible', async () => {
|
|
87
|
+
const first = hostWithPresets(['acme-reviewer']);
|
|
88
|
+
const second = hostWithPresets(['globex-auditor']);
|
|
89
|
+
|
|
90
|
+
const firstList = await executePresetCommand(first.context, 'list');
|
|
91
|
+
const secondList = await executePresetCommand(second.context, 'list');
|
|
92
|
+
|
|
93
|
+
expect(firstList.message).toContain('acme-reviewer');
|
|
94
|
+
expect(firstList.message).not.toContain('globex-auditor');
|
|
95
|
+
expect(secondList.message).toContain('globex-auditor');
|
|
96
|
+
expect(secondList.message).not.toContain('acme-reviewer');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import { createPresetRegistry } from '@robota-sdk/agent-preset';
|
|
3
|
+
import type {
|
|
4
|
+
ICommandHostContext,
|
|
5
|
+
ICommandSessionRuntime,
|
|
6
|
+
IUnknownCommandModuleName,
|
|
7
|
+
} from '@robota-sdk/agent-framework';
|
|
4
8
|
import { createPresetCommandModule, executePresetCommand } from '../index.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
import { createTestSessionRuntime } from '@robota-sdk/agent-framework/testing';
|
|
10
|
+
import {
|
|
11
|
+
createTestCommandHost,
|
|
12
|
+
type ICreateTestCommandHostOptions,
|
|
13
|
+
} from '@robota-sdk/agent-framework/testing';
|
|
14
|
+
|
|
15
|
+
function createSessionRuntime(overrides?: Partial<ICommandSessionRuntime>) {
|
|
16
|
+
return createTestSessionRuntime({
|
|
8
17
|
clearHistory: () => undefined,
|
|
9
18
|
compact: async () => undefined,
|
|
10
19
|
getContextState: () => ({
|
|
@@ -22,59 +31,61 @@ function createSessionRuntime(overrides?: Partial<ICommandSessionRuntime>): ICom
|
|
|
22
31
|
getFullHistory: () => [],
|
|
23
32
|
getHistory: () => [],
|
|
24
33
|
...overrides,
|
|
25
|
-
};
|
|
34
|
+
});
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
function createCommandHostContext(
|
|
29
38
|
runtime: ICommandSessionRuntime,
|
|
30
|
-
overrides?:
|
|
31
|
-
)
|
|
32
|
-
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
39
|
+
overrides?: ICreateTestCommandHostOptions['overrides'],
|
|
40
|
+
) {
|
|
41
|
+
return createTestCommandHost({
|
|
42
|
+
overrides: {
|
|
43
|
+
getSession: () => runtime,
|
|
44
|
+
getContextState: () => ({
|
|
45
|
+
maxTokens: 100,
|
|
46
|
+
usedTokens: 10,
|
|
47
|
+
usedPercentage: 10,
|
|
48
|
+
remainingPercentage: 90,
|
|
49
|
+
}),
|
|
50
|
+
getAutoCompactThreshold: () => 0.8,
|
|
51
|
+
compactContext: async () => undefined,
|
|
52
|
+
getCwd: () => '/workspace',
|
|
53
|
+
listEditCheckpoints: () => [],
|
|
54
|
+
restoreEditCheckpoint: async () => ({
|
|
55
|
+
target: {
|
|
56
|
+
id: 'checkpoint_1',
|
|
57
|
+
sessionId: 'session_1',
|
|
58
|
+
sequence: 1,
|
|
59
|
+
prompt: 'edit',
|
|
60
|
+
createdAt: '2026-05-03T00:00:00.000Z',
|
|
61
|
+
fileCount: 0,
|
|
62
|
+
},
|
|
63
|
+
restoredCheckpointCount: 1,
|
|
64
|
+
restoredFileCount: 0,
|
|
65
|
+
removedCheckpointCount: 0,
|
|
66
|
+
}),
|
|
67
|
+
rollbackEditCheckpoint: async () => ({
|
|
68
|
+
target: {
|
|
69
|
+
id: 'checkpoint_1',
|
|
70
|
+
sessionId: 'session_1',
|
|
71
|
+
sequence: 1,
|
|
72
|
+
prompt: 'edit',
|
|
73
|
+
createdAt: '2026-05-03T00:00:00.000Z',
|
|
74
|
+
fileCount: 0,
|
|
75
|
+
},
|
|
76
|
+
restoredCheckpointCount: 1,
|
|
77
|
+
restoredFileCount: 0,
|
|
78
|
+
removedCheckpointCount: 0,
|
|
79
|
+
}),
|
|
80
|
+
getUsedMemoryReferences: () => [],
|
|
81
|
+
recordMemoryEvent: () => undefined,
|
|
82
|
+
listBackgroundTasks: () => [],
|
|
83
|
+
readBackgroundTaskLog: async (taskId) => ({ taskId, lines: [] }),
|
|
84
|
+
cancelBackgroundTask: async () => undefined,
|
|
85
|
+
closeBackgroundTask: async () => undefined,
|
|
86
|
+
...overrides,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
describe('preset command module', () => {
|
|
@@ -90,7 +101,7 @@ describe('preset command module', () => {
|
|
|
90
101
|
const result = await executePresetCommand(context, '');
|
|
91
102
|
|
|
92
103
|
expect(result.success).toBe(true);
|
|
93
|
-
for (const preset of listPresets()) {
|
|
104
|
+
for (const preset of createPresetRegistry().listPresets()) {
|
|
94
105
|
expect(result.message).toContain(preset.id);
|
|
95
106
|
}
|
|
96
107
|
// The active preset is marked with the `* ` prefix.
|
|
@@ -130,7 +141,7 @@ describe('preset command module', () => {
|
|
|
130
141
|
const result = await executePresetCommand(context, '__nope__');
|
|
131
142
|
|
|
132
143
|
expect(result.success).toBe(false);
|
|
133
|
-
for (const preset of listPresets()) {
|
|
144
|
+
for (const preset of createPresetRegistry().listPresets()) {
|
|
134
145
|
expect(result.message).toContain(preset.id);
|
|
135
146
|
}
|
|
136
147
|
expect(setActivePresetId).not.toHaveBeenCalled();
|
|
@@ -171,4 +182,39 @@ describe('preset command module', () => {
|
|
|
171
182
|
expect(result.message).toContain('Available presets:');
|
|
172
183
|
expect(setActivePresetId).not.toHaveBeenCalled();
|
|
173
184
|
});
|
|
185
|
+
|
|
186
|
+
describe('INFRA-032: unmatched command-module names surface in the /preset result', () => {
|
|
187
|
+
it('a preset disabling an unknown module name reports it (no longer silent)', async () => {
|
|
188
|
+
// ARCH-009: the external preset reaches `/preset` through the HOST, not through a process-wide
|
|
189
|
+
// registration this case used to perform and then have to undo in an afterEach. This is the
|
|
190
|
+
// seam the item added, exercised by a case that already needed an external preset.
|
|
191
|
+
const registry = createPresetRegistry([
|
|
192
|
+
{
|
|
193
|
+
id: 'ext-unknown-module',
|
|
194
|
+
title: 'Ext Unknown Module',
|
|
195
|
+
description: 'disables a short-form command module name',
|
|
196
|
+
disabledCommandModules: ['editor'],
|
|
197
|
+
},
|
|
198
|
+
]);
|
|
199
|
+
const unknowns: readonly IUnknownCommandModuleName[] = [{ name: 'editor', kind: 'disabled' }];
|
|
200
|
+
const applyCommandModuleSelection = vi.fn(() => unknowns);
|
|
201
|
+
const runtime = createSessionRuntime({ setActivePresetId: vi.fn() });
|
|
202
|
+
const context = createCommandHostContext(runtime, {
|
|
203
|
+
applyCommandModuleSelection,
|
|
204
|
+
getCommandHostAdapters: () => ({ presetRegistry: registry }),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const result = await executePresetCommand(context, 'ext-unknown-module');
|
|
208
|
+
|
|
209
|
+
expect(applyCommandModuleSelection).toHaveBeenCalledWith(undefined, ['editor']);
|
|
210
|
+
// Non-fatal: the switch still succeeds…
|
|
211
|
+
expect(result.success).toBe(true);
|
|
212
|
+
expect(result.message).toContain('Switched to preset: ext-unknown-module');
|
|
213
|
+
// …and the unmatched name is surfaced in the command result (message + data).
|
|
214
|
+
expect(result.message).toContain(
|
|
215
|
+
'Preset command-module "editor" (disabled) matched no module',
|
|
216
|
+
);
|
|
217
|
+
expect(result.data?.unknownCommandModules).toEqual(unknowns);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
174
220
|
});
|
|
@@ -1,20 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPresetRegistry } from '@robota-sdk/agent-preset';
|
|
2
2
|
|
|
3
3
|
import { executePresetCommand } from './preset-command.js';
|
|
4
4
|
|
|
5
5
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
6
|
-
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-
|
|
6
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
7
7
|
|
|
8
8
|
const PRESET_COMMAND_DESCRIPTION = 'List presets or switch the active preset';
|
|
9
9
|
const PRESET_ARGUMENT_HINT = 'list | <preset-id>';
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Build one subcommand per registered preset id (mirrors the permission-mode subcommands).
|
|
13
|
+
*
|
|
14
|
+
* ARCH-009 LIMIT, stated rather than left implicit: this lists the BUILT-INS, because it builds a
|
|
15
|
+
* STATIC catalog entry at module-construction time and has no `ICommandHostContext` to reach a
|
|
16
|
+
* host-supplied registry. `/preset`'s own execution — listing, lookup, resolution — goes through the
|
|
17
|
+
* host's registry, so an embedded host with its own presets resolves and applies correctly; only the
|
|
18
|
+
* pre-computed subcommand hints for tab-completion omit that host's external presets.
|
|
19
|
+
*
|
|
20
|
+
* What ARCH-009 changed here is what the limit COSTS. This used to read a process-wide mutable
|
|
21
|
+
* registry, so the hints a host saw depended on which other host had loaded presets first. They are
|
|
22
|
+
* now the built-ins and nothing else — wrong the same way for everyone, and never someone else's.
|
|
23
|
+
*
|
|
24
|
+
* Closing it needs the catalog to be built per host rather than per module, which is a change to how
|
|
25
|
+
* command modules are constructed and not to this file. Filed separately.
|
|
26
|
+
*/
|
|
12
27
|
function buildPresetSubcommands(source = 'preset'): ICommand[] {
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
return createPresetRegistry()
|
|
29
|
+
.listPresets()
|
|
30
|
+
.map((preset) => ({
|
|
31
|
+
name: preset.id,
|
|
32
|
+
description: preset.description,
|
|
33
|
+
source,
|
|
34
|
+
}));
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
export function createPresetCommandEntry(): ICommand {
|
|
@@ -25,6 +42,7 @@ export function createPresetCommandEntry(): ICommand {
|
|
|
25
42
|
source: 'preset',
|
|
26
43
|
argumentHint: PRESET_ARGUMENT_HINT,
|
|
27
44
|
subcommands: buildPresetSubcommands('preset'),
|
|
45
|
+
// User-only: a preset swaps tools, commands and permission posture; the user chooses it.
|
|
28
46
|
modelInvocable: false,
|
|
29
47
|
};
|
|
30
48
|
}
|