@robota-sdk/agent-command 3.0.0-beta.83 → 3.0.0-beta.85
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/CHANGELOG.md +69 -0
- package/README.md +71 -62
- package/dist/node/index.cjs +75 -72
- package/dist/node/index.d.cts +37 -45
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +37 -45
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +78 -75
- package/dist/node/index.js.map +1 -1
- package/package.json +9 -8
- package/src/agent/__tests__/agent-command.test.ts +62 -0
- package/src/agent/agent-command-parser.ts +2 -2
- package/src/agent/agent-command.ts +20 -0
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +2 -2
- package/src/default/__tests__/default-command-modules.test.ts +5 -4
- package/src/default/__tests__/model-exposure.test.ts +2 -0
- package/src/default/default-command-modules.ts +8 -0
- package/src/doctor/__tests__/doctor-fixture.ts +1 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +26 -6
- package/src/doctor/doctor-extensions-probe.ts +36 -12
- package/src/effort/effort-command.test.ts +65 -2
- package/src/effort/effort-command.ts +19 -5
- package/src/git/__tests__/fake-git-port.ts +9 -2
- package/src/git/git-command-module.ts +3 -4
- package/src/git/git-commit.ts +6 -3
- package/src/git/index.ts +4 -1
- package/src/goal/goal-command.ts +3 -1
- package/src/index.ts +6 -0
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +225 -1
- package/src/mcp-activation/mcp-activation-command-module.ts +6 -0
- package/src/mcp-activation/mcp-activation-command.ts +77 -2
- package/src/memory/__tests__/memory-command-module.test.ts +40 -2
- package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
- package/src/memory/memory-command-module.ts +4 -2
- package/src/memory/memory-command.ts +42 -2
- package/src/mode/__tests__/mode-command-module.test.ts +40 -0
- package/src/mode/mode-command.ts +1 -1
- package/src/model/__tests__/model-command-module.test.ts +80 -0
- package/src/model/__tests__/model-command.test.ts +240 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +56 -0
- package/src/model/model-command.ts +223 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +84 -3
- package/src/plugin/plugin-command.ts +50 -8
- package/src/provider/__tests__/org-policy.test.ts +4 -0
- package/src/provider/__tests__/provider-command-module.test.ts +276 -14
- package/src/provider/__tests__/scripted-interaction.ts +12 -2
- package/src/provider/provider-command-execution.ts +54 -8
- package/src/provider/provider-command-profile-lifecycle.ts +48 -2
- package/src/provider/provider-command-profile-operations.ts +14 -0
- package/src/provider/provider-command-profile.ts +7 -4
- package/src/provider/provider-command-setup.ts +14 -1
- package/src/rewind/__tests__/rewind-command-module.test.ts +2 -1
- package/src/schedule/__tests__/loop-command-functional.test.ts +13 -0
- package/src/schedule/__tests__/loop-command.test.ts +238 -4
- package/src/schedule/loop-command.ts +95 -3
- package/src/schedule/schedule-command-module.ts +8 -4
- package/src/settings/settings-command-module.ts +2 -2
- package/src/git/__tests__/git-diff.test.ts +0 -125
- package/src/git/__tests__/git-process.test.ts +0 -221
- package/src/git/__tests__/git-status.test.ts +0 -117
- package/src/git/git-diff.ts +0 -129
- package/src/git/git-process.ts +0 -169
- package/src/git/git-status.ts +0 -134
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-command",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0-beta.85",
|
|
4
|
+
"description": "Slash-command modules for Robota SDK sessions (/help, /permissions, /mcp, /rewind, …)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
7
7
|
"types": "dist/node/index.d.ts",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"CHANGELOG.md"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@robota-sdk/agent-
|
|
44
|
-
"@robota-sdk/agent-framework": "3.0.0-beta.
|
|
45
|
-
"@robota-sdk/agent-
|
|
46
|
-
"@robota-sdk/agent-interface-execution": "3.0.0-beta.
|
|
47
|
-
"@robota-sdk/agent-interface-session": "3.0.0-beta.
|
|
48
|
-
"@robota-sdk/agent-preset": "3.0.0-beta.
|
|
43
|
+
"@robota-sdk/agent-interface-command": "3.0.0-beta.85",
|
|
44
|
+
"@robota-sdk/agent-framework": "3.0.0-beta.85",
|
|
45
|
+
"@robota-sdk/agent-core": "3.0.0-beta.85",
|
|
46
|
+
"@robota-sdk/agent-interface-execution": "3.0.0-beta.85",
|
|
47
|
+
"@robota-sdk/agent-interface-session": "3.0.0-beta.85",
|
|
48
|
+
"@robota-sdk/agent-preset": "3.0.0-beta.85"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.19.21",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"url": "https://github.com/woojubb/robota.git",
|
|
61
61
|
"directory": "packages/agent-command"
|
|
62
62
|
},
|
|
63
|
+
"homepage": "https://docs.robota.io/en/packages/agent-command/",
|
|
63
64
|
"publishConfig": {
|
|
64
65
|
"access": "public"
|
|
65
66
|
},
|
|
@@ -15,6 +15,7 @@ function createMockSession(overrides?: Record<string, unknown>) {
|
|
|
15
15
|
{ name: 'general-purpose', description: 'General-purpose task execution agent.' },
|
|
16
16
|
{ name: 'Plan', description: 'Read-only planning agent.' },
|
|
17
17
|
]),
|
|
18
|
+
setDefaultAgentType: vi.fn(),
|
|
18
19
|
listAgentJobs: vi.fn().mockReturnValue([]),
|
|
19
20
|
readBackgroundTaskLog: vi.fn().mockResolvedValue({ taskId: 'agent_1', lines: [] }),
|
|
20
21
|
spawnAgentJob: vi.fn().mockResolvedValue({
|
|
@@ -518,6 +519,67 @@ describe('agent command module', () => {
|
|
|
518
519
|
expect(result?.uiIntents).toEqual([{ type: 'show-agent-switcher' }]);
|
|
519
520
|
});
|
|
520
521
|
|
|
522
|
+
it('#3282 §4: /agent <name> with nothing after it selects a default agent instead of failing', async () => {
|
|
523
|
+
const module = createAgentCommandModule();
|
|
524
|
+
const executor = new SystemCommandExecutor([
|
|
525
|
+
...createSystemCommands(),
|
|
526
|
+
...(module.systemCommands ?? []),
|
|
527
|
+
]);
|
|
528
|
+
const session = createMockSession();
|
|
529
|
+
|
|
530
|
+
const result = await executor.execute('agent', session, 'Plan');
|
|
531
|
+
|
|
532
|
+
expect(result?.success).toBe(true);
|
|
533
|
+
expect(result?.message).toBe('Default agent: Plan');
|
|
534
|
+
expect(result?.data).toEqual({ agentType: 'Plan' });
|
|
535
|
+
expect(
|
|
536
|
+
(session as unknown as { setDefaultAgentType: ReturnType<typeof vi.fn> }).setDefaultAgentType,
|
|
537
|
+
).toHaveBeenCalledWith('Plan');
|
|
538
|
+
expect(
|
|
539
|
+
(session as unknown as { spawnAgentJob: ReturnType<typeof vi.fn> }).spawnAgentJob,
|
|
540
|
+
).not.toHaveBeenCalled();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('#3282 §4: a single unrecognized word is still a prompt for the default agent, not a selection', async () => {
|
|
544
|
+
const module = createAgentCommandModule();
|
|
545
|
+
const executor = new SystemCommandExecutor([
|
|
546
|
+
...createSystemCommands(),
|
|
547
|
+
...(module.systemCommands ?? []),
|
|
548
|
+
]);
|
|
549
|
+
const session = createMockSession();
|
|
550
|
+
|
|
551
|
+
const result = await executor.execute('agent', session, 'NotARealAgent');
|
|
552
|
+
|
|
553
|
+
expect(result?.success).toBe(true);
|
|
554
|
+
expect(
|
|
555
|
+
(session as unknown as { spawnAgentJob: ReturnType<typeof vi.fn> }).spawnAgentJob,
|
|
556
|
+
).toHaveBeenCalledWith({
|
|
557
|
+
agentType: 'general-purpose',
|
|
558
|
+
label: 'general-purpose',
|
|
559
|
+
mode: 'background',
|
|
560
|
+
prompt: 'NotARealAgent',
|
|
561
|
+
});
|
|
562
|
+
expect(
|
|
563
|
+
(session as unknown as { setDefaultAgentType: ReturnType<typeof vi.fn> }).setDefaultAgentType,
|
|
564
|
+
).not.toHaveBeenCalled();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('#3282 §4: a known agent name followed by a prompt still runs a job, not a selection', async () => {
|
|
568
|
+
const module = createAgentCommandModule();
|
|
569
|
+
const executor = new SystemCommandExecutor([
|
|
570
|
+
...createSystemCommands(),
|
|
571
|
+
...(module.systemCommands ?? []),
|
|
572
|
+
]);
|
|
573
|
+
const session = createMockSession();
|
|
574
|
+
|
|
575
|
+
const result = await executor.execute('agent', session, 'Plan "draft architecture"');
|
|
576
|
+
|
|
577
|
+
expect(result?.success).toBe(true);
|
|
578
|
+
expect(
|
|
579
|
+
(session as unknown as { setDefaultAgentType: ReturnType<typeof vi.fn> }).setDefaultAgentType,
|
|
580
|
+
).not.toHaveBeenCalled();
|
|
581
|
+
});
|
|
582
|
+
|
|
521
583
|
it('shows text list when agent list subcommand is used explicitly', async () => {
|
|
522
584
|
const module = createAgentCommandModule();
|
|
523
585
|
const executor = new SystemCommandExecutor([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DEFAULT_AGENT_TYPE } from '@robota-sdk/agent-framework';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { TBackgroundTaskIsolation } from '@robota-sdk/agent-interface-execution';
|
|
4
4
|
|
|
5
5
|
export type TAgentMode = 'background';
|
|
6
6
|
|
|
@@ -44,6 +44,21 @@ function executeOpenSwitcher(): ICommandResult {
|
|
|
44
44
|
return { message: '', uiIntents: [{ type: 'show-agent-switcher' }], success: true };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* #3282 §4: `/agent <name>` with nothing after the name sets the session's default agent type —
|
|
49
|
+
* the one `/agent <prompt>` falls back to, and the one the agent switcher shows checked. Distinct
|
|
50
|
+
* from `executeRun`, which a name FOLLOWED by a prompt still reaches (a prompt makes it a run, not
|
|
51
|
+
* a selection).
|
|
52
|
+
*/
|
|
53
|
+
function executeSelectDefaultAgent(session: IAgentJobHostContext, agentType: string): ICommandResult {
|
|
54
|
+
session.setDefaultAgentType(agentType);
|
|
55
|
+
return {
|
|
56
|
+
message: `Default agent: ${agentType}`,
|
|
57
|
+
success: true,
|
|
58
|
+
data: { agentType },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
async function executeList(session: IAgentJobHostContext): Promise<ICommandResult> {
|
|
48
63
|
const agents = session.listAgentDefinitions();
|
|
49
64
|
const jobs = session.listAgentJobs();
|
|
@@ -223,6 +238,11 @@ export async function executeAgentCommand(
|
|
|
223
238
|
if (action === 'send') return executeSend(session, tokens);
|
|
224
239
|
if (action === 'stop' || action === 'cancel') return executeStop(session, tokens);
|
|
225
240
|
if (action === 'close') return executeClose(session, tokens);
|
|
241
|
+
// `/agent <name>` alone (a known agent, nothing after it) selects a default rather than failing
|
|
242
|
+
// the "run" parse for want of a prompt — the same one-token shape the switcher sends.
|
|
243
|
+
if (tokens.length === 0 && getAvailableAgentNames(session).has(action)) {
|
|
244
|
+
return executeSelectDefaultAgent(session, action);
|
|
245
|
+
}
|
|
226
246
|
return executeRun(session, [action, ...tokens]);
|
|
227
247
|
} catch (error) {
|
|
228
248
|
return { message: formatError(error), success: false };
|
|
@@ -44,7 +44,7 @@ Run it with no arguments.",
|
|
|
44
44
|
},
|
|
45
45
|
"loop": {
|
|
46
46
|
"argumentHint": "[prompt] | <N><s|m|h|d> [prompt] | <prompt> every <N> <unit> | list | stop <id>",
|
|
47
|
-
"description": "Repeat a prompt on a fixed cadence (\`<N><s|m|h|d>
|
|
47
|
+
"description": "Repeat a prompt on a fixed cadence (\`<N><s|m|h|d> <prompt>\`) or self-paced (\`<prompt>\`: each iteration chooses its next delay or stops). Use it when the user asks for recurring work in this session, and always give your own prompt: any form that would fall back to the host's default maintenance prompt — bare \`/loop\` or an interval alone (\`<N><s|m|h|d>\`) — needs a person to confirm it and is always refused for you, whether or not one is attached. \`list\` shows active loops; \`stop <id>\` ends one, even mid-turn. Returns the loop id and its next fire time, the loop list, or a refusal naming the command to suggest.",
|
|
48
48
|
"kind": "builtin-command",
|
|
49
49
|
"modelInvocable": true,
|
|
50
50
|
"name": "loop",
|
|
@@ -64,7 +64,7 @@ Subcommands you may run (bare \`mcp\` is also allowed):
|
|
|
64
64
|
},
|
|
65
65
|
"memory": {
|
|
66
66
|
"argumentHint": "[list | show [topic] | add <user|feedback|project|reference> <topic> <text> | pending | used]",
|
|
67
|
-
"description": "Read and write this project’s durable memory. Use \`list\` or \`show [topic]\` to look up stored conventions, preferences or references before asking the user again; \`add <type> <topic> <text>\` to save a durable preference, project convention, feedback item or reference worth reusing across sessions (never secrets, credentials or transient facts); \`pending\` to see candidates awaiting the user’s review; \`used\` to report which memory items informed this turn. Bare lists topics. Returns the requested topics or entries, or a confirmation of what was saved. Approving or rejecting a pending candidate is the user’s review: suggest \`/memory approve <id>\` or \`/memory reject <id>\`.
|
|
67
|
+
"description": "Read and write this project’s durable memory. Use \`list\` or \`show [topic]\` to look up stored conventions, preferences or references before asking the user again; \`add <type> <topic> <text>\` to save a durable preference, project convention, feedback item or reference worth reusing across sessions (never secrets, credentials or transient facts); \`pending\` to see candidates awaiting the user’s review; \`used\` to report which memory items informed this turn. Bare lists topics. In plan mode \`add\` is refused: save after plan mode ends. Returns the requested topics or entries, or a confirmation of what was saved. Approving or rejecting a pending candidate is the user’s review: suggest \`/memory approve <id>\` or \`/memory reject <id>\`.
|
|
68
68
|
Subcommands you may run (bare \`memory\` is also allowed):
|
|
69
69
|
- list: List project memory topics
|
|
70
70
|
- show: Show project memory index or a topic
|
|
@@ -50,10 +50,10 @@ describe('createDefaultCommandModules — PRESET-004 module-selection delta', ()
|
|
|
50
50
|
const names = moduleNames(baseOptions);
|
|
51
51
|
// No-regression: the default set length is the documented 32 modules (SELFHOST-002 added
|
|
52
52
|
// `/plan`; PEER-004 added `/peers`; CLI-1994 added `/fork`; MCP-2520 added `/mcp`; CLI-1988 added
|
|
53
|
-
// `/output-style`; FLOW-008 added `/effort`; `/advisor` joined beside it
|
|
54
|
-
// assertion that matters — a length on its own can be restored by any
|
|
55
|
-
// exists only to catch an addition that also removed something.
|
|
56
|
-
expect(names).toHaveLength(
|
|
53
|
+
// `/output-style`; FLOW-008 added `/effort`; `/advisor` joined beside it; #3282 §2 added `/model`).
|
|
54
|
+
// The list below is the assertion that matters — a length on its own can be restored by any
|
|
55
|
+
// substitution, and the count exists only to catch an addition that also removed something.
|
|
56
|
+
expect(names).toHaveLength(39);
|
|
57
57
|
expect(names).toEqual([
|
|
58
58
|
'agent-command-skills',
|
|
59
59
|
'agent-command-help',
|
|
@@ -94,6 +94,7 @@ describe('createDefaultCommandModules — PRESET-004 module-selection delta', ()
|
|
|
94
94
|
'agent-command-handoff',
|
|
95
95
|
'agent-command-remote-control',
|
|
96
96
|
'agent-command-provider',
|
|
97
|
+
'agent-command-model',
|
|
97
98
|
]);
|
|
98
99
|
});
|
|
99
100
|
|
|
@@ -109,6 +109,8 @@ const USER_ONLY_COMMANDS = [
|
|
|
109
109
|
'events',
|
|
110
110
|
// Account and credential actions.
|
|
111
111
|
'provider',
|
|
112
|
+
// Cost and capability decisions: the person chooses, never the model on its own (#3282 §2).
|
|
113
|
+
'model',
|
|
112
114
|
// Permission widening or permission-mode changes.
|
|
113
115
|
'mode',
|
|
114
116
|
'permissions',
|
|
@@ -20,6 +20,7 @@ import { createLanguageCommandModule } from '../language/index.js';
|
|
|
20
20
|
import { createMCPActivationCommandModule } from '../mcp-activation/index.js';
|
|
21
21
|
import { createMemoryCommandModule } from '../memory/index.js';
|
|
22
22
|
import { createModeCommandModule } from '../mode/index.js';
|
|
23
|
+
import { createModelCommandModule } from '../model/index.js';
|
|
23
24
|
import { createSandboxCommandModule } from '../sandbox/index.js';
|
|
24
25
|
import { createOutputStyleCommandModule } from '../output-style/index.js';
|
|
25
26
|
import { createEventsCommandModule } from '../events/index.js';
|
|
@@ -204,6 +205,13 @@ export function createDefaultCommandModules({
|
|
|
204
205
|
settings: providerSettingsAdapter,
|
|
205
206
|
...(orgPolicy === undefined ? {} : { orgPolicy }),
|
|
206
207
|
}),
|
|
208
|
+
// #3282 §2: shares the provider command's settings adapter and definitions — one settings write
|
|
209
|
+
// owner, never a second copy that could drift from what `/provider switch` reads and writes.
|
|
210
|
+
createModelCommandModule({
|
|
211
|
+
providerDefinitions,
|
|
212
|
+
settings: providerSettingsAdapter,
|
|
213
|
+
...(orgPolicy === undefined ? {} : { orgPolicy }),
|
|
214
|
+
}),
|
|
207
215
|
];
|
|
208
216
|
const builtModuleNames = modules.map((module) => module.name);
|
|
209
217
|
return {
|
|
@@ -139,6 +139,7 @@ export function installBrokenHome(fixture: IDoctorFixture): void {
|
|
|
139
139
|
JSON.stringify({
|
|
140
140
|
mcpServers: {
|
|
141
141
|
ghost: { command: 'robota-doctor-missing-binary', env: { GHOST_TOKEN: MARKERS.mcp } },
|
|
142
|
+
ready: { type: 'http', url: 'https://mcp.example.test' },
|
|
142
143
|
},
|
|
143
144
|
}),
|
|
144
145
|
);
|
|
@@ -53,13 +53,16 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
53
53
|
expect(byId(neutral, 'provider.resolution').detail?.join(' ')).not.toMatch(/robota/i);
|
|
54
54
|
expect(byId(neutral, 'workspace.trust').detail?.join(' ')).not.toMatch(/robota/i);
|
|
55
55
|
|
|
56
|
-
const hosted = await runDoctor(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const hosted = await runDoctor(
|
|
57
|
+
{
|
|
58
|
+
...f.inputs,
|
|
59
|
+
diagnosticGuidance: {
|
|
60
|
+
providerResolution: 'Run atlas configure.',
|
|
61
|
+
projectTrust: 'Run atlas trust.',
|
|
62
|
+
},
|
|
61
63
|
},
|
|
62
|
-
|
|
64
|
+
f.deps,
|
|
65
|
+
);
|
|
63
66
|
expect(byId(hosted, 'provider.resolution').detail).toEqual(['Run atlas configure.']);
|
|
64
67
|
expect(byId(hosted, 'workspace.trust').detail).toEqual(['Run atlas trust.']);
|
|
65
68
|
});
|
|
@@ -143,6 +146,9 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
143
146
|
const ghost = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ghost');
|
|
144
147
|
expect(ghost).toMatchObject({ status: 'warn' });
|
|
145
148
|
expect(ghost.cause).toContain('robota-doctor-missing-binary');
|
|
149
|
+
const declaration = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ready');
|
|
150
|
+
expect(declaration.status).toBe('warn');
|
|
151
|
+
expect(declaration.detail?.join(' ')).toContain('not a runtime source');
|
|
146
152
|
expect(byId(report, 'mcp.activation').status).toBe('not-configured');
|
|
147
153
|
expect(byId(report, 'mcp.connection').status).toBe('not-probed');
|
|
148
154
|
expect(byId(report, 'hooks.execution').status).toBe('not-probed');
|
|
@@ -197,6 +203,20 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
197
203
|
expect(byId(report, 'settings.user.robota')).toMatchObject({ status: 'fail' });
|
|
198
204
|
});
|
|
199
205
|
|
|
206
|
+
it('an unparseable user settings file is a failed plugin check, not a crash', async () => {
|
|
207
|
+
const f = fixture({ env: {} });
|
|
208
|
+
f.mkdir('.robota');
|
|
209
|
+
f.mkdir('.robota/sessions');
|
|
210
|
+
f.write('.robota/settings.json', '{ "enabledPlugins": { "helper": false }, }');
|
|
211
|
+
|
|
212
|
+
const report = await runDoctor(f.inputs, f.deps);
|
|
213
|
+
|
|
214
|
+
expect(byId(report, 'plugins')).toMatchObject({
|
|
215
|
+
status: 'fail',
|
|
216
|
+
cause: expect.stringContaining('not valid JSON'),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
200
220
|
it('TC-04: derives the reachability host from profile baseURL, then defaults.baseURL, then endpoint', async () => {
|
|
201
221
|
const profile = fixture({ env: {} });
|
|
202
222
|
profile.write(
|
|
@@ -179,22 +179,29 @@ function hookChecks(
|
|
|
179
179
|
|
|
180
180
|
function mcpServerCheck(server: IBundlePluginMcpServer, deps: IDoctorDeps): IDoctorCheck {
|
|
181
181
|
const id = `mcp.plugin.${server.pluginId}.${server.name}`;
|
|
182
|
+
const detail = [
|
|
183
|
+
'Declaration only: plugin .mcp.json is not a runtime source. Copy a supported definition to mcpServers in settings, then use /mcp approve <name>.',
|
|
184
|
+
];
|
|
182
185
|
if (server.transport === 'stdio' && server.command !== undefined) {
|
|
183
186
|
return deps.resolveCommand(server.command)
|
|
184
187
|
? {
|
|
185
188
|
id,
|
|
186
|
-
label: 'MCP
|
|
187
|
-
status: '
|
|
189
|
+
label: 'MCP declaration (plugin)',
|
|
190
|
+
status: 'warn',
|
|
188
191
|
path: server.mcpPath,
|
|
189
192
|
cause: `stdio command ${server.command} resolves`,
|
|
193
|
+
detail,
|
|
190
194
|
}
|
|
191
195
|
: {
|
|
192
196
|
id,
|
|
193
|
-
label: 'MCP
|
|
197
|
+
label: 'MCP declaration (plugin)',
|
|
194
198
|
status: 'warn',
|
|
195
199
|
path: server.mcpPath,
|
|
196
200
|
cause: `stdio command ${server.command} not found on PATH`,
|
|
197
|
-
detail:
|
|
201
|
+
detail: [
|
|
202
|
+
...detail,
|
|
203
|
+
...(server.envKeys.length === 0 ? [] : [`env keys: ${server.envKeys.join(', ')}`]),
|
|
204
|
+
],
|
|
198
205
|
};
|
|
199
206
|
}
|
|
200
207
|
if (server.transport === 'http' && server.url !== undefined) {
|
|
@@ -202,28 +209,31 @@ function mcpServerCheck(server: IBundlePluginMcpServer, deps: IDoctorDeps): IDoc
|
|
|
202
209
|
new URL(server.url);
|
|
203
210
|
return {
|
|
204
211
|
id,
|
|
205
|
-
label: 'MCP
|
|
206
|
-
status: '
|
|
212
|
+
label: 'MCP declaration (plugin)',
|
|
213
|
+
status: 'warn',
|
|
207
214
|
path: server.mcpPath,
|
|
208
215
|
cause: `url ${server.url}`,
|
|
216
|
+
detail,
|
|
209
217
|
};
|
|
210
218
|
} catch {
|
|
211
219
|
// allow-fallback: an unparseable url is the finding
|
|
212
220
|
return {
|
|
213
221
|
id,
|
|
214
|
-
label: 'MCP
|
|
222
|
+
label: 'MCP declaration (plugin)',
|
|
215
223
|
status: 'warn',
|
|
216
224
|
path: server.mcpPath,
|
|
217
225
|
cause: 'url is not parseable',
|
|
226
|
+
detail,
|
|
218
227
|
};
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
230
|
return {
|
|
222
231
|
id,
|
|
223
|
-
label: 'MCP
|
|
232
|
+
label: 'MCP declaration (plugin)',
|
|
224
233
|
status: 'warn',
|
|
225
234
|
path: server.mcpPath,
|
|
226
235
|
cause: 'neither command nor url declared',
|
|
236
|
+
detail,
|
|
227
237
|
};
|
|
228
238
|
}
|
|
229
239
|
|
|
@@ -287,11 +297,25 @@ export function probeExtensions(
|
|
|
287
297
|
// Enablement is read the way the session loader reads it — from the user settings file through the
|
|
288
298
|
// plugin settings store — so the doctor and the session agree even where that store's own policy is
|
|
289
299
|
// a separate open item (recorded on #2670).
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
300
|
+
let plugins: IBundlePluginInspection[];
|
|
301
|
+
let settingsProblem: string | undefined;
|
|
302
|
+
try {
|
|
303
|
+
plugins = loadHostBundlePluginInspectionFromScopes(inputs.pluginsDirs, {
|
|
304
|
+
settingsPath: inputs.userSettingsPath,
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
// Unreadable plugin settings load no plugin in a session. The doctor says so, and still
|
|
308
|
+
// inspects every installed plugin so its other problems are not hidden behind this one.
|
|
309
|
+
settingsProblem = error instanceof Error ? error.message : String(error);
|
|
310
|
+
plugins = loadHostBundlePluginInspectionFromScopes(inputs.pluginsDirs, { enabledPlugins: {} });
|
|
311
|
+
}
|
|
312
|
+
const pluginSummary = pluginChecks(plugins).map((check) =>
|
|
313
|
+
settingsProblem !== undefined && check.id === 'plugins'
|
|
314
|
+
? { ...check, status: 'fail' as const, cause: `no plugin loads: ${settingsProblem}` }
|
|
315
|
+
: check,
|
|
316
|
+
);
|
|
293
317
|
return [
|
|
294
|
-
...
|
|
318
|
+
...pluginSummary,
|
|
295
319
|
...skillChecks(inputs),
|
|
296
320
|
...hookChecks(settings, plugins, deps),
|
|
297
321
|
...mcpChecks(inputs, plugins, deps),
|
|
@@ -5,6 +5,37 @@ import { executeEffortCommand } from './effort-command.js';
|
|
|
5
5
|
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
6
6
|
|
|
7
7
|
describe('/effort', () => {
|
|
8
|
+
it('#3282 §2: reports plainly when the model has no matching effort control at all', async () => {
|
|
9
|
+
const host = createTestCommandHost({
|
|
10
|
+
overrides: {
|
|
11
|
+
getCommandHostAdapters: () => ({
|
|
12
|
+
settings: { read: () => ({}), write: vi.fn() },
|
|
13
|
+
effort: {
|
|
14
|
+
getResolution: () => ({
|
|
15
|
+
requested: 'low',
|
|
16
|
+
effective: 'low',
|
|
17
|
+
source: 'command',
|
|
18
|
+
disposition: 'not-applied',
|
|
19
|
+
modelDefault: 'high',
|
|
20
|
+
}),
|
|
21
|
+
apply: async () => ({
|
|
22
|
+
requested: 'low',
|
|
23
|
+
effective: 'low',
|
|
24
|
+
source: 'command',
|
|
25
|
+
disposition: 'not-applied',
|
|
26
|
+
modelDefault: 'high',
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await executeEffortCommand(host, 'low');
|
|
34
|
+
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
expect(result.message).toBe("This model doesn't support effort levels, so it will use its default.");
|
|
37
|
+
});
|
|
38
|
+
|
|
8
39
|
it('applies a requested level and returns the complete resolution', async () => {
|
|
9
40
|
const applyModelOptions = vi.fn();
|
|
10
41
|
const writeSettings = vi.fn();
|
|
@@ -36,6 +67,7 @@ describe('/effort', () => {
|
|
|
36
67
|
const result = await executeEffortCommand(host, 'low');
|
|
37
68
|
|
|
38
69
|
expect(result.success).toBe(true);
|
|
70
|
+
expect(result.message).toBe('Effort: Low');
|
|
39
71
|
expect(result.data).toMatchObject({
|
|
40
72
|
effort: { requested: 'low', effective: 'low', source: 'command', disposition: 'applied' },
|
|
41
73
|
});
|
|
@@ -50,7 +82,7 @@ describe('/effort', () => {
|
|
|
50
82
|
const result = await executeEffortCommand(host, '');
|
|
51
83
|
|
|
52
84
|
expect(result.success).toBe(true);
|
|
53
|
-
expect(result.message).
|
|
85
|
+
expect(result.message).toBe('Effort: High');
|
|
54
86
|
expect(applyModelOptions).not.toHaveBeenCalled();
|
|
55
87
|
});
|
|
56
88
|
|
|
@@ -133,8 +165,39 @@ describe('/effort', () => {
|
|
|
133
165
|
const result = await executeEffortCommand(host, '');
|
|
134
166
|
|
|
135
167
|
expect(result.success).toBe(true);
|
|
136
|
-
expect(result.message).
|
|
168
|
+
expect(result.message).toBe('Effort: Medium');
|
|
137
169
|
expect(applyModelOptions).not.toHaveBeenCalled();
|
|
138
170
|
expect(writeSettings).not.toHaveBeenCalled();
|
|
139
171
|
});
|
|
172
|
+
|
|
173
|
+
it('#3282 §2: uses the full plain word for xhigh, not the internal id', async () => {
|
|
174
|
+
const host = createTestCommandHost({
|
|
175
|
+
overrides: {
|
|
176
|
+
getCommandHostAdapters: () => ({
|
|
177
|
+
settings: { read: () => ({}), write: vi.fn() },
|
|
178
|
+
effort: {
|
|
179
|
+
getResolution: () => ({
|
|
180
|
+
requested: 'auto',
|
|
181
|
+
effective: 'high',
|
|
182
|
+
source: 'model-default',
|
|
183
|
+
disposition: 'model-default',
|
|
184
|
+
modelDefault: 'high',
|
|
185
|
+
}),
|
|
186
|
+
apply: async (selection) => ({
|
|
187
|
+
requested: selection,
|
|
188
|
+
effective: selection === 'auto' ? 'high' : selection,
|
|
189
|
+
source: 'command',
|
|
190
|
+
disposition: 'applied',
|
|
191
|
+
modelDefault: 'high',
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const result = await executeEffortCommand(host, 'xhigh');
|
|
199
|
+
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
expect(result.message).toBe('Effort: Extra high');
|
|
202
|
+
});
|
|
140
203
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MODEL_EFFORT_VALUES, selectAction } from '@robota-sdk/agent-core';
|
|
2
|
-
import { parseModelEffort, resolveModelEffort } from '@robota-sdk/agent-framework';
|
|
2
|
+
import { EFFORT_LEVEL_LABELS, parseModelEffort, resolveModelEffort } from '@robota-sdk/agent-framework';
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
ICommandHostAdapterAccess,
|
|
@@ -14,13 +14,27 @@ import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
|
14
14
|
|
|
15
15
|
const EFFORT_SELECTIONS: readonly TEffortSelection[] = ['auto', ...MODEL_EFFORT_VALUES];
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* #3282 §2 — re-exported for callers already importing it from here; the canonical definition moved
|
|
19
|
+
* to `agent-framework` (`effort/effort-resolution.ts`) so headless `/goal` output (in that same
|
|
20
|
+
* package) can use the same labels without a circular dependency on `agent-command`.
|
|
21
|
+
*/
|
|
22
|
+
export { EFFORT_LEVEL_LABELS };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Plain outcome text (#3282 §2), replacing the earlier `Model effort: requested=…, effective=…,
|
|
26
|
+
* source=…, disposition=…` internal-field dump. `not-applied` means the model has no matching native
|
|
27
|
+
* control at all — the earlier fields carried no information a person could act on for that case, so
|
|
28
|
+
* it gets its own plain sentence instead of a label.
|
|
29
|
+
*/
|
|
17
30
|
function formatEffortMessage(resolution: {
|
|
18
31
|
requested: TEffortSelection;
|
|
19
|
-
effective: string;
|
|
20
|
-
source: string;
|
|
21
32
|
disposition: string;
|
|
22
33
|
}): string {
|
|
23
|
-
|
|
34
|
+
if (resolution.disposition === 'not-applied') {
|
|
35
|
+
return "This model doesn't support effort levels, so it will use its default.";
|
|
36
|
+
}
|
|
37
|
+
return `Effort: ${EFFORT_LEVEL_LABELS[resolution.requested]}`;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
async function askForEffort(
|
|
@@ -32,7 +46,7 @@ async function askForEffort(
|
|
|
32
46
|
selectAction(
|
|
33
47
|
'effort',
|
|
34
48
|
'Select model effort',
|
|
35
|
-
EFFORT_SELECTIONS.map((value) => ({ value, label: value })),
|
|
49
|
+
EFFORT_SELECTIONS.map((value) => ({ value, label: EFFORT_LEVEL_LABELS[value] })),
|
|
36
50
|
),
|
|
37
51
|
);
|
|
38
52
|
const selected = response.type === 'answer' ? response.values[0] : undefined;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A scripted `IGitProcessPort`: answers by argv, records every call, throws on an unscripted one.
|
|
3
|
+
*
|
|
4
|
+
* The port and outcome types moved to `@robota-sdk/agent-framework` with the git process module
|
|
5
|
+
* (#3282 §4c); this double stays here (duplicated, not imported cross-package) because it is a tiny,
|
|
6
|
+
* self-contained test fixture, the same call `agent-framework`'s own `git/__tests__/fake-git-port.ts`
|
|
7
|
+
* makes for its side.
|
|
8
|
+
*/
|
|
9
|
+
import type { IGitProcessPort, TGitProcessOutcome } from '@robota-sdk/agent-framework';
|
|
3
10
|
|
|
4
11
|
export interface IFakeGitPort extends IGitProcessPort {
|
|
5
12
|
readonly calls: readonly (readonly string[])[];
|
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
* on the user path (`SystemCommandExecutor.executeCommand` calls `execute` directly).
|
|
6
6
|
*/
|
|
7
7
|
import { executeGitCommit } from './git-commit.js';
|
|
8
|
-
import { executeGitDiff } from './git-diff.js';
|
|
9
|
-
import { createGitProcess } from './git-process.js';
|
|
10
|
-
import { executeGitStatus } from './git-status.js';
|
|
11
8
|
|
|
12
|
-
import
|
|
9
|
+
import { createGitProcess, executeGitDiff, executeGitStatus } from '@robota-sdk/agent-framework';
|
|
10
|
+
|
|
13
11
|
import type {
|
|
14
12
|
ICommandHostUserInteraction,
|
|
15
13
|
ICommandHostWorkspace,
|
|
16
14
|
ICommandModule,
|
|
15
|
+
IGitProcessPort,
|
|
17
16
|
ISystemCommand,
|
|
18
17
|
} from '@robota-sdk/agent-framework';
|
|
19
18
|
import type { ICommand, ICommandResult, ICommandSource } from '@robota-sdk/agent-interface-command';
|
package/src/git/git-commit.ts
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { confirmAction, isConfirmed, textAction } from '@robota-sdk/agent-core';
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
gitFailureMessage,
|
|
14
|
+
parseStatusPorcelainV2,
|
|
15
|
+
GIT_STATUS_ARGS,
|
|
16
|
+
} from '@robota-sdk/agent-framework';
|
|
14
17
|
|
|
15
|
-
import type { IGitProcessPort } from '
|
|
18
|
+
import type { IGitProcessPort } from '@robota-sdk/agent-framework';
|
|
16
19
|
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
17
20
|
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
18
21
|
|
package/src/git/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// The git process port and the status/diff readers moved to `@robota-sdk/agent-framework` (#3282
|
|
2
|
+
// §4c) so the Project panel's session-level reads and this command run through the exact same
|
|
3
|
+
// functions; re-exported here so an existing `from '@robota-sdk/agent-command'` import keeps working.
|
|
1
4
|
export {
|
|
2
5
|
createGitProcess,
|
|
3
6
|
gitEnvironment,
|
|
@@ -6,7 +9,7 @@ export {
|
|
|
6
9
|
type IGitProcessRunOptions,
|
|
7
10
|
type TGitProcessFailureReason,
|
|
8
11
|
type TGitProcessOutcome,
|
|
9
|
-
} from '
|
|
12
|
+
} from '@robota-sdk/agent-framework';
|
|
10
13
|
export {
|
|
11
14
|
createGitCommandEntry,
|
|
12
15
|
createGitCommandModule,
|
package/src/goal/goal-command.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* command host context; all loop logic lives in agent-framework.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { isGoalCancelVerb } from '@robota-sdk/agent-framework';
|
|
8
|
+
|
|
7
9
|
import type { ICommandHostGoal } from '@robota-sdk/agent-framework';
|
|
8
10
|
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
9
11
|
import type { IGoalState } from '@robota-sdk/agent-interface-session';
|
|
@@ -45,7 +47,7 @@ export async function executeGoalCommand(
|
|
|
45
47
|
: { message: 'No goal is set.', success: true };
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
if (
|
|
50
|
+
if (isGoalCancelVerb(trimmed)) {
|
|
49
51
|
const stopped = context.cancelGoal();
|
|
50
52
|
return stopped
|
|
51
53
|
? { message: `Goal cancelled: ${stopped.objective}`, success: true }
|
package/src/index.ts
CHANGED
|
@@ -123,6 +123,12 @@ export {
|
|
|
123
123
|
} from './mcp-activation/mcp-model-notice.js';
|
|
124
124
|
export type { TMCPUserAction, TMCPUserActionSurface } from './mcp-activation/mcp-model-notice.js';
|
|
125
125
|
export * from './mode/index.js';
|
|
126
|
+
export {
|
|
127
|
+
createModelCommandEntry,
|
|
128
|
+
createModelCommandModule,
|
|
129
|
+
ModelCommandSource,
|
|
130
|
+
} from './model/model-command-module.js';
|
|
131
|
+
export { executeModelCommand } from './model/model-command.js';
|
|
126
132
|
export {
|
|
127
133
|
createSandboxCommandModule,
|
|
128
134
|
executeSandboxCommand,
|