@robota-sdk/agent-command 3.0.0-beta.64

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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/node/index.cjs +30 -0
  3. package/dist/node/index.d.ts +293 -0
  4. package/dist/node/index.d.ts.map +1 -0
  5. package/dist/node/index.js +31 -0
  6. package/dist/node/index.js.map +1 -0
  7. package/package.json +48 -0
  8. package/src/agent/__tests__/agent-command.test.ts +504 -0
  9. package/src/agent/agent-command-module.ts +82 -0
  10. package/src/agent/agent-command-parser.ts +180 -0
  11. package/src/agent/agent-command.ts +235 -0
  12. package/src/agent/index.ts +7 -0
  13. package/src/background/__tests__/background-command-module.test.ts +255 -0
  14. package/src/background/background-command-module.ts +53 -0
  15. package/src/background/background-command.ts +63 -0
  16. package/src/background/index.ts +6 -0
  17. package/src/compact/__tests__/compact-command-module.test.ts +162 -0
  18. package/src/compact/compact-command-module.ts +51 -0
  19. package/src/compact/compact-command.ts +21 -0
  20. package/src/compact/index.ts +6 -0
  21. package/src/context/__tests__/context-command-module.test.ts +294 -0
  22. package/src/context/context-command-module.ts +54 -0
  23. package/src/context/context-command.ts +298 -0
  24. package/src/context/index.ts +6 -0
  25. package/src/exit/__tests__/exit-command-module.test.ts +35 -0
  26. package/src/exit/exit-command-module.ts +48 -0
  27. package/src/exit/exit-command.ts +10 -0
  28. package/src/exit/index.ts +6 -0
  29. package/src/help/__tests__/help-command-module.test.ts +106 -0
  30. package/src/help/help-command-module.ts +48 -0
  31. package/src/help/help-command.ts +9 -0
  32. package/src/help/index.ts +6 -0
  33. package/src/index.ts +20 -0
  34. package/src/language/__tests__/language-command-module.test.ts +105 -0
  35. package/src/language/index.ts +6 -0
  36. package/src/language/language-command-module.ts +56 -0
  37. package/src/language/language-command.ts +22 -0
  38. package/src/memory/__tests__/memory-command-module.test.ts +272 -0
  39. package/src/memory/index.ts +6 -0
  40. package/src/memory/memory-command-module.ts +57 -0
  41. package/src/memory/memory-command.ts +234 -0
  42. package/src/mode/__tests__/mode-command-module.test.ts +143 -0
  43. package/src/mode/index.ts +6 -0
  44. package/src/mode/mode-command-module.ts +56 -0
  45. package/src/mode/mode-command.ts +34 -0
  46. package/src/model/__tests__/model-command-module.test.ts +273 -0
  47. package/src/model/index.ts +6 -0
  48. package/src/model/model-command-module.ts +68 -0
  49. package/src/model/model-command.ts +40 -0
  50. package/src/permissions/__tests__/permissions-command-module.test.ts +164 -0
  51. package/src/permissions/index.ts +6 -0
  52. package/src/permissions/permissions-command-module.ts +56 -0
  53. package/src/permissions/permissions-command.ts +45 -0
  54. package/src/plugin/__tests__/plugin-command-module.test.ts +214 -0
  55. package/src/plugin/index.ts +7 -0
  56. package/src/plugin/plugin-command-module.ts +81 -0
  57. package/src/plugin/plugin-command.ts +230 -0
  58. package/src/provider/__tests__/provider-command-module.test.ts +488 -0
  59. package/src/provider/__tests__/provider-setup-flow.test.ts +43 -0
  60. package/src/provider/index.ts +30 -0
  61. package/src/provider/provider-command-execution.ts +150 -0
  62. package/src/provider/provider-command-module.ts +65 -0
  63. package/src/provider/provider-command-profile-lifecycle.ts +211 -0
  64. package/src/provider/provider-command-profile-operations.ts +198 -0
  65. package/src/provider/provider-command-profile.ts +109 -0
  66. package/src/provider/provider-command-setup.ts +104 -0
  67. package/src/provider/provider-setup-flow.ts +309 -0
  68. package/src/reset/__tests__/reset-command-module.test.ts +63 -0
  69. package/src/reset/index.ts +2 -0
  70. package/src/reset/reset-command-module.ts +49 -0
  71. package/src/reset/reset-command.ts +10 -0
  72. package/src/rewind/__tests__/rewind-command-module.test.ts +215 -0
  73. package/src/rewind/index.ts +2 -0
  74. package/src/rewind/rewind-command-module.ts +57 -0
  75. package/src/rewind/rewind-command.ts +184 -0
  76. package/src/session/__tests__/session-command-module.test.ts +339 -0
  77. package/src/session/index.ts +17 -0
  78. package/src/session/session-command-module.ts +168 -0
  79. package/src/session/session-command.ts +74 -0
  80. package/src/settings/index.ts +7 -0
  81. package/src/settings/settings-command-module.ts +50 -0
  82. package/src/skills/__tests__/skills-command-module.test.ts +157 -0
  83. package/src/skills/index.ts +6 -0
  84. package/src/skills/skills-command-module.ts +62 -0
  85. package/src/skills/skills-command.ts +110 -0
  86. package/src/statusline/__tests__/statusline-command-module.test.ts +95 -0
  87. package/src/statusline/index.ts +6 -0
  88. package/src/statusline/statusline-command-module.ts +56 -0
  89. package/src/statusline/statusline-command.ts +79 -0
  90. package/src/user-local/__tests__/user-local-command.test.ts +145 -0
  91. package/src/user-local/index.ts +13 -0
  92. package/src/user-local/user-local-command-constants.ts +5 -0
  93. package/src/user-local/user-local-command-module.ts +67 -0
  94. package/src/user-local/user-local-command.ts +205 -0
  95. package/src/user-local/user-local-memory-command.ts +147 -0
