@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.
Files changed (251) hide show
  1. package/CHANGELOG.md +1351 -0
  2. package/LICENSE +661 -0
  3. package/README.md +100 -0
  4. package/dist/node/index.cjs +121 -0
  5. package/dist/node/index.d.cts +1053 -0
  6. package/dist/node/index.d.cts.map +1 -0
  7. package/dist/node/index.d.ts +1053 -0
  8. package/dist/node/index.d.ts.map +1 -0
  9. package/dist/node/index.js +122 -0
  10. package/dist/node/index.js.map +1 -0
  11. package/package.json +83 -0
  12. package/src/advisor/advisor-command-module.ts +47 -0
  13. package/src/advisor/advisor-command.test.ts +102 -0
  14. package/src/advisor/advisor-command.ts +55 -0
  15. package/src/advisor/advisor-cost-functional.test.ts +92 -0
  16. package/src/advisor/index.ts +6 -0
  17. package/src/agent/__tests__/agent-command.test.ts +597 -0
  18. package/src/agent/agent-command-module.ts +89 -0
  19. package/src/agent/agent-command-parser.ts +180 -0
  20. package/src/agent/agent-command.ts +255 -0
  21. package/src/agent/index.ts +7 -0
  22. package/src/background/__tests__/background-command-module.test.ts +261 -0
  23. package/src/background/background-command-module.ts +52 -0
  24. package/src/background/background-command.ts +65 -0
  25. package/src/background/index.ts +6 -0
  26. package/src/command-module-utils.test.ts +37 -0
  27. package/src/command-module-utils.ts +35 -0
  28. package/src/compact/__tests__/compact-command-module.test.ts +181 -0
  29. package/src/compact/compact-command-module.ts +59 -0
  30. package/src/compact/compact-command.ts +33 -0
  31. package/src/compact/index.ts +6 -0
  32. package/src/context/__tests__/context-command-module.test.ts +502 -0
  33. package/src/context/auto-compact-format.ts +25 -0
  34. package/src/context/context-breakdown.ts +295 -0
  35. package/src/context/context-command-module.ts +82 -0
  36. package/src/context/context-command.ts +291 -0
  37. package/src/context/index.ts +6 -0
  38. package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
  39. package/src/default/__tests__/default-command-modules.test.ts +192 -0
  40. package/src/default/__tests__/model-exposure.test.ts +269 -0
  41. package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
  42. package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
  43. package/src/default/default-command-modules.ts +247 -0
  44. package/src/default/index.ts +5 -0
  45. package/src/devices/__tests__/devices-command-module.test.ts +299 -0
  46. package/src/devices/devices-command-module.ts +308 -0
  47. package/src/devices/devices-command-port.ts +143 -0
  48. package/src/devices/index.ts +14 -0
  49. package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
  50. package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
  51. package/src/doctor/__tests__/doctor-fixture.ts +146 -0
  52. package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
  53. package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
  54. package/src/doctor/doctor-command-module.ts +132 -0
  55. package/src/doctor/doctor-extensions-probe.ts +323 -0
  56. package/src/doctor/doctor-node-deps.ts +88 -0
  57. package/src/doctor/doctor-redaction.ts +99 -0
  58. package/src/doctor/doctor-render.ts +70 -0
  59. package/src/doctor/doctor-repair.ts +136 -0
  60. package/src/doctor/doctor-runner.ts +122 -0
  61. package/src/doctor/doctor-settings-probe.ts +239 -0
  62. package/src/doctor/doctor-storage-probe.ts +187 -0
  63. package/src/doctor/doctor-types.ts +109 -0
  64. package/src/doctor/index.ts +32 -0
  65. package/src/editor/__tests__/editor-command-functional.test.ts +124 -0
  66. package/src/editor/editor-command-module.ts +53 -0
  67. package/src/editor/editor-command.ts +58 -0
  68. package/src/editor/index.ts +7 -0
  69. package/src/editor/resolve-editor.ts +21 -0
  70. package/src/effort/effort-command-module.ts +51 -0
  71. package/src/effort/effort-command.test.ts +203 -0
  72. package/src/effort/effort-command.ts +136 -0
  73. package/src/effort/index.ts +6 -0
  74. package/src/events/__tests__/events-command.test.ts +73 -0
  75. package/src/events/events-command-module.ts +49 -0
  76. package/src/events/events-command.ts +69 -0
  77. package/src/events/index.ts +2 -0
  78. package/src/exit/__tests__/exit-command-module.test.ts +61 -0
  79. package/src/exit/exit-command-module.ts +47 -0
  80. package/src/exit/exit-command.ts +26 -0
  81. package/src/exit/index.ts +6 -0
  82. package/src/fork/__tests__/fork-command.test.ts +184 -0
  83. package/src/fork/fork-command-module.ts +61 -0
  84. package/src/fork/fork-command.ts +162 -0
  85. package/src/fork/index.ts +8 -0
  86. package/src/git/__tests__/fake-git-port.ts +35 -0
  87. package/src/git/__tests__/git-command-module.test.ts +111 -0
  88. package/src/git/__tests__/git-commit.test.ts +185 -0
  89. package/src/git/git-command-module.ts +138 -0
  90. package/src/git/git-commit.ts +254 -0
  91. package/src/git/index.ts +20 -0
  92. package/src/goal/__tests__/goal-command.test.ts +80 -0
  93. package/src/goal/goal-command-module.ts +49 -0
  94. package/src/goal/goal-command.ts +69 -0
  95. package/src/goal/index.ts +6 -0
  96. package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
  97. package/src/handoff/__tests__/handoff-command.test.ts +218 -0
  98. package/src/handoff/handoff-command-module.ts +49 -0
  99. package/src/handoff/handoff-command.ts +187 -0
  100. package/src/handoff/index.ts +6 -0
  101. package/src/help/__tests__/help-command-module.test.ts +134 -0
  102. package/src/help/__tests__/help-command.test.ts +130 -0
  103. package/src/help/help-command-module.ts +47 -0
  104. package/src/help/help-command.ts +11 -0
  105. package/src/help/index.ts +6 -0
  106. package/src/index.ts +163 -0
  107. package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
  108. package/src/keybindings/index.ts +6 -0
  109. package/src/keybindings/keybindings-command-module.ts +88 -0
  110. package/src/language/__tests__/language-command-module.test.ts +126 -0
  111. package/src/language/index.ts +6 -0
  112. package/src/language/language-command-module.ts +55 -0
  113. package/src/language/language-command.ts +52 -0
  114. package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
  115. package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
  116. package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
  117. package/src/mcp-activation/index.ts +12 -0
  118. package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
  119. package/src/mcp-activation/mcp-activation-command.ts +662 -0
  120. package/src/mcp-activation/mcp-model-notice.ts +99 -0
  121. package/src/mcp-activation/mcp-model-status.ts +104 -0
  122. package/src/memory/__tests__/memory-command-module.test.ts +476 -0
  123. package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
  124. package/src/memory/index.ts +6 -0
  125. package/src/memory/memory-command-module.ts +77 -0
  126. package/src/memory/memory-command.ts +326 -0
  127. package/src/mode/__tests__/mode-command-module.test.ts +230 -0
  128. package/src/mode/index.ts +6 -0
  129. package/src/mode/mode-command-module.ts +55 -0
  130. package/src/mode/mode-command.ts +67 -0
  131. package/src/model/__tests__/model-command-module.test.ts +80 -0
  132. package/src/model/__tests__/model-command.test.ts +265 -0
  133. package/src/model/index.ts +6 -0
  134. package/src/model/model-command-module.ts +56 -0
  135. package/src/model/model-command.ts +249 -0
  136. package/src/output-style/__tests__/output-style-command.test.ts +96 -0
  137. package/src/output-style/index.ts +6 -0
  138. package/src/output-style/output-style-command-module.ts +143 -0
  139. package/src/peers/__tests__/peers-command-module.test.ts +24 -0
  140. package/src/peers/__tests__/peers-command.test.ts +399 -0
  141. package/src/peers/index.ts +9 -0
  142. package/src/peers/peers-command-module.ts +49 -0
  143. package/src/peers/peers-command.ts +220 -0
  144. package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
  145. package/src/permissions/index.ts +6 -0
  146. package/src/permissions/permissions-command-module.ts +55 -0
  147. package/src/permissions/permissions-command.ts +89 -0
  148. package/src/plan/__tests__/plan-command.test.ts +129 -0
  149. package/src/plan/index.ts +9 -0
  150. package/src/plan/plan-command-module.ts +49 -0
  151. package/src/plan/plan-command.ts +92 -0
  152. package/src/plugin/__tests__/plugin-active-turn.test.ts +100 -0
  153. package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
  154. package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
  155. package/src/plugin/index.ts +7 -0
  156. package/src/plugin/plugin-command-module.ts +85 -0
  157. package/src/plugin/plugin-command.ts +271 -0
  158. package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
  159. package/src/preset/__tests__/preset-command-module.test.ts +220 -0
  160. package/src/preset/index.ts +6 -0
  161. package/src/preset/preset-command-module.ts +80 -0
  162. package/src/preset/preset-command.ts +148 -0
  163. package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
  164. package/src/provider/__tests__/org-policy.test.ts +174 -0
  165. package/src/provider/__tests__/provider-command-module.test.ts +884 -0
  166. package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
  167. package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
  168. package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
  169. package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
  170. package/src/provider/__tests__/scripted-interaction.ts +40 -0
  171. package/src/provider/index.ts +32 -0
  172. package/src/provider/provider-command-execution.ts +235 -0
  173. package/src/provider/provider-command-module.ts +82 -0
  174. package/src/provider/provider-command-profile-lifecycle.ts +239 -0
  175. package/src/provider/provider-command-profile-operations.ts +218 -0
  176. package/src/provider/provider-command-profile.ts +116 -0
  177. package/src/provider/provider-command-setup.ts +79 -0
  178. package/src/provider/provider-connection-setup.ts +287 -0
  179. package/src/provider/provider-connection-validation.ts +38 -0
  180. package/src/provider/provider-onboarding.ts +73 -0
  181. package/src/provider/provider-prompt-interaction.ts +28 -0
  182. package/src/provider/provider-setup-ask.ts +67 -0
  183. package/src/provider/provider-setup-flow.ts +326 -0
  184. package/src/provider/provider-startup.ts +225 -0
  185. package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
  186. package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
  187. package/src/remote-control/index.ts +8 -0
  188. package/src/remote-control/remote-control-command-module.ts +61 -0
  189. package/src/remote-control/remote-control-command.ts +138 -0
  190. package/src/reset/__tests__/reset-command-module.test.ts +63 -0
  191. package/src/reset/index.ts +2 -0
  192. package/src/reset/reset-command-module.ts +47 -0
  193. package/src/reset/reset-command.ts +10 -0
  194. package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
  195. package/src/rewind/index.ts +2 -0
  196. package/src/rewind/rewind-command-module.ts +56 -0
  197. package/src/rewind/rewind-command.ts +279 -0
  198. package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
  199. package/src/sandbox/index.ts +2 -0
  200. package/src/sandbox/sandbox-command-module.ts +56 -0
  201. package/src/sandbox/sandbox-command.ts +94 -0
  202. package/src/schedule/__tests__/loop-command-functional.test.ts +399 -0
  203. package/src/schedule/__tests__/loop-command.test.ts +594 -0
  204. package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
  205. package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
  206. package/src/schedule/__tests__/schedule-command.test.ts +247 -0
  207. package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
  208. package/src/schedule/index.ts +12 -0
  209. package/src/schedule/loop-command.ts +427 -0
  210. package/src/schedule/loop-jitter.ts +41 -0
  211. package/src/schedule/schedule-command-module.ts +184 -0
  212. package/src/schedule/schedule-command.ts +161 -0
  213. package/src/schedule/schedule-spec-parser.ts +70 -0
  214. package/src/session/__tests__/model-pricing.test.ts +66 -0
  215. package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
  216. package/src/session/__tests__/session-command-module.test.ts +589 -0
  217. package/src/session/index.ts +17 -0
  218. package/src/session/model-pricing.ts +26 -0
  219. package/src/session/session-command-module.ts +197 -0
  220. package/src/session/session-command.ts +272 -0
  221. package/src/settings/index.ts +7 -0
  222. package/src/settings/settings-command-module.ts +47 -0
  223. package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
  224. package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
  225. package/src/shell/index.ts +8 -0
  226. package/src/shell/resolve-shell.ts +25 -0
  227. package/src/shell/shell-command-module.ts +53 -0
  228. package/src/shell/shell-command.ts +48 -0
  229. package/src/shell/spawn-inherited.ts +32 -0
  230. package/src/skills/__tests__/skill-read-command.test.ts +64 -0
  231. package/src/skills/__tests__/skills-command-module.test.ts +200 -0
  232. package/src/skills/index.ts +7 -0
  233. package/src/skills/skill-read-command.ts +60 -0
  234. package/src/skills/skills-command-module.ts +76 -0
  235. package/src/skills/skills-command.ts +108 -0
  236. package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
  237. package/src/statusline/index.ts +6 -0
  238. package/src/statusline/statusline-command-module.ts +55 -0
  239. package/src/statusline/statusline-command.ts +80 -0
  240. package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
  241. package/src/terminal-client/index.ts +5 -0
  242. package/src/terminal-client/terminal-client-commands.ts +109 -0
  243. package/src/theme/__tests__/theme-command.test.ts +204 -0
  244. package/src/theme/index.ts +12 -0
  245. package/src/theme/theme-command-module.ts +221 -0
  246. package/src/user-local/__tests__/user-local-command.test.ts +180 -0
  247. package/src/user-local/index.ts +13 -0
  248. package/src/user-local/user-local-command-constants.ts +5 -0
  249. package/src/user-local/user-local-command-module.ts +65 -0
  250. package/src/user-local/user-local-command.ts +214 -0
  251. 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
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ AdvisorCommandSource,
3
+ createAdvisorCommandEntry,
4
+ createAdvisorCommandModule,
5
+ } from './advisor-command-module.js';
6
+ export { ADVISOR_SETTING_KEY, executeAdvisorCommand } from './advisor-command.js';