@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,52 @@
1
+ import {
2
+ BACKGROUND_COMMAND_DESCRIPTION,
3
+ buildBackgroundCommandSubcommands,
4
+ } from '@robota-sdk/agent-framework';
5
+
6
+ import { executeBackgroundCommand } from './background-command.js';
7
+
8
+ import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
9
+ import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
10
+
11
+ export function createBackgroundCommandEntry(): ICommand {
12
+ return {
13
+ name: 'background',
14
+ displayName: 'Background Tasks',
15
+ description: BACKGROUND_COMMAND_DESCRIPTION,
16
+ source: 'background',
17
+ // User-only: a view onto the user's job panel; the model manages its own jobs through `agent`.
18
+ modelInvocable: false,
19
+ subcommands: buildBackgroundCommandSubcommands(),
20
+ };
21
+ }
22
+
23
+ function createBackgroundSystemCommand(): ISystemCommand {
24
+ const entry = createBackgroundCommandEntry();
25
+ return {
26
+ name: entry.name,
27
+ displayName: entry.displayName,
28
+ description: entry.description,
29
+ requiresPermission: false,
30
+ userInvocable: true,
31
+ modelInvocable: false,
32
+ lifecycle: 'inline',
33
+ subcommands: entry.subcommands,
34
+ execute: executeBackgroundCommand,
35
+ };
36
+ }
37
+
38
+ export class BackgroundCommandSource implements ICommandSource {
39
+ readonly name = 'background';
40
+
41
+ getCommands(): ICommand[] {
42
+ return [createBackgroundCommandEntry()];
43
+ }
44
+ }
45
+
46
+ export function createBackgroundCommandModule(): ICommandModule {
47
+ return {
48
+ name: 'agent-command-background',
49
+ commandSources: [new BackgroundCommandSource()],
50
+ systemCommands: [createBackgroundSystemCommand()],
51
+ };
52
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ BACKGROUND_COMMAND_USAGE,
3
+ cancelCommandBackgroundTask,
4
+ closeCommandBackgroundTask,
5
+ formatCommandBackgroundTaskList,
6
+ listCommandBackgroundTasks,
7
+ parseCommandBackgroundLogCursor,
8
+ readCommandBackgroundTaskLog,
9
+ } from '@robota-sdk/agent-framework';
10
+
11
+ import type { ICommandHostBackgroundTasks } from '@robota-sdk/agent-framework';
12
+ import type { ICommandResult } from '@robota-sdk/agent-interface-command';
13
+
14
+ function parseCommandParts(args: string): string[] {
15
+ return args.trim().split(/\s+/).filter(Boolean);
16
+ }
17
+
18
+ export async function executeBackgroundCommand(
19
+ context: ICommandHostBackgroundTasks,
20
+ args: string,
21
+ ): Promise<ICommandResult> {
22
+ const [action = 'list', taskId, ...reasonParts] = parseCommandParts(args);
23
+ if (action === 'list') {
24
+ const tasks = listCommandBackgroundTasks(context);
25
+ return {
26
+ message: formatCommandBackgroundTaskList(tasks),
27
+ success: true,
28
+ data: { count: tasks.length },
29
+ };
30
+ }
31
+
32
+ if (!taskId) {
33
+ return {
34
+ message: BACKGROUND_COMMAND_USAGE,
35
+ success: false,
36
+ };
37
+ }
38
+
39
+ if (action === 'read' || action === 'log' || action === 'open') {
40
+ const page = await readCommandBackgroundTaskLog(
41
+ context,
42
+ taskId,
43
+ parseCommandBackgroundLogCursor(reasonParts[0]),
44
+ );
45
+ const next = page.nextCursor ? `\nNext offset: ${page.nextCursor.offset}` : '';
46
+ return {
47
+ message:
48
+ page.lines.length > 0 ? `${page.lines.join('\n')}${next}` : `No log lines: ${taskId}`,
49
+ success: true,
50
+ data: { taskId, nextOffset: page.nextCursor?.offset },
51
+ };
52
+ }
53
+
54
+ if (action === 'cancel' || action === 'stop') {
55
+ await cancelCommandBackgroundTask(context, taskId, reasonParts.join(' ') || undefined);
56
+ return { message: `Background task cancelled: ${taskId}`, success: true, data: { taskId } };
57
+ }
58
+
59
+ if (action === 'close' || action === 'dismiss') {
60
+ await closeCommandBackgroundTask(context, taskId);
61
+ return { message: `Background task closed: ${taskId}`, success: true, data: { taskId } };
62
+ }
63
+
64
+ return { message: `Unknown background action: ${action}`, success: false };
65
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ BackgroundCommandSource,
3
+ createBackgroundCommandEntry,
4
+ createBackgroundCommandModule,
5
+ } from './background-command-module.js';
6
+ export { executeBackgroundCommand } from './background-command.js';
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createSystemCommandFromEntry } from './command-module-utils.js';
4
+
5
+ const behavior = {
6
+ lifecycle: 'inline',
7
+ requiresPermission: false,
8
+ execute: () => ({ success: true, message: 'ran' }),
9
+ } as const;
10
+
11
+ describe('createSystemCommandFromEntry', () => {
12
+ it('#3189: carries where the entry says it runs, so the catalog and the palette agree', () => {
13
+ const command = createSystemCommandFromEntry(
14
+ {
15
+ name: 'local',
16
+ description: 'Runs on the terminal',
17
+ source: 'test',
18
+ runner: 'client',
19
+ surfaces: ['terminal'],
20
+ },
21
+ behavior,
22
+ );
23
+
24
+ expect(command.runner).toBe('client');
25
+ expect(command.surfaces).toEqual(['terminal']);
26
+ });
27
+
28
+ it('leaves an unstated placement unstated rather than guessing one', () => {
29
+ const command = createSystemCommandFromEntry(
30
+ { name: 'plain', description: 'Runs on the runtime', source: 'test' },
31
+ behavior,
32
+ );
33
+
34
+ expect('runner' in command).toBe(false);
35
+ expect('surfaces' in command).toBe(false);
36
+ });
37
+ });
@@ -0,0 +1,35 @@
1
+ import type { ICommand, ICommandResult } from '@robota-sdk/agent-interface-command';
2
+ import type { ICommandHostContext, ISystemCommand } from '@robota-sdk/agent-framework';
3
+
4
+ /** Build executable metadata from the palette entry so user and model views cannot drift. */
5
+ export function createSystemCommandFromEntry(
6
+ entry: ICommand,
7
+ behavior: {
8
+ lifecycle: NonNullable<ISystemCommand['lifecycle']>;
9
+ requiresPermission: boolean;
10
+ execute: (
11
+ context: ICommandHostContext,
12
+ args: string,
13
+ ) => ICommandResult | Promise<ICommandResult>;
14
+ semanticRole?: ISystemCommand['semanticRole'];
15
+ },
16
+ ): ISystemCommand {
17
+ return {
18
+ name: entry.name,
19
+ ...(entry.displayName !== undefined ? { displayName: entry.displayName } : {}),
20
+ description: entry.description,
21
+ ...(entry.example !== undefined ? { example: entry.example } : {}),
22
+ ...(entry.modelInvocable !== undefined ? { modelInvocable: entry.modelInvocable } : {}),
23
+ ...(entry.modelDescription !== undefined ? { modelDescription: entry.modelDescription } : {}),
24
+ ...(entry.userInvocable !== undefined ? { userInvocable: entry.userInvocable } : {}),
25
+ ...(entry.argumentHint !== undefined ? { argumentHint: entry.argumentHint } : {}),
26
+ ...(entry.safety !== undefined ? { safety: entry.safety } : {}),
27
+ ...(entry.subcommands !== undefined ? { subcommands: entry.subcommands } : {}),
28
+ ...(entry.runner !== undefined ? { runner: entry.runner } : {}),
29
+ ...(entry.surfaces !== undefined ? { surfaces: entry.surfaces } : {}),
30
+ ...(behavior.semanticRole !== undefined ? { semanticRole: behavior.semanticRole } : {}),
31
+ lifecycle: behavior.lifecycle,
32
+ requiresPermission: behavior.requiresPermission,
33
+ execute: behavior.execute,
34
+ };
35
+ }
@@ -0,0 +1,181 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { ICommandHostContext, ICommandSessionRuntime } from '@robota-sdk/agent-framework';
3
+ import { InteractiveSession, SystemCommandExecutor } from '@robota-sdk/agent-framework';
4
+ import { createCompactCommandModule } from '../compact-command-module.js';
5
+ import type { ICommandHostContextWindow } from '@robota-sdk/agent-framework';
6
+ import {
7
+ createTestCommandHost,
8
+ createTestSessionRuntime,
9
+ } from '@robota-sdk/agent-framework/testing';
10
+
11
+ type TContextWindowState = ReturnType<ICommandHostContextWindow['getContextState']>;
12
+ type TPermissionMode = ReturnType<ICommandSessionRuntime['getPermissionMode']>;
13
+
14
+ const BEFORE_CONTEXT: TContextWindowState = {
15
+ usedTokens: 80,
16
+ maxTokens: 100,
17
+ usedPercentage: 80,
18
+ remainingPercentage: 20,
19
+ };
20
+
21
+ const AFTER_CONTEXT: TContextWindowState = {
22
+ usedTokens: 35,
23
+ maxTokens: 100,
24
+ usedPercentage: 35,
25
+ remainingPercentage: 65,
26
+ };
27
+
28
+ function createRuntime(beforeCount = 10, afterCount = 3) {
29
+ let mode: TPermissionMode = 'default';
30
+ const getMessageCount = vi.fn().mockReturnValueOnce(beforeCount).mockReturnValue(afterCount);
31
+ return createTestSessionRuntime({
32
+ clearHistory: vi.fn(),
33
+ compact: vi.fn().mockResolvedValue(undefined),
34
+ getContextState: vi.fn().mockReturnValue(AFTER_CONTEXT),
35
+ getPermissionMode: () => mode,
36
+ setPermissionMode: (nextMode) => {
37
+ mode = nextMode;
38
+ },
39
+ getSessionId: () => 'session_1',
40
+ getMessageCount,
41
+ getSessionAllowedTools: () => [],
42
+ getAutoCompactThreshold: () => 0.835,
43
+ getFullHistory: () => [],
44
+ getHistory: () => [],
45
+ });
46
+ }
47
+
48
+ function createCommandHostContext(): ReturnType<typeof createTestCommandHost> & {
49
+ compactContext: ReturnType<typeof vi.fn>;
50
+ } {
51
+ const runtime = createRuntime();
52
+ const getContextState = vi
53
+ .fn()
54
+ .mockReturnValueOnce(BEFORE_CONTEXT)
55
+ .mockReturnValue(AFTER_CONTEXT);
56
+ return createTestCommandHost({
57
+ overrides: {
58
+ getSession: () => runtime,
59
+ getContextState,
60
+ getAutoCompactThreshold: () => 0.835,
61
+ compactContext: vi.fn().mockResolvedValue(undefined),
62
+ getCwd: () => '/workspace',
63
+ listCommands: () => [],
64
+ listEditCheckpoints: () => [],
65
+ restoreEditCheckpoint: vi.fn(),
66
+ rollbackEditCheckpoint: vi.fn(),
67
+ getUsedMemoryReferences: () => [],
68
+ recordMemoryEvent: vi.fn(),
69
+ listBackgroundTasks: () => [],
70
+ readBackgroundTaskLog: vi.fn().mockResolvedValue({ taskId: 'task_1', lines: [] }),
71
+ cancelBackgroundTask: vi.fn(),
72
+ closeBackgroundTask: vi.fn(),
73
+ },
74
+ }) as ReturnType<typeof createCommandHostContext>;
75
+ }
76
+
77
+ describe('createCompactCommandModule', () => {
78
+ it('provides compact metadata and a blocking executable command', () => {
79
+ const module = createCompactCommandModule();
80
+ const command = module.systemCommands?.[0];
81
+ const entry = module.commandSources?.[0]?.getCommands()[0];
82
+
83
+ expect(module.name).toBe('agent-command-compact');
84
+ expect(entry).toEqual(
85
+ expect.objectContaining({
86
+ name: 'compact',
87
+ description: 'Compress context window',
88
+ argumentHint: '[instructions]',
89
+ modelInvocable: true,
90
+ safety: 'write',
91
+ }),
92
+ );
93
+ expect(command).toEqual(
94
+ expect.objectContaining({
95
+ name: 'compact',
96
+ semanticRole: 'contextReduction',
97
+ lifecycle: 'blocking',
98
+ modelInvocable: true,
99
+ safety: 'write',
100
+ }),
101
+ );
102
+ });
103
+
104
+ it('projects compact as a model-invocable descriptor through the command executor', () => {
105
+ const executor = new SystemCommandExecutor([
106
+ ...(createCompactCommandModule().systemCommands ?? []),
107
+ ]);
108
+
109
+ expect(executor.listModelInvocableCommands()).toEqual([
110
+ {
111
+ name: 'compact',
112
+ kind: 'builtin-command',
113
+ description: expect.stringContaining('Summarize older conversation turns'),
114
+ userInvocable: true,
115
+ modelInvocable: true,
116
+ argumentHint: '[instructions]',
117
+ safety: 'write',
118
+ requiresPermission: false,
119
+ },
120
+ ]);
121
+ });
122
+
123
+ it('compacts context through the command host facade', async () => {
124
+ const context = createCommandHostContext();
125
+ const executor = new SystemCommandExecutor([
126
+ ...(createCompactCommandModule().systemCommands ?? []),
127
+ ]);
128
+
129
+ const result = await executor.execute('compact', context, ' focus on tests ');
130
+
131
+ expect(result?.success).toBe(true);
132
+ expect(result?.message).toBe(
133
+ 'Context compacted.\n Removed messages: 7 (70% of total)\n Context: 80% → 35%',
134
+ );
135
+ expect(result?.data).toEqual({
136
+ before: BEFORE_CONTEXT,
137
+ after: AFTER_CONTEXT,
138
+ beforeMessageCount: 10,
139
+ afterMessageCount: 3,
140
+ });
141
+ expect(context.compactContext).toHaveBeenCalledWith('focus on tests');
142
+ });
143
+
144
+ it('runs through InteractiveSession foreground lifecycle when composed', async () => {
145
+ let resolveCompact: () => void;
146
+ const runtime = createRuntime();
147
+ const compact = vi.fn(
148
+ () =>
149
+ new Promise<void>((resolve) => {
150
+ resolveCompact = resolve;
151
+ }),
152
+ );
153
+ const session = new InteractiveSession({
154
+ session: {
155
+ ...runtime,
156
+ compact,
157
+ } as never,
158
+ commandModules: [createCompactCommandModule()],
159
+ });
160
+ const thinkingStates: boolean[] = [];
161
+ session.on('thinking', (isThinking) => thinkingStates.push(isThinking));
162
+
163
+ const pending = session.executeCommand('compact', 'focus on tests');
164
+ await new Promise((resolve) => setTimeout(resolve, 10));
165
+ const blocked = await session.executeCommand('compact', '');
166
+
167
+ expect(session.isExecuting()).toBe(true);
168
+ expect(thinkingStates).toEqual([true]);
169
+ expect(blocked?.success).toBe(false);
170
+ expect(blocked?.message).toContain('already running');
171
+ expect(compact).toHaveBeenCalledWith('focus on tests');
172
+
173
+ resolveCompact!();
174
+ const result = await pending;
175
+
176
+ expect(result?.success).toBe(true);
177
+ expect(result?.message).toContain('Context compacted');
178
+ expect(session.isExecuting()).toBe(false);
179
+ expect(thinkingStates).toEqual([true, false]);
180
+ });
181
+ });
@@ -0,0 +1,59 @@
1
+ import { executeCompactCommand } from './compact-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 createCompactCommandEntry(): ICommand {
7
+ return {
8
+ name: 'compact',
9
+ displayName: 'Compact Context',
10
+ description: 'Compress context window',
11
+ // Model-invocable: compaction only summarizes the model's own context, and the model is the one
12
+ // that notices when it is running out of room.
13
+ modelDescription:
14
+ 'Summarize older conversation turns to free context-window space. Use it when the context is ' +
15
+ 'nearly full (check with the context command) or before starting a large new task; optional ' +
16
+ 'instructions say what the summary must keep. Returns how many messages were removed and the ' +
17
+ 'context usage before and after.',
18
+ source: 'compact',
19
+ modelInvocable: true,
20
+ argumentHint: '[instructions]',
21
+ safety: 'write',
22
+ example: '/compact Summarize the current context',
23
+ };
24
+ }
25
+
26
+ function createCompactSystemCommand(): ISystemCommand {
27
+ const entry = createCompactCommandEntry();
28
+ return {
29
+ name: entry.name,
30
+ semanticRole: 'contextReduction',
31
+ displayName: entry.displayName,
32
+ description: entry.description,
33
+ ...(entry.modelDescription !== undefined ? { modelDescription: entry.modelDescription } : {}),
34
+ example: entry.example,
35
+ requiresPermission: false,
36
+ userInvocable: true,
37
+ modelInvocable: entry.modelInvocable,
38
+ argumentHint: entry.argumentHint,
39
+ safety: entry.safety,
40
+ lifecycle: 'blocking',
41
+ execute: executeCompactCommand,
42
+ };
43
+ }
44
+
45
+ export class CompactCommandSource implements ICommandSource {
46
+ readonly name = 'compact';
47
+
48
+ getCommands(): ICommand[] {
49
+ return [createCompactCommandEntry()];
50
+ }
51
+ }
52
+
53
+ export function createCompactCommandModule(): ICommandModule {
54
+ return {
55
+ name: 'agent-command-compact',
56
+ commandSources: [new CompactCommandSource()],
57
+ systemCommands: [createCompactSystemCommand()],
58
+ };
59
+ }
@@ -0,0 +1,33 @@
1
+ import { compactCommandContext } from '@robota-sdk/agent-framework';
2
+
3
+ import type {
4
+ ICommandHostContextWindow,
5
+ ICommandHostSessionAccess,
6
+ } from '@robota-sdk/agent-framework';
7
+ import type { ICommandResult } from '@robota-sdk/agent-interface-command';
8
+
9
+ function parseInstructions(args: string): string | undefined {
10
+ const instructions = args.trim();
11
+ return instructions.length > 0 ? instructions : undefined;
12
+ }
13
+
14
+ export async function executeCompactCommand(
15
+ context: ICommandHostContextWindow & ICommandHostSessionAccess,
16
+ args: string,
17
+ ): Promise<ICommandResult> {
18
+ const result = await compactCommandContext(context, parseInstructions(args));
19
+ const { before, after, beforeMessageCount, afterMessageCount } = result;
20
+ const removedMessages = beforeMessageCount - afterMessageCount;
21
+ const removedPercent =
22
+ beforeMessageCount > 0 ? Math.round((removedMessages / beforeMessageCount) * 100) : 0;
23
+ const message = [
24
+ 'Context compacted.',
25
+ ` Removed messages: ${removedMessages} (${removedPercent}% of total)`,
26
+ ` Context: ${Math.round(before.usedPercentage)}% → ${Math.round(after.usedPercentage)}%`,
27
+ ].join('\n');
28
+ return {
29
+ message,
30
+ success: true,
31
+ data: { before, after, beforeMessageCount, afterMessageCount },
32
+ };
33
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ CompactCommandSource,
3
+ createCompactCommandEntry,
4
+ createCompactCommandModule,
5
+ } from './compact-command-module.js';
6
+ export { executeCompactCommand } from './compact-command.js';