@@ -0,0 +1,234 @@
1
+ import type {
2
+ IAppendMemoryInput,
3
+ ICommandHostContext,
4
+ ICommandPendingMemoryStore,
5
+ ICommandProjectMemoryStore,
6
+ ICommandResult,
7
+ IMemoryEvent,
8
+ } from '@robota-sdk/agent-framework';
9
+ import {
10
+ MEMORY_COMMAND_USAGE,
11
+ createCommandMemoryStores,
12
+ hasSensitiveCommandMemoryContent,
13
+ isCommandMemoryType,
14
+ listCommandUsedMemoryReferences,
15
+ recordCommandMemoryEvent,
16
+ } from '@robota-sdk/agent-framework';
17
+
18
+ const SUBCOMMAND_INDEX = 0;
19
+ const TYPE_INDEX = 1;
20
+ const TOPIC_INDEX = 2;
21
+ const TEXT_START_INDEX = 3;
22
+
23
+ function usage(): ICommandResult {
24
+ return {
25
+ message: MEMORY_COMMAND_USAGE,
26
+ success: false,
27
+ };
28
+ }
29
+
30
+ function formatError(error: Error | string): ICommandResult {
31
+ return {
32
+ message: error instanceof Error ? error.message : String(error),
33
+ success: false,
34
+ };
35
+ }
36
+
37
+ function formatList(store: ICommandProjectMemoryStore): ICommandResult {
38
+ const summary = store.list();
39
+ const topics =
40
+ summary.topics.length > 0
41
+ ? summary.topics.map((topic) => `- ${topic.name}: ${topic.path}`).join('\n')
42
+ : '(none)';
43
+
44
+ return {
45
+ message: [
46
+ `Memory index: ${summary.indexPath}`,
47
+ `Topics directory: ${summary.topicsPath}`,
48
+ 'Topics:',
49
+ topics,
50
+ ].join('\n'),
51
+ success: true,
52
+ data: {
53
+ indexPath: summary.indexPath,
54
+ topicsPath: summary.topicsPath,
55
+ topicCount: summary.topics.length,
56
+ },
57
+ };
58
+ }
59
+
60
+ function formatShow(store: ICommandProjectMemoryStore, topic?: string): ICommandResult {
61
+ if (!topic || topic === 'index') {
62
+ const memory = store.loadStartupMemory();
63
+ return {
64
+ message: memory.content || '(empty memory index)',
65
+ success: true,
66
+ data: {
67
+ path: memory.path,
68
+ lineCount: memory.lineCount,
69
+ truncated: memory.truncated,
70
+ },
71
+ };
72
+ }
73
+
74
+ const content = store.readTopic(topic);
75
+ return {
76
+ message: content || `(empty memory topic: ${topic})`,
77
+ success: true,
78
+ data: { topic },
79
+ };
80
+ }
81
+
82
+ function parseAdd(args: readonly string[]): IAppendMemoryInput | undefined {
83
+ const type = args[TYPE_INDEX];
84
+ const topic = args[TOPIC_INDEX];
85
+ const text = args.slice(TEXT_START_INDEX).join(' ').trim();
86
+
87
+ if (!type || !isCommandMemoryType(type) || !topic || text.length === 0) return undefined;
88
+ return { type, topic, text };
89
+ }
90
+
91
+ function formatPending(store: ICommandPendingMemoryStore): ICommandResult {
92
+ const records = store.list('pending');
93
+ const lines =
94
+ records.length > 0
95
+ ? records.map(
96
+ (record) =>
97
+ `- ${record.id} ${record.type}/${record.topic} confidence=${record.confidence}: ${record.text}`,
98
+ )
99
+ : ['(no pending memory candidates)'];
100
+
101
+ return {
102
+ message: ['Pending memory candidates:', ...lines].join('\n'),
103
+ success: true,
104
+ data: { count: records.length },
105
+ };
106
+ }
107
+
108
+ function recordEvent(context: ICommandHostContext, event: Omit<IMemoryEvent, 'at'>): void {
109
+ recordCommandMemoryEvent(context, event);
110
+ }
111
+
112
+ function approvePending(
113
+ context: ICommandHostContext,
114
+ pendingStore: ICommandPendingMemoryStore,
115
+ memoryStore: ICommandProjectMemoryStore,
116
+ id: string | undefined,
117
+ ): ICommandResult {
118
+ if (!id) return usage();
119
+ try {
120
+ const approved = pendingStore.mark(id, 'approved', 'approved-by-user');
121
+ const saved = memoryStore.append(approved);
122
+ const record = pendingStore.mark(id, 'saved', 'approved-and-saved');
123
+ recordEvent(context, {
124
+ type: 'memory_candidate_approved',
125
+ candidateId: record.id,
126
+ topic: record.topic,
127
+ reason: 'approved-by-user',
128
+ });
129
+ recordEvent(context, {
130
+ type: 'memory_candidate_saved',
131
+ candidateId: record.id,
132
+ topic: record.topic,
133
+ reason: saved.deduplicated ? 'deduplicated' : 'approved-and-saved',
134
+ });
135
+ return {
136
+ message: saved.deduplicated
137
+ ? `Saved memory candidate ${id} was already present in ${saved.topicPath}`
138
+ : `Saved memory candidate ${id} to ${saved.topicPath}`,
139
+ success: true,
140
+ data: {
141
+ id,
142
+ status: record.status,
143
+ topic: saved.topic,
144
+ topicPath: saved.topicPath,
145
+ deduplicated: saved.deduplicated,
146
+ },
147
+ };
148
+ } catch (error) {
149
+ return formatError(error instanceof Error ? error : String(error));
150
+ }
151
+ }
152
+
153
+ function rejectPending(
154
+ context: ICommandHostContext,
155
+ pendingStore: ICommandPendingMemoryStore,
156
+ id: string | undefined,
157
+ ): ICommandResult {
158
+ if (!id) return usage();
159
+ try {
160
+ const record = pendingStore.mark(id, 'rejected', 'rejected-by-user');
161
+ recordEvent(context, {
162
+ type: 'memory_candidate_rejected',
163
+ candidateId: record.id,
164
+ topic: record.topic,
165
+ reason: 'rejected-by-user',
166
+ });
167
+ return {
168
+ message: `Rejected memory candidate ${id}`,
169
+ success: true,
170
+ data: { id, status: record.status },
171
+ };
172
+ } catch (error) {
173
+ return formatError(error instanceof Error ? error : String(error));
174
+ }
175
+ }
176
+
177
+ function formatUsed(context: ICommandHostContext): ICommandResult {
178
+ const references = listCommandUsedMemoryReferences(context);
179
+ const lines =
180
+ references.length > 0
181
+ ? references.map((reference) => {
182
+ const suffix = reference.truncated ? ' truncated=true' : '';
183
+ return `- ${reference.topic} score=${reference.score}${suffix}: ${reference.path}`;
184
+ })
185
+ : ['(no memory used in current turn)'];
186
+
187
+ return {
188
+ message: ['Used memory references:', ...lines].join('\n'),
189
+ success: true,
190
+ data: { count: references.length, references: [...references] },
191
+ };
192
+ }
193
+
194
+ export function executeMemoryCommand(
195
+ context: ICommandHostContext,
196
+ rawArgs: string,
197
+ ): ICommandResult {
198
+ const args = rawArgs.trim().split(/\s+/).filter(Boolean);
199
+ const subcommand = args[SUBCOMMAND_INDEX] ?? 'list';
200
+ const stores = createCommandMemoryStores(context);
201
+
202
+ if (subcommand === 'list') return formatList(stores.project);
203
+ if (subcommand === 'show') return formatShow(stores.project, args[TYPE_INDEX]);
204
+ if (subcommand === 'pending') return formatPending(stores.pending);
205
+ if (subcommand === 'approve')
206
+ return approvePending(context, stores.pending, stores.project, args[TYPE_INDEX]);
207
+ if (subcommand === 'reject') return rejectPending(context, stores.pending, args[TYPE_INDEX]);
208
+ if (subcommand === 'used') return formatUsed(context);
209
+ if (subcommand === 'add') {
210
+ const input = parseAdd(args);
211
+ if (!input) return usage();
212
+ if (hasSensitiveCommandMemoryContent(input.text)) {
213
+ return {
214
+ message: 'Refusing to save sensitive memory content.',
215
+ success: false,
216
+ };
217
+ }
218
+ const result = stores.project.append(input);
219
+ return {
220
+ message: result.deduplicated
221
+ ? `${input.type} memory already exists in ${result.topicPath}`
222
+ : `Saved ${input.type} memory to ${result.topicPath}`,
223
+ success: true,
224
+ data: {
225
+ indexPath: result.indexPath,
226
+ topicPath: result.topicPath,
227
+ topic: result.topic,
228
+ deduplicated: result.deduplicated,
229
+ },
230
+ };
231
+ }
232
+
233
+ return usage();
234
+ }
@@ -0,0 +1,143 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type {
3
+ ICommandHostContext,
4
+ IEditCheckpointRestoreResult,
5
+ } from '@robota-sdk/agent-framework';
6
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
7
+ import { createModeCommandModule } from '../mode-command-module.js';
8
+
9
+ type TPermissionModeName = 'plan' | 'default' | 'acceptEdits' | 'bypassPermissions';
10
+ type TSetPermissionModeSpy = ReturnType<typeof vi.fn<[nextMode: TPermissionModeName], void>>;
11
+
12
+ function createCheckpointResult(): IEditCheckpointRestoreResult {
13
+ return {
14
+ target: {
15
+ id: 'checkpoint_1',
16
+ sessionId: 'session_1',
17
+ sequence: 1,
18
+ prompt: 'edit files',
19
+ createdAt: '2026-05-03T00:00:00.000Z',
20
+ fileCount: 1,
21
+ },
22
+ restoredCheckpointCount: 0,
23
+ restoredFileCount: 0,
24
+ removedCheckpointCount: 0,
25
+ };
26
+ }
27
+
28
+ function createCommandHostContext(): ICommandHostContext & {
29
+ setPermissionMode: TSetPermissionModeSpy;
30
+ } {
31
+ let mode: TPermissionModeName = 'default';
32
+ const setPermissionMode = vi.fn((nextMode: TPermissionModeName) => {
33
+ mode = nextMode;
34
+ });
35
+
36
+ return {
37
+ getSession: () => {
38
+ throw new Error('mode command should use the permission mode adapter');
39
+ },
40
+ getCommandHostAdapters: () => ({
41
+ permissionMode: {
42
+ getPermissionMode: () => mode,
43
+ setPermissionMode,
44
+ listSessionAllowedTools: () => [],
45
+ },
46
+ }),
47
+ getContextState: () => ({
48
+ usedTokens: 0,
49
+ maxTokens: 1,
50
+ usedPercentage: 0,
51
+ remainingPercentage: 100,
52
+ }),
53
+ getAutoCompactThreshold: () => 0.835,
54
+ compactContext: async () => undefined,
55
+ getCwd: () => '/workspace',
56
+ listEditCheckpoints: () => [],
57
+ restoreEditCheckpoint: async () => createCheckpointResult(),
58
+ rollbackEditCheckpoint: async () => createCheckpointResult(),
59
+ getUsedMemoryReferences: () => [],
60
+ recordMemoryEvent: () => undefined,
61
+ listBackgroundTasks: () => [],
62
+ readBackgroundTaskLog: async () => ({ taskId: 'task_1', lines: [] }),
63
+ cancelBackgroundTask: async () => undefined,
64
+ closeBackgroundTask: async () => undefined,
65
+ setPermissionMode,
66
+ };
67
+ }
68
+
69
+ describe('createModeCommandModule', () => {
70
+ it('provides mode metadata and executable command from one module owner', () => {
71
+ const module = createModeCommandModule();
72
+ const command = module.systemCommands?.[0];
73
+ const entry = module.commandSources?.[0]?.getCommands()[0];
74
+
75
+ expect(module.name).toBe('agent-command-mode');
76
+ expect(entry).toEqual(
77
+ expect.objectContaining({
78
+ name: 'mode',
79
+ description: 'Show/change permission mode',
80
+ argumentHint: 'plan | default | acceptEdits | bypassPermissions',
81
+ source: 'mode',
82
+ modelInvocable: false,
83
+ }),
84
+ );
85
+ expect(entry?.subcommands?.map((subcommand) => subcommand.name)).toEqual([
86
+ 'plan',
87
+ 'default',
88
+ 'acceptEdits',
89
+ 'bypassPermissions',
90
+ ]);
91
+ expect(command).toEqual(
92
+ expect.objectContaining({
93
+ name: 'mode',
94
+ description: 'Show/change permission mode',
95
+ argumentHint: 'plan | default | acceptEdits | bypassPermissions',
96
+ lifecycle: 'inline',
97
+ modelInvocable: false,
98
+ }),
99
+ );
100
+ expect(command?.subcommands).toEqual(entry?.subcommands);
101
+ });
102
+
103
+ it('reports the current permission mode through the SDK command adapter', async () => {
104
+ const executor = new SystemCommandExecutor([
105
+ ...(createModeCommandModule().systemCommands ?? []),
106
+ ]);
107
+
108
+ const result = await executor.execute('mode', createCommandHostContext(), '');
109
+
110
+ expect(result?.success).toBe(true);
111
+ expect(result?.message).toBe('Current mode: default');
112
+ expect(result?.data?.mode).toBe('default');
113
+ });
114
+
115
+ it('updates valid permission modes through the SDK command adapter', async () => {
116
+ const executor = new SystemCommandExecutor([
117
+ ...(createModeCommandModule().systemCommands ?? []),
118
+ ]);
119
+ const context = createCommandHostContext();
120
+
121
+ const result = await executor.execute('mode', context, 'plan');
122
+
123
+ expect(result?.success).toBe(true);
124
+ expect(result?.message).toBe('Permission mode set to: plan');
125
+ expect(result?.data?.mode).toBe('plan');
126
+ expect(context.setPermissionMode).toHaveBeenCalledWith('plan');
127
+ });
128
+
129
+ it('rejects invalid permission modes without writing state', async () => {
130
+ const executor = new SystemCommandExecutor([
131
+ ...(createModeCommandModule().systemCommands ?? []),
132
+ ]);
133
+ const context = createCommandHostContext();
134
+
135
+ const result = await executor.execute('mode', context, 'invalid');
136
+
137
+ expect(result?.success).toBe(false);
138
+ expect(result?.message).toBe(
139
+ 'Invalid mode. Valid: plan | default | acceptEdits | bypassPermissions',
140
+ );
141
+ expect(context.setPermissionMode).not.toHaveBeenCalled();
142
+ });
143
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ createModeCommandEntry,
3
+ createModeCommandModule,
4
+ ModeCommandSource,
5
+ } from './mode-command-module.js';
6
+ export { executeModeCommand } from './mode-command.js';
@@ -0,0 +1,56 @@
1
+ import type {
2
+ ICommand,
3
+ ICommandModule,
4
+ ICommandSource,
5
+ ISystemCommand,
6
+ } from '@robota-sdk/agent-framework';
7
+ import {
8
+ buildPermissionModeSubcommands,
9
+ PERMISSION_MODE_ARGUMENT_HINT,
10
+ PERMISSION_MODE_COMMAND_DESCRIPTION,
11
+ } from '@robota-sdk/agent-framework';
12
+ import { executeModeCommand } from './mode-command.js';
13
+
14
+ export function createModeCommandEntry(): ICommand {
15
+ return {
16
+ name: 'mode',
17
+ displayName: 'Interaction Mode',
18
+ description: PERMISSION_MODE_COMMAND_DESCRIPTION,
19
+ source: 'mode',
20
+ argumentHint: PERMISSION_MODE_ARGUMENT_HINT,
21
+ subcommands: buildPermissionModeSubcommands('mode'),
22
+ modelInvocable: false,
23
+ };
24
+ }
25
+
26
+ function createModeSystemCommand(): ISystemCommand {
27
+ const entry = createModeCommandEntry();
28
+ return {
29
+ name: entry.name,
30
+ displayName: entry.displayName,
31
+ description: entry.description,
32
+ requiresPermission: false,
33
+ userInvocable: true,
34
+ modelInvocable: false,
35
+ argumentHint: entry.argumentHint,
36
+ subcommands: entry.subcommands,
37
+ lifecycle: 'inline',
38
+ execute: executeModeCommand,
39
+ };
40
+ }
41
+
42
+ export class ModeCommandSource implements ICommandSource {
43
+ readonly name = 'mode';
44
+
45
+ getCommands(): ICommand[] {
46
+ return [createModeCommandEntry()];
47
+ }
48
+ }
49
+
50
+ export function createModeCommandModule(): ICommandModule {
51
+ return {
52
+ name: 'agent-command-mode',
53
+ commandSources: [new ModeCommandSource()],
54
+ systemCommands: [createModeSystemCommand()],
55
+ };
56
+ }
@@ -0,0 +1,34 @@
1
+ import type { ICommandHostContext, ICommandResult } from '@robota-sdk/agent-framework';
2
+ import {
3
+ formatInvalidPermissionModeMessage,
4
+ isPermissionMode,
5
+ parsePermissionModeArgument,
6
+ readCommandPermissionMode,
7
+ writeCommandPermissionMode,
8
+ } from '@robota-sdk/agent-framework';
9
+
10
+ export function executeModeCommand(context: ICommandHostContext, args: string): ICommandResult {
11
+ const arg = parsePermissionModeArgument(args);
12
+ if (arg === undefined) {
13
+ const mode = readCommandPermissionMode(context);
14
+ return {
15
+ message: `Current mode: ${mode}`,
16
+ success: true,
17
+ data: { mode },
18
+ };
19
+ }
20
+
21
+ if (!isPermissionMode(arg)) {
22
+ return {
23
+ message: formatInvalidPermissionModeMessage(),
24
+ success: false,
25
+ };
26
+ }
27
+
28
+ writeCommandPermissionMode(context, arg);
29
+ return {
30
+ message: `Permission mode set to: ${arg}`,
31
+ success: true,
32
+ data: { mode: arg },
33
+ };
34
+ }