@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
@@ -0,0 +1,121 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`built-in commands offered to the model > pins the descriptor each offered command gets 1`] = `
4
+ {
5
+ "agent": {
6
+ "argumentHint": "PROMPT | AGENT_NAME PROMPT | list | parallel [--wait|--detach] LABEL:"PROMPT" [LABEL=AGENT_NAME:"PROMPT"] | wait GROUP_ID | read AGENT_ID [OFFSET] | send AGENT_ID PROMPT | stop AGENT_ID | close AGENT_ID",
7
+ "description": "Start and manage background subagent jobs. Natural-language arguments start one background agent job. When the user explicitly asks to create, run, spawn, delegate to, or use agents/subagents, start the requested agent command immediately and do not ask a follow-up question unless execution is impossible or unsafe. If the target item is unspecified, include target selection inside the agent prompt instead of delaying execution. The parallel form starts multiple background agent jobs as a wait_all group and returns a consolidated group summary unless --detach is present. list, wait, read, send, stop, close, and open manage existing agent jobs. Returns the started job id (or group summary), the job list, or the requested log page.",
8
+ "kind": "builtin-command",
9
+ "modelInvocable": true,
10
+ "name": "agent",
11
+ "requiresPermission": false,
12
+ "safety": "background-agent",
13
+ "userInvocable": true,
14
+ },
15
+ "compact": {
16
+ "argumentHint": "[instructions]",
17
+ "description": "Summarize older conversation turns to free context-window space. Use it when the context is nearly full (check with the context command) or before starting a large new task; optional instructions say what the summary must keep. Returns how many messages were removed and the context usage before and after.",
18
+ "kind": "builtin-command",
19
+ "modelInvocable": true,
20
+ "name": "compact",
21
+ "requiresPermission": false,
22
+ "safety": "write",
23
+ "userInvocable": true,
24
+ },
25
+ "context": {
26
+ "argumentHint": "[list]",
27
+ "description": "Inspect the context window. Use it before a large task or before deciding to compact, or to see which files the user pinned into context. Bare returns used/max tokens and percentage, the auto-compact threshold, the pinned-reference summary and the turn count; \`list\` returns the per-category token breakdown and every pinned reference.
28
+ Subcommands you may run (bare \`context\` is also allowed):
29
+ - list: List loaded context references",
30
+ "kind": "builtin-command",
31
+ "modelInvocable": true,
32
+ "name": "context",
33
+ "requiresPermission": false,
34
+ "userInvocable": true,
35
+ },
36
+ "cost": {
37
+ "description": "Report this session’s token usage and estimated cost. Use it before an expensive step (a large refactor, many subagents) or when the user asks what the session has cost. Returns the message count, input/output token totals and the estimated USD cost, with the remaining monthly budget when one is set. Setting or clearing the budget is the user’s decision: suggest \`/cost budget <amount>\`.
38
+ Run it with no arguments.",
39
+ "kind": "builtin-command",
40
+ "modelInvocable": true,
41
+ "name": "cost",
42
+ "requiresPermission": false,
43
+ "userInvocable": true,
44
+ },
45
+ "loop": {
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> <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
+ "kind": "builtin-command",
49
+ "modelInvocable": true,
50
+ "name": "loop",
51
+ "requiresPermission": true,
52
+ "userInvocable": true,
53
+ },
54
+ "mcp": {
55
+ "argumentHint": "[status | skill-list <server>]",
56
+ "description": "Show which MCP servers are configured and why any of them is unavailable. Use it when an MCP server or its tools are missing, a tool call reports that a server needs sign-in or approval, or the user asks about MCP servers. Returns one line per server: its name, activation state, sign-in state, and the command to suggest to the user when the server needs their approval, sign-in or workspace trust. Use skill-list <server> to discover advertised skill names, URIs and descriptions without loading instructions. Returns an invocation name for each supported skill; use the session skill activation command with that exact name when its workflow fits the task. If content consent is needed, ask the user to run /mcp skill-inspect <server> <uri> and approve the reviewed fingerprint. You cannot approve, inspect instructions, sign in or sign out yourself.
57
+ Subcommands you may run (bare \`mcp\` is also allowed):
58
+ - status: Show every MCP server’s name, activation state and sign-in state
59
+ - skill-list: Discover MCP skill metadata and exact invocation names, or reasons they are unavailable, without activating instructions",
60
+ "kind": "builtin-command",
61
+ "modelInvocable": true,
62
+ "name": "mcp",
63
+ "requiresPermission": false,
64
+ "userInvocable": true,
65
+ },
66
+ "memory": {
67
+ "argumentHint": "[list | show [topic] | add <user|feedback|project|reference> <topic> <text> | pending | used]",
68
+ "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>\`. Correcting or forgetting saved knowledge is also user-only: suggest \`/memory correct <type> <topic> <text>\` to replace every entry in a topic or \`/memory forget <topic>\` to remove it from active recall. These operations block automatic restoration of that topic; historical transcripts and backups remain.
69
+ Subcommands you may run (bare \`memory\` is also allowed):
70
+ - list: List project memory topics
71
+ - show: Show project memory index or a topic
72
+ - add: Save durable project memory
73
+ - pending: List pending memory candidates
74
+ - used: Show memory references used in the current turn",
75
+ "kind": "builtin-command",
76
+ "modelInvocable": true,
77
+ "name": "memory",
78
+ "requiresPermission": false,
79
+ "safety": "write",
80
+ "userInvocable": true,
81
+ },
82
+ "monitor": {
83
+ "argumentHint": ""<command>" "<pattern>" <instruction>",
84
+ "description": "Run a command in the background and wake yourself when a line of its output matches a pattern. Use it to wait for a server to start, a test to fail or a log line to appear instead of polling. The command is allowed, refused or asked about by the same permission rules as a shell tool call. Returns the monitor task id, or a refusal naming what the user must allow.",
85
+ "kind": "builtin-command",
86
+ "modelInvocable": true,
87
+ "name": "monitor",
88
+ "requiresPermission": false,
89
+ "userInvocable": true,
90
+ },
91
+ "schedule": {
92
+ "argumentHint": "in <N><s|m|h|d> <instruction> | cron "<expr>" <instruction> | list | pause <id> | resume <id> | edit <id> <spec>",
93
+ "description": "Wake yourself later with an instruction. Use it when work must wait (a deploy, a long build, a reply) instead of polling: \`in <N><s|m|h|d> <instruction>\` or \`cron "<expr>" <instruction>\`; \`list\`, \`pause\`, \`resume\` and \`edit\` manage existing schedules. Returns the schedule id, or the schedule list.",
94
+ "kind": "builtin-command",
95
+ "modelInvocable": true,
96
+ "name": "schedule",
97
+ "requiresPermission": false,
98
+ "userInvocable": true,
99
+ },
100
+ "skill-read": {
101
+ "argumentHint": "["<skill-name>", "<resource-uri>"]",
102
+ "description": "Read verified supporting bytes from a skill already activated in this execution. Use when its instructions refer to a resource in the returned manifest. Args are a JSON array ["<skill-name>", "<resource-uri>"]. Returns the verified URI, digest, size and text or base64 blob with optional MIME type. This does not activate nested skills or grant consent; unactivated skills, undeclared resources and ended executions are refused. Binary blobs are returned as encoded data, not a rendered image.",
103
+ "kind": "builtin-command",
104
+ "modelInvocable": true,
105
+ "name": "skill-read",
106
+ "requiresPermission": false,
107
+ "safety": "read-only",
108
+ "userInvocable": true,
109
+ },
110
+ "skills": {
111
+ "argumentHint": "[list | <skill-name> [args]]",
112
+ "description": "Skill command. Before following a matching registered skill from the system prompt Skills section, invoke the projected skills command tool with args "<skill-name> [args]". Without arguments, list registered skills. With a skill name, activate that skill; a skill that declares it is not model-invocable is refused. Returns the skill list with each skill's description, or the activated skill's instructions to follow. Slash syntax is a UI input/display concern; the SDK command identity is "skills".",
113
+ "kind": "builtin-command",
114
+ "modelInvocable": true,
115
+ "name": "skills",
116
+ "requiresPermission": false,
117
+ "safety": "read-only",
118
+ "userInvocable": true,
119
+ },
120
+ }
121
+ `;
@@ -0,0 +1,192 @@
1
+ import { mkdtempSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ import { afterAll, describe, expect, it } from 'vitest';
6
+
7
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
8
+ import type {
9
+ IProviderCommandSettingsAdapter,
10
+ TProviderSettingsDocument,
11
+ } from '@robota-sdk/agent-framework';
12
+
13
+ import { createDefaultCommandModules } from '../default-command-modules.js';
14
+
15
+ /** User-local storage lives in a private per-run directory, never a fixed name under /tmp. */
16
+ const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'agent-test-test-'));
17
+ afterAll(() => rmSync(USER_LOCAL_STORAGE_ROOT, { recursive: true, force: true }));
18
+
19
+ const providerDefinitions: readonly IProviderDefinition[] = [
20
+ {
21
+ type: 'anthropic',
22
+ defaults: { model: 'claude-sonnet-4-6', apiKey: '$ENV:ANTHROPIC_API_KEY' },
23
+ setupSteps: [{ key: 'apiKey', title: 'anthropic API key', masked: true }],
24
+ requiresApiKey: true,
25
+ createProvider: () => {
26
+ throw new Error('not used');
27
+ },
28
+ },
29
+ ];
30
+
31
+ const providerSettingsAdapter: IProviderCommandSettingsAdapter = {
32
+ readMergedSettings: () => ({}) as TProviderSettingsDocument,
33
+ readTargetSettings: () => ({}) as TProviderSettingsDocument,
34
+ writeTargetSettings: () => undefined,
35
+ };
36
+
37
+ const baseOptions = { cwd: '/tmp', userLocalStorageRoot: USER_LOCAL_STORAGE_ROOT, providerDefinitions, providerSettingsAdapter } as const;
38
+
39
+ function moduleNames(opts: Parameters<typeof createDefaultCommandModules>[0]): string[] {
40
+ return createDefaultCommandModules(opts).modules.map((module) => module.name);
41
+ }
42
+
43
+ describe('createDefaultCommandModules — PRESET-004 module-selection delta', () => {
44
+ // Module `name`s are the stable ICommandModule ids (the `agent-command-*` form).
45
+ const HELP = 'agent-command-help';
46
+ const AGENT = 'agent-command-agent';
47
+ const BACKGROUND = 'agent-command-background';
48
+
49
+ it('TC-04: neither enabled nor disabled given → full default set unchanged (no-regression)', () => {
50
+ const names = moduleNames(baseOptions);
51
+ // No-regression: the default set length is the documented 32 modules (SELFHOST-002 added
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; #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
+ expect(names).toEqual([
58
+ 'agent-command-skills',
59
+ 'agent-command-help',
60
+ 'agent-command-agent',
61
+ 'agent-command-effort',
62
+ 'agent-command-advisor',
63
+ 'agent-command-permissions',
64
+ 'agent-command-mode',
65
+ 'agent-command-sandbox',
66
+ 'agent-command-preset',
67
+ 'agent-command-output-style',
68
+ 'agent-command-language',
69
+ 'agent-command-background',
70
+ // CLI-1994: registered beside `/background`, because a fork IS a background job.
71
+ 'agent-command-fork',
72
+ 'agent-command-goal',
73
+ 'agent-command-plan',
74
+ 'agent-command-shell',
75
+ 'agent-command-editor',
76
+ 'agent-command-git',
77
+ 'agent-command-keybindings',
78
+ 'agent-command-theme',
79
+ 'agent-command-memory',
80
+ 'agent-command-mcp-activation',
81
+ 'agent-command-user-local',
82
+ 'agent-command-compact',
83
+ 'agent-command-context',
84
+ 'agent-command-exit',
85
+ 'agent-command-session',
86
+ 'agent-command-reset',
87
+ 'agent-command-rewind',
88
+ 'agent-command-schedule',
89
+ 'agent-command-statusline',
90
+ 'agent-command-plugin',
91
+ 'agent-command-settings',
92
+ 'agent-command-peers',
93
+ 'agent-command-events',
94
+ 'agent-command-handoff',
95
+ 'agent-command-remote-control',
96
+ 'agent-command-provider',
97
+ 'agent-command-model',
98
+ ]);
99
+ });
100
+
101
+ it('TC-01: enabledCommandModules whitelist keeps exactly the listed module names', () => {
102
+ const names = moduleNames({ ...baseOptions, enabledCommandModules: [HELP, AGENT] });
103
+ expect(new Set(names)).toEqual(new Set([HELP, AGENT]));
104
+ expect(names).toHaveLength(2);
105
+ });
106
+
107
+ it('#3186: /keybindings and /theme exist on a host without a terminal, and say where they work', async () => {
108
+ const { modules } = createDefaultCommandModules(baseOptions);
109
+ const commands = modules.flatMap((module) => module.systemCommands ?? []);
110
+ for (const name of ['keybindings', 'theme']) {
111
+ const command = commands.find((candidate) => candidate.name === name);
112
+ expect(command, name).toBeDefined();
113
+ const result = await command!.execute({} as never, '');
114
+ expect(result).toMatchObject({ success: false });
115
+ expect(result.message).toMatch(/interactive terminal/);
116
+ }
117
+ });
118
+
119
+ it('#3189: exactly /editor, /keybindings, /shell and /theme run on the terminal client', () => {
120
+ const { modules } = createDefaultCommandModules(baseOptions);
121
+ const TERMINAL_CLIENT = ['editor', 'keybindings', 'shell', 'theme'];
122
+
123
+ // The executable commands: what the runtime keeps in its catalog and what the listing reads.
124
+ const clientSystemCommands = modules
125
+ .flatMap((module) => module.systemCommands ?? [])
126
+ .filter((command) => command.runner === 'client');
127
+ expect(clientSystemCommands.map((command) => command.name).sort()).toEqual(TERMINAL_CLIENT);
128
+ for (const command of clientSystemCommands) {
129
+ expect(command.surfaces, command.name).toEqual(['terminal']);
130
+ }
131
+
132
+ // The palette entries, which must say the same — the two views cannot disagree.
133
+ const clientEntries = modules
134
+ .flatMap((module) => module.commandSources ?? [])
135
+ .flatMap((source) => source.getCommands())
136
+ .filter((entry) => entry.runner === 'client');
137
+ expect(clientEntries.map((entry) => entry.name).sort()).toEqual(TERMINAL_CLIENT);
138
+ for (const entry of clientEntries) {
139
+ expect(entry.surfaces, entry.name).toEqual(['terminal']);
140
+ // Running on the client does not change who may run it: all four stay user-only.
141
+ expect(entry.modelInvocable, entry.name).toBe(false);
142
+ }
143
+ });
144
+
145
+ it('TC-02: disabledCommandModules blacklist removes the named module', () => {
146
+ const full = moduleNames(baseOptions);
147
+ const names = moduleNames({ ...baseOptions, disabledCommandModules: [BACKGROUND] });
148
+ expect(names).not.toContain(BACKGROUND);
149
+ expect(names).toHaveLength(full.length - 1);
150
+ });
151
+
152
+ it('TC-03: a name in both enabled and disabled is excluded (deny > allow)', () => {
153
+ const names = moduleNames({
154
+ ...baseOptions,
155
+ enabledCommandModules: [HELP, AGENT],
156
+ disabledCommandModules: [AGENT],
157
+ });
158
+ expect(new Set(names)).toEqual(new Set([HELP]));
159
+ expect(names).not.toContain(AGENT);
160
+ });
161
+
162
+ it('INFRA-032: an unknown whitelist name yields no module for it AND is reported as unknown', () => {
163
+ const result = createDefaultCommandModules({
164
+ ...baseOptions,
165
+ enabledCommandModules: [HELP, 'does-not-exist'],
166
+ });
167
+ // Known-name filtering is unchanged — only the matched module survives.
168
+ expect(result.modules.map((module) => module.name)).toEqual([HELP]);
169
+ // The unmatched name is surfaced (no longer silently dropped).
170
+ expect(result.unknownModuleNames).toEqual([{ name: 'does-not-exist', kind: 'enabled' }]);
171
+ });
172
+
173
+ it('INFRA-032: an unknown disabled name (short form) is reported as unknown', () => {
174
+ const result = createDefaultCommandModules({
175
+ ...baseOptions,
176
+ disabledCommandModules: ['editor'],
177
+ });
178
+ // The short form "editor" matches no module name (agent-command-editor stays enabled)…
179
+ expect(result.modules.map((module) => module.name)).toContain('agent-command-editor');
180
+ // …and is surfaced rather than silently ignored.
181
+ expect(result.unknownModuleNames).toEqual([{ name: 'editor', kind: 'disabled' }]);
182
+ });
183
+
184
+ it('INFRA-032: all names matching → unknownModuleNames is empty', () => {
185
+ const result = createDefaultCommandModules({
186
+ ...baseOptions,
187
+ enabledCommandModules: [HELP, AGENT],
188
+ disabledCommandModules: [AGENT],
189
+ });
190
+ expect(result.unknownModuleNames).toEqual([]);
191
+ });
192
+ });
@@ -0,0 +1,269 @@
1
+ /**
2
+ * What of the built-in command set the MODEL is offered, and what it is told.
3
+ *
4
+ * Pins, per command: offered or not, the exact model-facing descriptor, and that a user-only
5
+ * subcommand is neither offered nor runnable. The invariant behind it: trust, credential and
6
+ * permission-widening actions are never model-invocable. The reasons for each decision sit beside
7
+ * each command's entry.
8
+ */
9
+
10
+ import { mkdtempSync, rmSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
15
+
16
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
17
+ import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
18
+
19
+ import { createDoctorCommandModule } from '../../doctor/index.js';
20
+ import { createKeybindingsCommandModule } from '../../keybindings/index.js';
21
+ import { createThemeCommandModule } from '../../theme/index.js';
22
+ import { createDoctorFixture } from '../../doctor/__tests__/doctor-fixture.js';
23
+ import { createDefaultCommandModules } from '../default-command-modules.js';
24
+
25
+ import type { IDoctorFixture } from '../../doctor/__tests__/doctor-fixture.js';
26
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
27
+ import type {
28
+ IProviderCommandSettingsAdapter,
29
+ ISystemCommand,
30
+ TProviderSettingsDocument,
31
+ } from '@robota-sdk/agent-framework';
32
+ import type { IThemeCataloguePort } from '@robota-sdk/agent-interface-command';
33
+
34
+ const providerDefinitions: readonly IProviderDefinition[] = [];
35
+ const providerSettingsAdapter: IProviderCommandSettingsAdapter = {
36
+ readMergedSettings: () => ({}) as TProviderSettingsDocument,
37
+ readTargetSettings: () => ({}) as TProviderSettingsDocument,
38
+ writeTargetSettings: () => undefined,
39
+ };
40
+ const themeCataloguePort: IThemeCataloguePort = {
41
+ listThemes: () => [],
42
+ getTheme: () => undefined,
43
+ getAppearance: () => ({
44
+ settings: { theme: 'dark', syntaxHighlighting: true, reducedMotion: false },
45
+ }),
46
+ };
47
+
48
+ /** User-local storage lives in a private per-run directory, never a fixed name under /tmp. */
49
+ const USER_LOCAL_STORAGE_ROOT = mkdtempSync(join(tmpdir(), 'agent-test-test-'));
50
+ afterAll(() => rmSync(USER_LOCAL_STORAGE_ROOT, { recursive: true, force: true }));
51
+
52
+ const fixtures: IDoctorFixture[] = [];
53
+ afterEach(() => {
54
+ for (const fixture of fixtures.splice(0)) fixture.cleanup();
55
+ });
56
+
57
+ /** Every built-in command, including the ones only registered when their host port exists. */
58
+ function allBuiltInCommands(): ISystemCommand[] {
59
+ const fixture = createDoctorFixture({ env: {} });
60
+ fixtures.push(fixture);
61
+ const { modules } = createDefaultCommandModules({
62
+ cwd: '/tmp',
63
+ userLocalStorageRoot: USER_LOCAL_STORAGE_ROOT,
64
+ providerDefinitions,
65
+ providerSettingsAdapter,
66
+ });
67
+ // /keybindings and /theme are in the default set; built here with their capabilities instead.
68
+ const withoutPortless = modules.filter(
69
+ (module) =>
70
+ module.name !== 'agent-command-keybindings' && module.name !== 'agent-command-theme',
71
+ );
72
+ return [
73
+ ...withoutPortless,
74
+ createKeybindingsCommandModule({ ensureFile: async () => '/tmp/keybindings.json' }),
75
+ createThemeCommandModule(themeCataloguePort),
76
+ createDoctorCommandModule(fixture.inputs, fixture.deps),
77
+ ].flatMap((module) => [...(module.systemCommands ?? [])]);
78
+ }
79
+
80
+ /** Each command with its `execute` replaced, so a test can see whether it ran at all. */
81
+ function spiedExecutor(): {
82
+ executor: SystemCommandExecutor;
83
+ executed: ReturnType<typeof vi.fn>;
84
+ } {
85
+ const executed = vi.fn(() => ({ success: true, message: 'ran' }));
86
+ const commands = allBuiltInCommands().map((command) => ({ ...command, execute: executed }));
87
+ return { executor: new SystemCommandExecutor(commands), executed };
88
+ }
89
+
90
+ /** Offered to the model, each for a reason given beside its entry. */
91
+ const MODEL_INVOCABLE = [
92
+ 'agent',
93
+ 'compact',
94
+ 'context',
95
+ 'cost',
96
+ 'loop',
97
+ 'mcp',
98
+ 'memory',
99
+ 'monitor',
100
+ 'schedule',
101
+ 'skill-read',
102
+ 'skills',
103
+ ];
104
+
105
+ /** Whole commands the model is never offered. */
106
+ const USER_ONLY_COMMANDS = [
107
+ // Trust decisions.
108
+ 'plugin',
109
+ 'reload-plugins',
110
+ 'remote-control',
111
+ 'events',
112
+ // Account and credential actions.
113
+ 'provider',
114
+ // Cost and capability decisions: the person chooses, never the model on its own (#3282 §2).
115
+ 'model',
116
+ // Permission widening or permission-mode changes.
117
+ 'mode',
118
+ 'permissions',
119
+ 'sandbox',
120
+ 'preset',
121
+ 'plan',
122
+ 'goal',
123
+ 'cd',
124
+ // Crossing into another session, machine or process the user owns.
125
+ 'peers',
126
+ 'handoff',
127
+ 'fork',
128
+ 'shell',
129
+ 'git',
130
+ // Exits and destructive session changes.
131
+ 'exit',
132
+ 'clear',
133
+ 'reset',
134
+ 'rewind',
135
+ 'resume',
136
+ // UI-only preferences, and views the user reads.
137
+ 'advisor',
138
+ 'effort',
139
+ 'language',
140
+ 'output-style',
141
+ 'statusline',
142
+ 'theme',
143
+ 'keybindings',
144
+ 'settings',
145
+ 'editor',
146
+ 'rename',
147
+ 'help',
148
+ 'background',
149
+ 'doctor',
150
+ 'user-local',
151
+ 'validate-session',
152
+ ];
153
+
154
+ /** User-only subcommands of commands the model IS offered; `list` is an alias `/mcp` accepts for the user. */
155
+ const USER_ONLY_SUBCOMMANDS: Record<string, readonly string[]> = {
156
+ mcp: [
157
+ 'approve',
158
+ 'reject',
159
+ 'revoke',
160
+ 'logout',
161
+ 'login',
162
+ 'list',
163
+ 'skill-inspect',
164
+ 'skill-approve',
165
+ 'skill-withdraw',
166
+ ],
167
+ memory: ['approve', 'reject'],
168
+ context: ['add', 'remove', 'clear', 'auto'],
169
+ cost: ['budget'],
170
+ };
171
+
172
+ describe('built-in commands offered to the model', () => {
173
+ it('offers exactly the model-invocable set', () => {
174
+ const { executor } = spiedExecutor();
175
+ const offered = executor
176
+ .listModelInvocableCommands()
177
+ .map((descriptor) => descriptor.name)
178
+ .sort();
179
+ expect(offered).toEqual([...MODEL_INVOCABLE].sort());
180
+ });
181
+
182
+ it('covers every built-in command with a decision', () => {
183
+ const names = allBuiltInCommands()
184
+ .map((command) => command.name)
185
+ .sort();
186
+ expect(names).toEqual([...MODEL_INVOCABLE, ...USER_ONLY_COMMANDS].sort());
187
+ });
188
+
189
+ it('describes every offered command for the model: what, when, and what it returns', () => {
190
+ for (const command of allBuiltInCommands().filter((c) => c.modelInvocable === true)) {
191
+ expect(command.modelDescription, command.name).toBeDefined();
192
+ expect(command.modelDescription, command.name).toMatch(/\bUse\b|\bBefore\b|\bWhen\b/);
193
+ expect(command.modelDescription, command.name).toMatch(/\b[Rr]eturns\b/);
194
+ }
195
+ });
196
+
197
+ it('pins the descriptor each offered command gets', () => {
198
+ const { executor } = spiedExecutor();
199
+ const descriptors = Object.fromEntries(
200
+ executor
201
+ .listModelInvocableCommands()
202
+ .sort((a, b) => a.name.localeCompare(b.name))
203
+ .map((descriptor) => [descriptor.name, descriptor]),
204
+ );
205
+ expect(descriptors).toMatchSnapshot();
206
+ });
207
+
208
+ it('never offers or runs a user-only command', async () => {
209
+ const { executor, executed } = spiedExecutor();
210
+ const host = createTestCommandHost();
211
+ for (const name of USER_ONLY_COMMANDS) {
212
+ expect(executor.isModelInvocable(name), name).toBe(false);
213
+ expect(await executor.executeModelInvocable(name, host, ''), name).toBeNull();
214
+ }
215
+ expect(executed).not.toHaveBeenCalled();
216
+ });
217
+
218
+ it('never offers a user-only subcommand, and refuses it before the command runs', async () => {
219
+ const { executor, executed } = spiedExecutor();
220
+ const host = createTestCommandHost();
221
+ const descriptors = executor.listModelInvocableCommands();
222
+ for (const [name, subcommands] of Object.entries(USER_ONLY_SUBCOMMANDS)) {
223
+ const descriptor = descriptors.find((candidate) => candidate.name === name);
224
+ const offeredBlock = descriptor?.description.split('Subcommands you may run')[1] ?? '';
225
+ for (const sub of subcommands) {
226
+ expect(descriptor?.argumentHint ?? '', `${name} ${sub}`).not.toMatch(
227
+ new RegExp(`(?:^|\\s|\\[|\\|)${sub}(?=\\s|\\]|\\||$)`),
228
+ );
229
+ expect(offeredBlock, `${name} ${sub}`).not.toContain(`- ${sub}:`);
230
+ const result = await executor.executeModelInvocable(name, host, `${sub} some-arg`);
231
+ expect(result?.success, `${name} ${sub}`).toBe(false);
232
+ }
233
+ }
234
+ expect(executed).not.toHaveBeenCalled();
235
+ });
236
+
237
+ it('runs the read-only subset for the model', async () => {
238
+ const { executor, executed } = spiedExecutor();
239
+ const host = createTestCommandHost();
240
+ for (const [name, args] of [
241
+ ['mcp', ''],
242
+ ['mcp', 'status'],
243
+ ['mcp', 'skill-list docs'],
244
+ ['memory', 'list'],
245
+ ['context', ''],
246
+ ['context', 'list'],
247
+ ['cost', ''],
248
+ ] as const) {
249
+ expect((await executor.executeModelInvocable(name, host, args))?.message).toBe('ran');
250
+ }
251
+ expect(executed).toHaveBeenCalledTimes(7);
252
+ });
253
+
254
+ it('skips the by-name prompt only where the model reaches nothing that needs it', () => {
255
+ const { executor } = spiedExecutor();
256
+ const byName = Object.fromEntries(
257
+ executor.listModelInvocableCommands().map((d) => [d.name, d.requiresPermission]),
258
+ );
259
+ // `/mcp`: the model reaches only `status`. `/monitor`: its command is decided by the shell
260
+ // gate instead (monitor-model-permission-functional.test.ts).
261
+ expect(byName.mcp).toBe(false);
262
+ expect(byName.monitor).toBe(false);
263
+ // Neither becomes read-only for the user or a remote read-only policy.
264
+ for (const name of ['mcp', 'monitor']) {
265
+ const command = executor.getCommand(name)!;
266
+ expect(executor.resolveRequiresPermission(command), name).toBe(true);
267
+ }
268
+ });
269
+ });