@robota-sdk/agent-command 3.0.0-beta.100
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 +1351 -0
- package/LICENSE +661 -0
- package/README.md +100 -0
- package/dist/node/index.cjs +121 -0
- package/dist/node/index.d.cts +1053 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1053 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +122 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +83 -0
- 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 +597 -0
- package/src/agent/agent-command-module.ts +89 -0
- package/src/agent/agent-command-parser.ts +180 -0
- package/src/agent/agent-command.ts +255 -0
- package/src/agent/index.ts +7 -0
- package/src/background/__tests__/background-command-module.test.ts +261 -0
- package/src/background/background-command-module.ts +52 -0
- package/src/background/background-command.ts +65 -0
- package/src/background/index.ts +6 -0
- package/src/command-module-utils.test.ts +37 -0
- package/src/command-module-utils.ts +35 -0
- package/src/compact/__tests__/compact-command-module.test.ts +181 -0
- package/src/compact/compact-command-module.ts +59 -0
- package/src/compact/compact-command.ts +33 -0
- package/src/compact/index.ts +6 -0
- package/src/context/__tests__/context-command-module.test.ts +502 -0
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +82 -0
- package/src/context/context-command.ts +291 -0
- package/src/context/index.ts +6 -0
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
- package/src/default/__tests__/default-command-modules.test.ts +192 -0
- package/src/default/__tests__/model-exposure.test.ts +269 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
- package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +247 -0
- package/src/default/index.ts +5 -0
- package/src/devices/__tests__/devices-command-module.test.ts +299 -0
- package/src/devices/devices-command-module.ts +308 -0
- package/src/devices/devices-command-port.ts +143 -0
- package/src/devices/index.ts +14 -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 +146 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +323 -0
- package/src/doctor/doctor-node-deps.ts +88 -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 +124 -0
- package/src/editor/editor-command-module.ts +53 -0
- package/src/editor/editor-command.ts +58 -0
- package/src/editor/index.ts +7 -0
- package/src/editor/resolve-editor.ts +21 -0
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +203 -0
- package/src/effort/effort-command.ts +136 -0
- package/src/effort/index.ts +6 -0
- package/src/events/__tests__/events-command.test.ts +73 -0
- package/src/events/events-command-module.ts +49 -0
- package/src/events/events-command.ts +69 -0
- package/src/events/index.ts +2 -0
- package/src/exit/__tests__/exit-command-module.test.ts +61 -0
- package/src/exit/exit-command-module.ts +47 -0
- package/src/exit/exit-command.ts +26 -0
- package/src/exit/index.ts +6 -0
- 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 +35 -0
- package/src/git/__tests__/git-command-module.test.ts +111 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/git-command-module.ts +138 -0
- package/src/git/git-commit.ts +254 -0
- package/src/git/index.ts +20 -0
- package/src/goal/__tests__/goal-command.test.ts +80 -0
- package/src/goal/goal-command-module.ts +49 -0
- package/src/goal/goal-command.ts +69 -0
- package/src/goal/index.ts +6 -0
- package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +49 -0
- package/src/handoff/handoff-command.ts +187 -0
- package/src/handoff/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +134 -0
- package/src/help/__tests__/help-command.test.ts +130 -0
- package/src/help/help-command-module.ts +47 -0
- package/src/help/help-command.ts +11 -0
- package/src/help/index.ts +6 -0
- package/src/index.ts +163 -0
- 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 +88 -0
- package/src/language/__tests__/language-command-module.test.ts +126 -0
- package/src/language/index.ts +6 -0
- package/src/language/language-command-module.ts +55 -0
- package/src/language/language-command.ts +52 -0
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
- package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
- package/src/mcp-activation/mcp-activation-command.ts +662 -0
- package/src/mcp-activation/mcp-model-notice.ts +99 -0
- package/src/mcp-activation/mcp-model-status.ts +104 -0
- package/src/memory/__tests__/memory-command-module.test.ts +476 -0
- package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
- package/src/memory/index.ts +6 -0
- package/src/memory/memory-command-module.ts +77 -0
- package/src/memory/memory-command.ts +326 -0
- package/src/mode/__tests__/mode-command-module.test.ts +230 -0
- package/src/mode/index.ts +6 -0
- package/src/mode/mode-command-module.ts +55 -0
- package/src/mode/mode-command.ts +67 -0
- package/src/model/__tests__/model-command-module.test.ts +80 -0
- package/src/model/__tests__/model-command.test.ts +265 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +56 -0
- package/src/model/model-command.ts +249 -0
- 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-module.test.ts +24 -0
- package/src/peers/__tests__/peers-command.test.ts +399 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +49 -0
- package/src/peers/peers-command.ts +220 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
- package/src/permissions/index.ts +6 -0
- package/src/permissions/permissions-command-module.ts +55 -0
- package/src/permissions/permissions-command.ts +89 -0
- 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-active-turn.test.ts +100 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/plugin-command-module.ts +85 -0
- package/src/plugin/plugin-command.ts +271 -0
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +220 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/preset-command-module.ts +80 -0
- package/src/preset/preset-command.ts +148 -0
- package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
- package/src/provider/__tests__/org-policy.test.ts +174 -0
- package/src/provider/__tests__/provider-command-module.test.ts +884 -0
- package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
- package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
- package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +40 -0
- package/src/provider/index.ts +32 -0
- package/src/provider/provider-command-execution.ts +235 -0
- package/src/provider/provider-command-module.ts +82 -0
- package/src/provider/provider-command-profile-lifecycle.ts +239 -0
- package/src/provider/provider-command-profile-operations.ts +218 -0
- package/src/provider/provider-command-profile.ts +116 -0
- package/src/provider/provider-command-setup.ts +79 -0
- package/src/provider/provider-connection-setup.ts +287 -0
- package/src/provider/provider-connection-validation.ts +38 -0
- package/src/provider/provider-onboarding.ts +73 -0
- package/src/provider/provider-prompt-interaction.ts +28 -0
- package/src/provider/provider-setup-ask.ts +67 -0
- package/src/provider/provider-setup-flow.ts +326 -0
- package/src/provider/provider-startup.ts +225 -0
- 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 +63 -0
- package/src/reset/index.ts +2 -0
- package/src/reset/reset-command-module.ts +47 -0
- package/src/reset/reset-command.ts +10 -0
- package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
- package/src/rewind/index.ts +2 -0
- package/src/rewind/rewind-command-module.ts +56 -0
- package/src/rewind/rewind-command.ts +279 -0
- 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 +399 -0
- package/src/schedule/__tests__/loop-command.test.ts +594 -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 +247 -0
- package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
- package/src/schedule/index.ts +12 -0
- package/src/schedule/loop-command.ts +427 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +184 -0
- package/src/schedule/schedule-command.ts +161 -0
- package/src/schedule/schedule-spec-parser.ts +70 -0
- package/src/session/__tests__/model-pricing.test.ts +66 -0
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +589 -0
- package/src/session/index.ts +17 -0
- package/src/session/model-pricing.ts +26 -0
- package/src/session/session-command-module.ts +197 -0
- package/src/session/session-command.ts +272 -0
- package/src/settings/index.ts +7 -0
- package/src/settings/settings-command-module.ts +47 -0
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
- package/src/shell/index.ts +8 -0
- package/src/shell/resolve-shell.ts +25 -0
- package/src/shell/shell-command-module.ts +53 -0
- package/src/shell/shell-command.ts +48 -0
- package/src/shell/spawn-inherited.ts +32 -0
- package/src/skills/__tests__/skill-read-command.test.ts +64 -0
- package/src/skills/__tests__/skills-command-module.test.ts +200 -0
- package/src/skills/index.ts +7 -0
- package/src/skills/skill-read-command.ts +60 -0
- package/src/skills/skills-command-module.ts +76 -0
- package/src/skills/skills-command.ts +108 -0
- package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
- package/src/statusline/index.ts +6 -0
- package/src/statusline/statusline-command-module.ts +55 -0
- package/src/statusline/statusline-command.ts +80 -0
- package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
- package/src/terminal-client/index.ts +5 -0
- package/src/terminal-client/terminal-client-commands.ts +109 -0
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +221 -0
- package/src/user-local/__tests__/user-local-command.test.ts +180 -0
- package/src/user-local/index.ts +13 -0
- package/src/user-local/user-local-command-constants.ts +5 -0
- package/src/user-local/user-local-command-module.ts +65 -0
- package/src/user-local/user-local-command.ts +214 -0
- package/src/user-local/user-local-memory-command.ts +160 -0
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@robota-sdk/agent-command",
|
|
3
|
+
"version": "3.0.0-beta.100",
|
|
4
|
+
"description": "Slash-command modules for Robota SDK sessions (/help, /permissions, /mcp, /rewind, …)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/node/index.js",
|
|
7
|
+
"types": "dist/node/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"source": "./src/index.ts",
|
|
11
|
+
"node": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/node/index.d.ts",
|
|
14
|
+
"default": "./dist/node/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/node/index.d.cts",
|
|
18
|
+
"default": "./dist/node/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"default": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/node/index.d.ts",
|
|
24
|
+
"default": "./dist/node/index.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/node/index.d.cts",
|
|
28
|
+
"default": "./dist/node/index.cjs"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22.12.0"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@robota-sdk/agent-core": "3.0.0-beta.100",
|
|
44
|
+
"@robota-sdk/agent-framework": "3.0.0-beta.100",
|
|
45
|
+
"@robota-sdk/agent-interface-command": "3.0.0-beta.100",
|
|
46
|
+
"@robota-sdk/agent-interface-execution": "3.0.0-beta.100",
|
|
47
|
+
"@robota-sdk/agent-interface-session": "3.0.0-beta.100",
|
|
48
|
+
"@robota-sdk/agent-preset": "3.0.0-beta.100"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.19.21",
|
|
52
|
+
"rimraf": "^5.0.10",
|
|
53
|
+
"tsdown": "^0.22.14",
|
|
54
|
+
"typescript": "^6.0.3",
|
|
55
|
+
"vitest": "^3.2.6"
|
|
56
|
+
},
|
|
57
|
+
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"volta": {
|
|
62
|
+
"extends": "../../package.json"
|
|
63
|
+
},
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/woojubb/robota"
|
|
67
|
+
},
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/woojubb/robota/issues"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://robota.io/",
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "rimraf dist && tsdown",
|
|
74
|
+
"build:js": "pnpm run build",
|
|
75
|
+
"build:types": "pnpm run build",
|
|
76
|
+
"typecheck": "tsgo --noEmit && tsgo -p tsconfig.examples.json --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:coverage": "vitest run --coverage",
|
|
79
|
+
"scenario:verify": "pnpm scenario:verify:semantic-command-roles",
|
|
80
|
+
"scenario:verify:semantic-command-roles": "pnpm exec tsx --conditions=source examples/verify-semantic-command-roles.ts",
|
|
81
|
+
"clean": "rimraf dist"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { executeAdvisorCommand } from './advisor-command.js';
|
|
2
|
+
|
|
3
|
+
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
4
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
5
|
+
|
|
6
|
+
export function createAdvisorCommandEntry(): ICommand {
|
|
7
|
+
return {
|
|
8
|
+
name: 'advisor',
|
|
9
|
+
displayName: 'Advisor',
|
|
10
|
+
description: 'Show, set, or turn off the model the main model can consult for advice',
|
|
11
|
+
source: 'advisor',
|
|
12
|
+
argumentHint: '<profile> | <profile>:<model> | off',
|
|
13
|
+
// User-only: choosing a second model changes cost and what sees the conversation; the user decides.
|
|
14
|
+
modelInvocable: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createAdvisorSystemCommand(): ISystemCommand {
|
|
19
|
+
const entry = createAdvisorCommandEntry();
|
|
20
|
+
return {
|
|
21
|
+
name: entry.name,
|
|
22
|
+
displayName: entry.displayName,
|
|
23
|
+
description: entry.description,
|
|
24
|
+
requiresPermission: false,
|
|
25
|
+
userInvocable: true,
|
|
26
|
+
modelInvocable: false,
|
|
27
|
+
argumentHint: entry.argumentHint,
|
|
28
|
+
lifecycle: 'inline',
|
|
29
|
+
execute: executeAdvisorCommand,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AdvisorCommandSource implements ICommandSource {
|
|
34
|
+
readonly name = 'advisor';
|
|
35
|
+
|
|
36
|
+
getCommands(): ICommand[] {
|
|
37
|
+
return [createAdvisorCommandEntry()];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createAdvisorCommandModule(): ICommandModule {
|
|
42
|
+
return {
|
|
43
|
+
name: 'agent-command-advisor',
|
|
44
|
+
commandSources: [new AdvisorCommandSource()],
|
|
45
|
+
systemCommands: [createAdvisorSystemCommand()],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { AdvisorController, createAdvisorTool } from '@robota-sdk/agent-framework';
|
|
4
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
5
|
+
|
|
6
|
+
import { executeAdvisorCommand } from './advisor-command.js';
|
|
7
|
+
|
|
8
|
+
import type { IAIProvider } from '@robota-sdk/agent-core';
|
|
9
|
+
import type { ICommandSettingsDocument } from '@robota-sdk/agent-framework';
|
|
10
|
+
|
|
11
|
+
function advisorController(options: {
|
|
12
|
+
registered: boolean;
|
|
13
|
+
allowed?: string[];
|
|
14
|
+
}): AdvisorController {
|
|
15
|
+
return new AdvisorController({
|
|
16
|
+
...(options.registered ? { spec: { profile: 'strong' } } : {}),
|
|
17
|
+
resolveTarget: (spec) => {
|
|
18
|
+
if (spec.profile === 'missing') throw new Error('Provider profile "missing" not found.');
|
|
19
|
+
return {
|
|
20
|
+
provider: {} as IAIProvider,
|
|
21
|
+
model: `${spec.profile}-model`,
|
|
22
|
+
destination: 'vendor-a@default',
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
consent: { has: () => true, grant: () => undefined },
|
|
26
|
+
...(options.allowed ? { allowedProfiles: options.allowed } : {}),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function host(advisor: AdvisorController | undefined, initial: ICommandSettingsDocument = {}) {
|
|
31
|
+
let document: ICommandSettingsDocument = { ...initial };
|
|
32
|
+
const write = vi.fn((next: ICommandSettingsDocument) => {
|
|
33
|
+
document = next;
|
|
34
|
+
});
|
|
35
|
+
const context = createTestCommandHost({
|
|
36
|
+
overrides: {
|
|
37
|
+
getCommandHostAdapters: () => ({
|
|
38
|
+
...(advisor ? { advisor } : {}),
|
|
39
|
+
settings: { read: () => document, write },
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return { context, write, read: () => document };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('/advisor', () => {
|
|
47
|
+
it('saves the default and retargets the live advisor without changing the tool', async () => {
|
|
48
|
+
const advisor = advisorController({ registered: true });
|
|
49
|
+
const schema = JSON.stringify(createAdvisorTool(advisor).schema);
|
|
50
|
+
const { context, read } = host(advisor, { theme: 'dark' });
|
|
51
|
+
|
|
52
|
+
const result = await executeAdvisorCommand(context, 'other:big');
|
|
53
|
+
|
|
54
|
+
expect(result.success).toBe(true);
|
|
55
|
+
expect(read()).toEqual({ theme: 'dark', advisorModel: 'other:big' });
|
|
56
|
+
expect(advisor.status()).toMatchObject({ target: 'other:big', enabled: true });
|
|
57
|
+
expect(JSON.stringify(createAdvisorTool(advisor).schema)).toBe(schema);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('turns the advisor off and removes the saved default', async () => {
|
|
61
|
+
const advisor = advisorController({ registered: true });
|
|
62
|
+
const { context, read } = host(advisor, { advisorModel: 'strong', theme: 'dark' });
|
|
63
|
+
|
|
64
|
+
const result = await executeAdvisorCommand(context, 'off');
|
|
65
|
+
|
|
66
|
+
expect(result.success).toBe(true);
|
|
67
|
+
expect(read()).toEqual({ theme: 'dark' });
|
|
68
|
+
expect(advisor.status().enabled).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('says a new advisor takes effect next session when this one started without the tool', async () => {
|
|
72
|
+
const { context } = host(advisorController({ registered: false }));
|
|
73
|
+
const result = await executeAdvisorCommand(context, 'strong');
|
|
74
|
+
expect(result.message).toContain('next session');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('refuses a profile outside the organization allowlist and saves nothing', async () => {
|
|
78
|
+
const { context, write } = host(advisorController({ registered: true, allowed: ['strong'] }));
|
|
79
|
+
const result = await executeAdvisorCommand(context, 'elsewhere');
|
|
80
|
+
expect(result.success).toBe(false);
|
|
81
|
+
expect(result.message).toContain('organization policy');
|
|
82
|
+
expect(write).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('refuses a profile that does not exist and saves nothing', async () => {
|
|
86
|
+
const { context, write } = host(advisorController({ registered: true }));
|
|
87
|
+
const result = await executeAdvisorCommand(context, 'missing');
|
|
88
|
+
expect(result.success).toBe(false);
|
|
89
|
+
expect(write).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('reports the current advisor', async () => {
|
|
93
|
+
const { context } = host(advisorController({ registered: true }));
|
|
94
|
+
const result = await executeAdvisorCommand(context, '');
|
|
95
|
+
expect(result.message).toContain('Advisor: strong');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('fails explicitly on a host without an advisor', async () => {
|
|
99
|
+
const { context } = host(undefined);
|
|
100
|
+
expect((await executeAdvisorCommand(context, '')).success).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IAdvisorStatus,
|
|
3
|
+
ICommandHostAdapterAccess,
|
|
4
|
+
ICommandHostWorkspace,
|
|
5
|
+
} from '@robota-sdk/agent-framework';
|
|
6
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
7
|
+
|
|
8
|
+
/** The user-settings key `/advisor` saves the default advisor under. */
|
|
9
|
+
export const ADVISOR_SETTING_KEY = 'advisorModel';
|
|
10
|
+
|
|
11
|
+
function describeStatus(status: IAdvisorStatus): string {
|
|
12
|
+
if (status.killSwitch) return 'Advisor: disabled by the environment kill switch.';
|
|
13
|
+
if (status.target === undefined || !status.enabled) {
|
|
14
|
+
return 'Advisor: off. Set one with /advisor <profile> or /advisor <profile>:<model>.';
|
|
15
|
+
}
|
|
16
|
+
const availability = status.registered
|
|
17
|
+
? `${status.sessionCalls}/${status.maxCallsPerSession} calls used this session`
|
|
18
|
+
: 'available from the next session';
|
|
19
|
+
return `Advisor: ${status.target} (${availability}).`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `/advisor` — show the advisor, set it (`<profile>` or `<profile>:<model>`), or turn it off. A
|
|
24
|
+
* change the user makes is also saved as the default for later sessions.
|
|
25
|
+
*/
|
|
26
|
+
export async function executeAdvisorCommand(
|
|
27
|
+
context: ICommandHostAdapterAccess & ICommandHostWorkspace,
|
|
28
|
+
args: string,
|
|
29
|
+
): Promise<ICommandResult> {
|
|
30
|
+
const adapters = context.getCommandHostAdapters?.() ?? {};
|
|
31
|
+
const advisor = adapters.advisor;
|
|
32
|
+
if (advisor === undefined) {
|
|
33
|
+
return { success: false, message: 'The advisor is not available in this environment.' };
|
|
34
|
+
}
|
|
35
|
+
const value = args.trim();
|
|
36
|
+
if (value.length === 0) {
|
|
37
|
+
const status = advisor.status();
|
|
38
|
+
return { success: true, message: describeStatus(status), data: { advisor: { ...status } } };
|
|
39
|
+
}
|
|
40
|
+
const result = advisor.set(value);
|
|
41
|
+
if (!result.success || result.saved === undefined) {
|
|
42
|
+
return { success: result.success, message: result.message };
|
|
43
|
+
}
|
|
44
|
+
const settings = adapters.settings;
|
|
45
|
+
if (settings !== undefined && context.getCommandInvocationSource() === 'user') {
|
|
46
|
+
const current = settings.read();
|
|
47
|
+
if (result.saved === 'off') {
|
|
48
|
+
const { [ADVISOR_SETTING_KEY]: _removed, ...rest } = current;
|
|
49
|
+
settings.write(rest);
|
|
50
|
+
} else {
|
|
51
|
+
settings.write({ ...current, [ADVISOR_SETTING_KEY]: result.saved });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { success: true, message: result.message, data: { advisor: { ...advisor.status() } } };
|
|
55
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { calculateModelCost } from '@robota-sdk/agent-core';
|
|
4
|
+
import { AdvisorController, createAdvisorTool } from '@robota-sdk/agent-framework';
|
|
5
|
+
import { scriptedSession, type ScriptedSessionHarness } from '@robota-sdk/agent-framework/testing';
|
|
6
|
+
|
|
7
|
+
import { createSessionCommandModule } from '../session/session-command-module.js';
|
|
8
|
+
|
|
9
|
+
import type { IAIProvider } from '@robota-sdk/agent-core';
|
|
10
|
+
|
|
11
|
+
const ADVISOR_MODEL = 'claude-opus-4-6';
|
|
12
|
+
const MAIN_MODEL = 'claude-haiku-4-5';
|
|
13
|
+
|
|
14
|
+
function advisorProvider(): IAIProvider {
|
|
15
|
+
return {
|
|
16
|
+
name: 'advisor-vendor',
|
|
17
|
+
version: 'test',
|
|
18
|
+
chat: vi.fn(async () => ({
|
|
19
|
+
id: 'advice',
|
|
20
|
+
role: 'assistant' as const,
|
|
21
|
+
content: 'Run the tests before declaring done.',
|
|
22
|
+
state: 'complete' as const,
|
|
23
|
+
timestamp: new Date(),
|
|
24
|
+
metadata: { inputTokens: 3_000, outputTokens: 400 },
|
|
25
|
+
})),
|
|
26
|
+
} as unknown as IAIProvider;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('advisor usage in a real session', () => {
|
|
30
|
+
let harness: ScriptedSessionHarness | undefined;
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await harness?.dispose();
|
|
34
|
+
harness = undefined;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('/cost totals the turn and the advisor call, pricing the advisor on its model, and both persist', async () => {
|
|
38
|
+
const controller = new AdvisorController({
|
|
39
|
+
spec: { profile: 'strong' },
|
|
40
|
+
resolveTarget: () => ({
|
|
41
|
+
provider: advisorProvider(),
|
|
42
|
+
model: ADVISOR_MODEL,
|
|
43
|
+
destination: 'advisor-vendor@api.test',
|
|
44
|
+
}),
|
|
45
|
+
consent: { has: () => true, grant: () => undefined },
|
|
46
|
+
});
|
|
47
|
+
harness = scriptedSession({
|
|
48
|
+
turns: [
|
|
49
|
+
{
|
|
50
|
+
toolCalls: [{ name: 'Advisor', args: { question: 'done?' } }],
|
|
51
|
+
usage: { inputTokens: 1_000, outputTokens: 100 },
|
|
52
|
+
},
|
|
53
|
+
{ text: 'All done.', usage: { inputTokens: 1_200, outputTokens: 50 } },
|
|
54
|
+
],
|
|
55
|
+
model: MAIN_MODEL,
|
|
56
|
+
persistence: true,
|
|
57
|
+
additionalTools: [createAdvisorTool(controller)],
|
|
58
|
+
commandModules: [createSessionCommandModule()],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await harness.submit('finish the task');
|
|
62
|
+
const result = await harness.command('cost');
|
|
63
|
+
const data = result?.data as Record<string, number | string[] | undefined>;
|
|
64
|
+
|
|
65
|
+
const advisorCost = calculateModelCost(ADVISOR_MODEL, 3_000, 400)!;
|
|
66
|
+
const persisted = harness.sessionRecord()?.history ?? [];
|
|
67
|
+
const summaries = persisted
|
|
68
|
+
.filter((entry) => entry.type === 'usage-summary')
|
|
69
|
+
.map(
|
|
70
|
+
(entry) =>
|
|
71
|
+
entry.data as { promptTokens: number; completionTokens: number; source?: unknown },
|
|
72
|
+
);
|
|
73
|
+
const turn = summaries.find((summary) => summary.source === undefined);
|
|
74
|
+
expect(turn).toBeDefined();
|
|
75
|
+
expect(summaries).toHaveLength(2);
|
|
76
|
+
expect(data.inputTokens).toBe(turn!.promptTokens + 3_000);
|
|
77
|
+
expect(data.outputTokens).toBe(turn!.completionTokens + 400);
|
|
78
|
+
expect(data.estimatedCostUsd as number).toBeGreaterThan(advisorCost);
|
|
79
|
+
expect(result?.message).toContain(`Advisor (${ADVISOR_MODEL})`);
|
|
80
|
+
expect(result?.message).toContain('mixed');
|
|
81
|
+
|
|
82
|
+
const advisorObservation = persisted.find(
|
|
83
|
+
(entry) =>
|
|
84
|
+
entry.type === 'usage-observation' &&
|
|
85
|
+
(entry.data as { modelId?: string }).modelId === ADVISOR_MODEL,
|
|
86
|
+
);
|
|
87
|
+
expect(advisorObservation?.data).toMatchObject({
|
|
88
|
+
providerId: 'advisor-vendor',
|
|
89
|
+
usage: { promptTokens: 3_000, completionTokens: 400, costUsd: advisorCost },
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|