@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
|
@@ -6,24 +6,28 @@
|
|
|
6
6
|
import { resolveShell } from './resolve-shell.js';
|
|
7
7
|
import { spawnInherited } from './spawn-inherited.js';
|
|
8
8
|
|
|
9
|
-
import type {
|
|
10
|
-
|
|
9
|
+
import type {
|
|
10
|
+
ICommandHostTerminalHandoff,
|
|
11
|
+
ICommandHostWorkspace,
|
|
12
|
+
} from '@robota-sdk/agent-framework';
|
|
13
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
11
14
|
|
|
12
15
|
export const SHELL_COMMAND_DESCRIPTION =
|
|
13
16
|
'Drop to an interactive shell (or run `/shell <command>` interactively), then return to the agent.';
|
|
14
17
|
|
|
15
18
|
export async function executeShellCommand(
|
|
16
|
-
context:
|
|
19
|
+
context: ICommandHostTerminalHandoff & ICommandHostWorkspace,
|
|
17
20
|
args: string,
|
|
21
|
+
executable?: string,
|
|
18
22
|
): Promise<ICommandResult> {
|
|
19
|
-
if (context.canHandoffTerminal
|
|
23
|
+
if (!context.canHandoffTerminal()) {
|
|
20
24
|
return {
|
|
21
25
|
message: 'An interactive shell is unavailable here (no interactive terminal).',
|
|
22
26
|
success: false,
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
const shell = resolveShell();
|
|
30
|
+
const shell = resolveShell(executable);
|
|
27
31
|
const cwd = context.getCwd();
|
|
28
32
|
const command = args.trim();
|
|
29
33
|
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
createNodeHostContributionSource,
|
|
7
|
+
InteractiveSession,
|
|
8
|
+
SystemCommandExecutor,
|
|
9
|
+
WorkspaceTrustService,
|
|
10
|
+
} from '@robota-sdk/agent-framework';
|
|
7
11
|
import { createSkillsCommandModule } from '../skills-command-module.js';
|
|
12
|
+
import {
|
|
13
|
+
createTestCommandHost,
|
|
14
|
+
type ICreateTestCommandHostOptions,
|
|
15
|
+
} from '@robota-sdk/agent-framework/testing';
|
|
16
|
+
|
|
17
|
+
import type { IWorkspaceIdentity, IWorkspaceTrustStore } from '@robota-sdk/agent-framework';
|
|
18
|
+
|
|
19
|
+
async function createTrustedProjectAccess(cwd: string) {
|
|
20
|
+
const root = realpathSync(cwd);
|
|
21
|
+
const identity: IWorkspaceIdentity = {
|
|
22
|
+
repositoryKey: `skills-command-test:${root}`,
|
|
23
|
+
displayPath: root,
|
|
24
|
+
worktreeRoot: root,
|
|
25
|
+
};
|
|
26
|
+
const store: IWorkspaceTrustStore = {
|
|
27
|
+
inspect: () => Promise.resolve({ state: 'trusted', generation: 1 }),
|
|
28
|
+
grant: () => Promise.resolve({ state: 'trusted', generation: 1 }),
|
|
29
|
+
revoke: () => Promise.resolve({ state: 'revoked', generation: 2 }),
|
|
30
|
+
};
|
|
31
|
+
return new WorkspaceTrustService({
|
|
32
|
+
identityResolver: { resolve: () => identity },
|
|
33
|
+
store,
|
|
34
|
+
}).inspect(root);
|
|
35
|
+
}
|
|
8
36
|
|
|
9
37
|
function createTempSkill(cwd: string): void {
|
|
10
38
|
const skillDir = join(cwd, '.agents', 'skills', 'audit');
|
|
@@ -28,34 +56,39 @@ function makeParentSession() {
|
|
|
28
56
|
}),
|
|
29
57
|
injectMessage: vi.fn(),
|
|
30
58
|
getSessionId: vi.fn().mockReturnValue('parent-session-id'),
|
|
59
|
+
// SELFHOST-004 P6: the span collector subscribes to the session bus each turn.
|
|
60
|
+
getEventService: vi.fn().mockReturnValue({ subscribe: vi.fn(), unsubscribe: vi.fn() }),
|
|
31
61
|
getSystemMessage: vi.fn().mockReturnValue('# system'),
|
|
32
62
|
getToolSchemas: vi.fn().mockReturnValue([]),
|
|
33
63
|
};
|
|
34
64
|
}
|
|
35
65
|
|
|
36
|
-
function createMockContext(overrides?:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
function createMockContext(overrides?: ICreateTestCommandHostOptions['overrides']) {
|
|
67
|
+
// ARCH-029: overrides over a conformant host rather than a 20-member literal asserting it IS one.
|
|
68
|
+
return createTestCommandHost({
|
|
69
|
+
overrides: {
|
|
70
|
+
getSession: vi.fn(),
|
|
71
|
+
getContextState: vi.fn(),
|
|
72
|
+
getAutoCompactThreshold: vi.fn().mockReturnValue(0.835),
|
|
73
|
+
compactContext: vi.fn(),
|
|
74
|
+
getCwd: vi.fn().mockReturnValue('/workspace'),
|
|
75
|
+
listEditCheckpoints: vi.fn().mockReturnValue([]),
|
|
76
|
+
restoreEditCheckpoint: vi.fn(),
|
|
77
|
+
rollbackEditCheckpoint: vi.fn(),
|
|
78
|
+
getUsedMemoryReferences: vi.fn().mockReturnValue([]),
|
|
79
|
+
recordMemoryEvent: vi.fn(),
|
|
80
|
+
listBackgroundTasks: vi.fn().mockReturnValue([]),
|
|
81
|
+
readBackgroundTaskLog: vi.fn(),
|
|
82
|
+
cancelBackgroundTask: vi.fn(),
|
|
83
|
+
closeBackgroundTask: vi.fn(),
|
|
84
|
+
...overrides,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
54
87
|
}
|
|
55
88
|
|
|
56
89
|
describe('createSkillsCommandModule', () => {
|
|
57
90
|
it('exposes skills as a normal model-invocable command module', () => {
|
|
58
|
-
const module = createSkillsCommandModule({
|
|
91
|
+
const module = createSkillsCommandModule({ contributionSources: [] });
|
|
59
92
|
const command = module.systemCommands?.[0];
|
|
60
93
|
|
|
61
94
|
expect(module.name).toBe('agent-command-skills');
|
|
@@ -64,18 +97,19 @@ describe('createSkillsCommandModule', () => {
|
|
|
64
97
|
]);
|
|
65
98
|
expect(command).toMatchObject({
|
|
66
99
|
name: 'skills',
|
|
100
|
+
semanticRole: 'skillActivation',
|
|
67
101
|
userInvocable: true,
|
|
68
102
|
modelInvocable: true,
|
|
69
103
|
lifecycle: 'inline',
|
|
70
104
|
argumentHint: '[list | <skill-name> [args]]',
|
|
71
105
|
safety: 'read-only',
|
|
72
106
|
});
|
|
73
|
-
expect(command?.
|
|
107
|
+
expect(command?.modelDescription).toContain('projected skills command tool');
|
|
74
108
|
});
|
|
75
109
|
|
|
76
110
|
it('lists skill metadata from the SDK host context', async () => {
|
|
77
111
|
const executor = new SystemCommandExecutor([
|
|
78
|
-
...(createSkillsCommandModule({
|
|
112
|
+
...(createSkillsCommandModule({ contributionSources: [] }).systemCommands ?? []),
|
|
79
113
|
]);
|
|
80
114
|
|
|
81
115
|
const result = await executor.execute(
|
|
@@ -109,7 +143,7 @@ describe('createSkillsCommandModule', () => {
|
|
|
109
143
|
data: { skill: 'repo-writing' },
|
|
110
144
|
});
|
|
111
145
|
const executor = new SystemCommandExecutor([
|
|
112
|
-
...(createSkillsCommandModule({
|
|
146
|
+
...(createSkillsCommandModule({ contributionSources: [] }).systemCommands ?? []),
|
|
113
147
|
]);
|
|
114
148
|
|
|
115
149
|
const result = await executor.execute(
|
|
@@ -130,13 +164,21 @@ describe('createSkillsCommandModule', () => {
|
|
|
130
164
|
});
|
|
131
165
|
|
|
132
166
|
it('lets the SDK normalize virtual /skill-name commands into the composed skills command', async () => {
|
|
133
|
-
const cwd = mkdtempSync(join(tmpdir(), 'robota-skills-command-module-'));
|
|
167
|
+
const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'robota-skills-command-module-')));
|
|
134
168
|
createTempSkill(cwd);
|
|
135
169
|
const parentSession = makeParentSession();
|
|
136
170
|
const session = new InteractiveSession({
|
|
137
171
|
session: parentSession as never,
|
|
138
172
|
cwd,
|
|
139
|
-
|
|
173
|
+
projectAccess: await createTrustedProjectAccess(cwd),
|
|
174
|
+
contributionSources: [createNodeHostContributionSource(cwd)],
|
|
175
|
+
skillRoots: [{ root: join('.agents', 'skills'), kind: 'skills' }],
|
|
176
|
+
commandModules: [
|
|
177
|
+
createSkillsCommandModule({
|
|
178
|
+
contributionSources: [createNodeHostContributionSource(cwd)],
|
|
179
|
+
skillRoots: [{ root: join('.agents', 'skills'), kind: 'skills' }],
|
|
180
|
+
}),
|
|
181
|
+
],
|
|
140
182
|
});
|
|
141
183
|
|
|
142
184
|
const result = await session.executeCommand('audit', 'src/index.ts');
|
|
@@ -147,7 +189,6 @@ describe('createSkillsCommandModule', () => {
|
|
|
147
189
|
skill: 'audit',
|
|
148
190
|
sessionExecution: true,
|
|
149
191
|
}),
|
|
150
|
-
effects: [{ type: 'session-execution-started' }],
|
|
151
192
|
});
|
|
152
193
|
expect(parentSession.run).toHaveBeenCalledWith(
|
|
153
194
|
expect.stringContaining('Audit src/index.ts'),
|
package/src/skills/index.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { SkillCommandSource } from '@robota-sdk/agent-framework';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
executeSkillsCommand,
|
|
5
|
+
SKILLS_COMMAND_DESCRIPTION,
|
|
6
|
+
SKILLS_COMMAND_MODEL_DESCRIPTION,
|
|
7
|
+
} from './skills-command.js';
|
|
4
8
|
|
|
5
|
-
import type {
|
|
6
|
-
|
|
9
|
+
import type {
|
|
10
|
+
ICommandModule,
|
|
11
|
+
IContributionSource,
|
|
12
|
+
ISystemCommand,
|
|
13
|
+
ISkillRootDescriptor,
|
|
14
|
+
} from '@robota-sdk/agent-framework';
|
|
15
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
7
16
|
|
|
8
17
|
export interface ISkillsCommandModuleOptions {
|
|
9
|
-
readonly
|
|
18
|
+
readonly contributionSources: readonly IContributionSource[];
|
|
19
|
+
readonly skillRoots?: readonly ISkillRootDescriptor[];
|
|
10
20
|
}
|
|
11
21
|
|
|
12
22
|
export function createSkillsCommandEntry(): ICommand {
|
|
@@ -14,6 +24,9 @@ export function createSkillsCommandEntry(): ICommand {
|
|
|
14
24
|
name: 'skills',
|
|
15
25
|
displayName: 'Skills',
|
|
16
26
|
description: SKILLS_COMMAND_DESCRIPTION,
|
|
27
|
+
// Model-invocable: activating a skill is how the model follows one; a skill's own
|
|
28
|
+
// `disable-model-invocation` frontmatter still refuses it per skill.
|
|
29
|
+
modelDescription: SKILLS_COMMAND_MODEL_DESCRIPTION,
|
|
17
30
|
source: 'skills',
|
|
18
31
|
modelInvocable: true,
|
|
19
32
|
userInvocable: true,
|
|
@@ -26,6 +39,8 @@ function createSkillsSystemCommand(): ISystemCommand {
|
|
|
26
39
|
const entry = createSkillsCommandEntry();
|
|
27
40
|
return {
|
|
28
41
|
name: entry.name,
|
|
42
|
+
semanticRole: 'skillActivation',
|
|
43
|
+
...(entry.modelDescription !== undefined ? { modelDescription: entry.modelDescription } : {}),
|
|
29
44
|
displayName: entry.displayName,
|
|
30
45
|
description: entry.description,
|
|
31
46
|
requiresPermission: false,
|
|
@@ -48,7 +63,9 @@ export class SkillsCommandSource implements ICommandSource {
|
|
|
48
63
|
|
|
49
64
|
export function createSkillsCommandModule(options: ISkillsCommandModuleOptions): ICommandModule {
|
|
50
65
|
const commandSources: ICommandSource[] = [new SkillsCommandSource()];
|
|
51
|
-
commandSources.push(
|
|
66
|
+
commandSources.push(
|
|
67
|
+
new SkillCommandSource(options.contributionSources, options.skillRoots ?? []),
|
|
68
|
+
);
|
|
52
69
|
|
|
53
70
|
return {
|
|
54
71
|
name: 'agent-command-skills',
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ICommandHostCatalog,
|
|
3
|
+
ICommandHostWorkspace,
|
|
4
|
+
ICommandSkillListEntry,
|
|
5
|
+
} from '@robota-sdk/agent-framework';
|
|
6
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
3
7
|
|
|
4
|
-
export const SKILLS_COMMAND_DESCRIPTION =
|
|
5
|
-
|
|
8
|
+
export const SKILLS_COMMAND_DESCRIPTION = 'List registered skills, or activate one by name';
|
|
9
|
+
|
|
10
|
+
/** What the model is told about `skills`; `SKILLS_COMMAND_DESCRIPTION` is the `/help` line. */
|
|
11
|
+
export const SKILLS_COMMAND_MODEL_DESCRIPTION =
|
|
12
|
+
'Skill command. Before following a matching registered skill from the system prompt Skills section, invoke the projected skills command tool with args "<skill-name> [args]". Without arguments, list registered skills. With a skill name, activate that skill; a skill that declares it is not model-invocable is refused. Returns the skill list with each skill\'s description, or the activated skill\'s instructions to follow. Slash syntax is a UI input/display concern; the SDK command identity is "skills".';
|
|
6
13
|
|
|
7
14
|
interface IParsedSkillsArgs {
|
|
8
15
|
readonly action: 'list' | 'activate';
|
|
@@ -66,20 +73,14 @@ function parseSkillsArgs(args: string): IParsedSkillsArgs {
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export async function executeSkillsCommand(
|
|
69
|
-
context:
|
|
76
|
+
context: ICommandHostCatalog & ICommandHostWorkspace,
|
|
70
77
|
args = '',
|
|
71
78
|
): Promise<ICommandResult> {
|
|
72
79
|
const parsed = parseSkillsArgs(args);
|
|
73
80
|
if (parsed.action === 'activate' && parsed.skillName !== undefined) {
|
|
74
|
-
if (!context.executeSkillCommandByName) {
|
|
75
|
-
return {
|
|
76
|
-
success: false,
|
|
77
|
-
message: 'Skill activation is not available in this session.',
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
81
|
const displayInput = `/${parsed.skillName}${parsed.skillArgs ? ` ${parsed.skillArgs}` : ''}`;
|
|
81
82
|
const result = await context.executeSkillCommandByName(parsed.skillName, parsed.skillArgs, {
|
|
82
|
-
invocationSource: context.getCommandInvocationSource
|
|
83
|
+
invocationSource: context.getCommandInvocationSource(),
|
|
83
84
|
displayInput,
|
|
84
85
|
rawInput: displayInput,
|
|
85
86
|
});
|
|
@@ -91,7 +92,7 @@ export async function executeSkillsCommand(
|
|
|
91
92
|
);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const skills = context.listSkills
|
|
95
|
+
const skills = context.listSkills();
|
|
95
96
|
return {
|
|
96
97
|
success: true,
|
|
97
98
|
message: formatSkillsMessage(skills),
|
|
@@ -2,8 +2,9 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import type { ICommandHostContext } from '@robota-sdk/agent-framework';
|
|
3
3
|
import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
4
4
|
import { createStatusLineCommandModule } from '../statusline-command-module.js';
|
|
5
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
5
6
|
|
|
6
|
-
const COMMAND_CONTEXT =
|
|
7
|
+
const COMMAND_CONTEXT = createTestCommandHost();
|
|
7
8
|
|
|
8
9
|
describe('createStatusLineCommandModule', () => {
|
|
9
10
|
it('provides statusline metadata and user-only executable command from one module owner', () => {
|
|
@@ -76,12 +77,12 @@ describe('createStatusLineCommandModule', () => {
|
|
|
76
77
|
expect(result).toEqual({
|
|
77
78
|
success: true,
|
|
78
79
|
message,
|
|
79
|
-
|
|
80
|
+
hostActions: [{ type: 'statusline-settings-patch', patch }],
|
|
80
81
|
});
|
|
81
82
|
},
|
|
82
83
|
);
|
|
83
84
|
|
|
84
|
-
it('reports usage for unsupported arguments without
|
|
85
|
+
it('reports usage for unsupported arguments without host actions', async () => {
|
|
85
86
|
const executor = new SystemCommandExecutor([
|
|
86
87
|
...(createStatusLineCommandModule().systemCommands ?? []),
|
|
87
88
|
]);
|
|
@@ -90,6 +91,6 @@ describe('createStatusLineCommandModule', () => {
|
|
|
90
91
|
|
|
91
92
|
expect(result?.success).toBe(false);
|
|
92
93
|
expect(result?.message).toContain('Usage: /statusline');
|
|
93
|
-
expect(result?.
|
|
94
|
+
expect(result?.hostActions).toBeUndefined();
|
|
94
95
|
});
|
|
95
96
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { executeStatusLineCommand } from './statusline-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 createStatusLineCommandEntry(): ICommand {
|
|
13
13
|
return {
|
|
@@ -17,6 +17,7 @@ export function createStatusLineCommandEntry(): ICommand {
|
|
|
17
17
|
source: 'statusline',
|
|
18
18
|
argumentHint: STATUSLINE_COMMAND_ARGUMENT_HINT,
|
|
19
19
|
subcommands: buildStatusLineCommandSubcommands('statusline'),
|
|
20
|
+
// User-only: UI preference.
|
|
20
21
|
modelInvocable: false,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_STATUS_LINE_COMMAND_SETTINGS } from '@robota-sdk/agent-framework';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { ICommandHostNoCapability } from '@robota-sdk/agent-framework';
|
|
4
4
|
import type {
|
|
5
5
|
ICommandResult,
|
|
6
6
|
TStatusLineCommandSettingsPatch,
|
|
7
|
-
} from '@robota-sdk/agent-interface-
|
|
7
|
+
} from '@robota-sdk/agent-interface-command';
|
|
8
8
|
|
|
9
9
|
interface IStatusLineCommandSuccessAction {
|
|
10
10
|
success: true;
|
|
@@ -64,7 +64,7 @@ function parseStatusLineArgs(args: string): TStatusLineCommandAction {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export function executeStatusLineCommand(
|
|
67
|
-
_context:
|
|
67
|
+
_context: ICommandHostNoCapability,
|
|
68
68
|
args: string,
|
|
69
69
|
): ICommandResult {
|
|
70
70
|
const action = parseStatusLineArgs(args);
|
|
@@ -75,6 +75,6 @@ export function executeStatusLineCommand(
|
|
|
75
75
|
return {
|
|
76
76
|
success: true,
|
|
77
77
|
message: action.message,
|
|
78
|
-
|
|
78
|
+
hostActions: [{ type: 'statusline-settings-patch', patch: action.patch }],
|
|
79
79
|
};
|
|
80
80
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCREEN-2002 TC-09 — `/theme`.
|
|
3
|
+
*
|
|
4
|
+
* Three properties carry this command. It emits EXACTLY ONE patch per invocation, so a submission
|
|
5
|
+
* cannot half-apply. An unknown id writes NOTHING — not the theme, and not the toggles submitted
|
|
6
|
+
* beside it — because a user who mistyped an id did not also ask for the rest. And `motion on`
|
|
7
|
+
* means `reducedMotion: false`: the inversion is the easy thing to read backwards, and a user
|
|
8
|
+
* asking for motion and getting stillness would look like the command doing nothing at all.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
|
|
12
|
+
import { executeThemeCommand } from '../theme-command-module.js';
|
|
13
|
+
|
|
14
|
+
import type { TAppearanceSettingsPatch } from '@robota-sdk/agent-framework';
|
|
15
|
+
import type {
|
|
16
|
+
IThemeAppearanceState,
|
|
17
|
+
IThemeCatalogueEntry,
|
|
18
|
+
IThemeCataloguePort,
|
|
19
|
+
TReducedMotionOverride,
|
|
20
|
+
} from '@robota-sdk/agent-interface-command';
|
|
21
|
+
|
|
22
|
+
const THEMES: IThemeCatalogueEntry[] = [
|
|
23
|
+
{ id: 'dark', name: 'Dark', appearance: 'dark', source: 'built-in' },
|
|
24
|
+
{ id: 'light', name: 'Light', appearance: 'light', source: 'built-in' },
|
|
25
|
+
{
|
|
26
|
+
id: 'dark-daltonized',
|
|
27
|
+
name: 'Dark (daltonized)',
|
|
28
|
+
appearance: 'dark',
|
|
29
|
+
source: 'built-in',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function catalogue(
|
|
34
|
+
state: Partial<IThemeAppearanceState['settings']> = {},
|
|
35
|
+
reducedMotionOverride?: TReducedMotionOverride,
|
|
36
|
+
reducedMotionForRun?: boolean,
|
|
37
|
+
): IThemeCataloguePort {
|
|
38
|
+
const settings = {
|
|
39
|
+
theme: 'dark',
|
|
40
|
+
syntaxHighlighting: true,
|
|
41
|
+
reducedMotion: false,
|
|
42
|
+
...state,
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
listThemes: () => THEMES,
|
|
46
|
+
getTheme: (id) => THEMES.find((theme) => theme.id === id),
|
|
47
|
+
getAppearance: () => ({
|
|
48
|
+
settings,
|
|
49
|
+
...(reducedMotionOverride === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: {
|
|
52
|
+
reducedMotionPin: {
|
|
53
|
+
tier: reducedMotionOverride,
|
|
54
|
+
reducedMotion: reducedMotionForRun ?? true,
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function patchOf(result: { hostActions?: readonly unknown[] }): TAppearanceSettingsPatch {
|
|
62
|
+
expect(result.hostActions).toHaveLength(1);
|
|
63
|
+
const action = result.hostActions?.[0] as {
|
|
64
|
+
type: string;
|
|
65
|
+
patch: TAppearanceSettingsPatch;
|
|
66
|
+
};
|
|
67
|
+
expect(action.type).toBe('appearance-settings-patch');
|
|
68
|
+
return action.patch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('/theme without a catalogue (SCREEN-2002 TC-09)', () => {
|
|
72
|
+
it('reports its unavailability instead of failing silently or writing anything', () => {
|
|
73
|
+
const result = executeThemeCommand(undefined, 'light');
|
|
74
|
+
|
|
75
|
+
expect(result.success).toBe(false);
|
|
76
|
+
expect(result.message).toBe('Themes are not available in this environment.');
|
|
77
|
+
expect(result.hostActions).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('/theme with no argument (SCREEN-2002 TC-09)', () => {
|
|
82
|
+
it('asks the surface for the picker rather than changing anything', () => {
|
|
83
|
+
const result = executeThemeCommand(catalogue(), ' ');
|
|
84
|
+
|
|
85
|
+
expect(result.success).toBe(true);
|
|
86
|
+
expect(result.uiIntents).toEqual([{ type: 'show-theme-picker' }]);
|
|
87
|
+
expect(result.hostActions).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('/theme list (SCREEN-2002 TC-09)', () => {
|
|
92
|
+
it('marks the active theme and names each source, and writes nothing', () => {
|
|
93
|
+
const result = executeThemeCommand(catalogue({ theme: 'light' }), 'list');
|
|
94
|
+
|
|
95
|
+
expect(result.success).toBe(true);
|
|
96
|
+
expect(result.message).toContain('* light — Light (light, built-in)');
|
|
97
|
+
expect(result.message).toContain(' dark — Dark (dark, built-in)');
|
|
98
|
+
expect(result.message).toContain('syntax highlighting: on');
|
|
99
|
+
expect(result.message).toContain('reduced motion: off');
|
|
100
|
+
expect(result.hostActions).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('reports what THIS RUN does and what is saved, never one in place of the other', () => {
|
|
104
|
+
// SCREEN-2002 TC-13 found this at the PTY level: reporting the persisted value beside the tier
|
|
105
|
+
// printed `reduced motion: off (… pinned by flag)` on a `--reduced-motion` run — a line that
|
|
106
|
+
// contradicts itself, and that answers "is motion reduced right now" with the wrong word.
|
|
107
|
+
const pinnedOn = executeThemeCommand(catalogue({ reducedMotion: false }, 'flag', true), 'list');
|
|
108
|
+
expect(pinnedOn.message).toContain(
|
|
109
|
+
'reduced motion: on for this run (pinned by flag; saved off)',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// `--no-reduced-motion` is an override too, and it pins the opposite value: a surface that
|
|
113
|
+
// renders the TIER alone cannot tell these two runs apart.
|
|
114
|
+
const pinnedOff = executeThemeCommand(
|
|
115
|
+
catalogue({ reducedMotion: true }, 'flag', false),
|
|
116
|
+
'list',
|
|
117
|
+
);
|
|
118
|
+
expect(pinnedOff.message).toContain(
|
|
119
|
+
'reduced motion: off for this run (pinned by flag; saved on)',
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('/theme <id> (SCREEN-2002 TC-09)', () => {
|
|
125
|
+
it('emits exactly one patch naming the theme', () => {
|
|
126
|
+
const result = executeThemeCommand(catalogue(), 'dark-daltonized');
|
|
127
|
+
|
|
128
|
+
expect(result.success).toBe(true);
|
|
129
|
+
expect(patchOf(result)).toEqual({ theme: 'dark-daltonized' });
|
|
130
|
+
expect(result.message).toContain('Dark (daltonized)');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('refuses an unknown id, names the catalogue, and writes NOTHING', () => {
|
|
134
|
+
const result = executeThemeCommand(catalogue(), 'solarized');
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(false);
|
|
137
|
+
expect(result.message).toContain('Unknown theme "solarized"');
|
|
138
|
+
expect(result.message).toContain('dark, light, dark-daltonized');
|
|
139
|
+
expect(result.hostActions).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('does not apply the toggles submitted beside an unknown id', () => {
|
|
143
|
+
const result = executeThemeCommand(catalogue(), 'solarized syntax off');
|
|
144
|
+
|
|
145
|
+
expect(result.success).toBe(false);
|
|
146
|
+
expect(result.hostActions).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('carries the theme and its toggles in ONE patch', () => {
|
|
150
|
+
const result = executeThemeCommand(catalogue(), 'light syntax off motion off');
|
|
151
|
+
|
|
152
|
+
expect(patchOf(result)).toEqual({
|
|
153
|
+
theme: 'light',
|
|
154
|
+
syntaxHighlighting: false,
|
|
155
|
+
reducedMotion: true,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('the motion inversion (SCREEN-2002 TC-09)', () => {
|
|
161
|
+
it('reads `motion on` as animation, which is reducedMotion FALSE', () => {
|
|
162
|
+
expect(patchOf(executeThemeCommand(catalogue({ reducedMotion: true }), 'motion on'))).toEqual({
|
|
163
|
+
reducedMotion: false,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('reads `motion off` as stillness, which is reducedMotion TRUE', () => {
|
|
168
|
+
expect(patchOf(executeThemeCommand(catalogue(), 'motion off'))).toEqual({
|
|
169
|
+
reducedMotion: true,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('reports the change in the user s words, not the settings key s', () => {
|
|
174
|
+
expect(executeThemeCommand(catalogue(), 'motion off').message).toContain('motion off');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('persists the setting but says this run keeps what pinned it', () => {
|
|
178
|
+
const result = executeThemeCommand(catalogue({ reducedMotion: true }, 'flag'), 'motion on');
|
|
179
|
+
|
|
180
|
+
expect(patchOf(result)).toEqual({ reducedMotion: false });
|
|
181
|
+
expect(result.message).toContain('Saved, but this run keeps reduced motion pinned by flag');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('/theme syntax (SCREEN-2002 TC-09)', () => {
|
|
186
|
+
it('toggles highlighting without touching the theme', () => {
|
|
187
|
+
expect(patchOf(executeThemeCommand(catalogue(), 'syntax off'))).toEqual({
|
|
188
|
+
syntaxHighlighting: false,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('a malformed argument (SCREEN-2002 TC-09)', () => {
|
|
194
|
+
it.each(['motion', 'syntax', 'motion yes', 'syntax enable', 'light motion', 'light nonsense on'])(
|
|
195
|
+
'refuses %o with the usage line rather than guessing',
|
|
196
|
+
(args) => {
|
|
197
|
+
const result = executeThemeCommand(catalogue(), args);
|
|
198
|
+
|
|
199
|
+
expect(result.success).toBe(false);
|
|
200
|
+
expect(result.message).toContain('Usage: /theme');
|
|
201
|
+
expect(result.hostActions).toBeUndefined();
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createThemeCommandEntry,
|
|
3
|
+
createThemeCommandModule,
|
|
4
|
+
executeThemeCommand,
|
|
5
|
+
ThemeCommandSource,
|
|
6
|
+
} from './theme-command-module.js';
|
|
7
|
+
export type {
|
|
8
|
+
IThemeAppearanceState,
|
|
9
|
+
IThemeCatalogueEntry,
|
|
10
|
+
IThemeCataloguePort,
|
|
11
|
+
TReducedMotionOverride,
|
|
12
|
+
} from '@robota-sdk/agent-interface-command';
|