@ai-setting/roy-agent-cli 1.0.0

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 (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. package/src/util/which.ts +55 -0
@@ -0,0 +1,434 @@
1
+ /**
2
+ * @fileoverview QueryExecutor Test
3
+ *
4
+ * TDD: 测试 QueryExecutor 的核心功能
5
+ */
6
+
7
+ import { describe, test, expect, vi, beforeEach, afterEach } from "bun:test";
8
+ import { QueryExecutor } from "./query-executor";
9
+ import { OutputService } from "../../services/output.service";
10
+ import { StreamOutputService, resetStreamAbort } from "../../services/stream-output.service";
11
+ import { ContextHandlerService } from "../../services/context-handler.service";
12
+
13
+ // Mock types
14
+ interface MockEnvironment {
15
+ subscribeTo: ReturnType<typeof vi.fn>;
16
+ getComponent: ReturnType<typeof vi.fn>;
17
+ }
18
+
19
+ interface MockSessionComponent {
20
+ setSummaryComponents: ReturnType<typeof vi.fn>;
21
+ }
22
+
23
+ describe("QueryExecutor", () => {
24
+ let queryExecutor: QueryExecutor;
25
+ let mockEnv: MockEnvironment;
26
+ let mockSessionComponent: MockSessionComponent;
27
+ let mockOutput: OutputService;
28
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
29
+ let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
30
+
31
+ beforeEach(() => {
32
+ // 重置全局 abort 状态,避免被其他测试污染
33
+ resetStreamAbort();
34
+
35
+ // Create mock Environment
36
+ const mockUnsubscribe = vi.fn();
37
+ mockEnv = {
38
+ subscribeTo: vi.fn().mockReturnValue(mockUnsubscribe),
39
+ getComponent: vi.fn(),
40
+ };
41
+
42
+ mockSessionComponent = {
43
+ setSummaryComponents: vi.fn(),
44
+ };
45
+
46
+ mockOutput = new OutputService();
47
+ mockOutput.configure({ quiet: true });
48
+
49
+ // Spy on console
50
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
51
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
52
+
53
+ queryExecutor = new QueryExecutor({
54
+ env: mockEnv as any,
55
+ sessionComponent: mockSessionComponent as any,
56
+ output: mockOutput,
57
+ quiet: true,
58
+ });
59
+ });
60
+
61
+ afterEach(() => {
62
+ vi.restoreAllMocks();
63
+ queryExecutor.dispose();
64
+ });
65
+
66
+ describe("constructor", () => {
67
+ test("should create instance with options", () => {
68
+ expect(queryExecutor).toBeDefined();
69
+ });
70
+ });
71
+
72
+ describe("initSummaryAgent()", () => {
73
+ test("should call setSummaryComponents on sessionComponent", () => {
74
+ // Given
75
+ const mockLLMComponent = {};
76
+ const mockPromptComponent = {};
77
+
78
+ // When
79
+ queryExecutor.initSummaryAgent(
80
+ mockLLMComponent as any,
81
+ mockPromptComponent as any
82
+ );
83
+
84
+ // Then
85
+ expect(mockSessionComponent.setSummaryComponents).toHaveBeenCalledWith(
86
+ mockPromptComponent,
87
+ mockLLMComponent
88
+ );
89
+ });
90
+ });
91
+
92
+ describe("subscribeToEvents()", () => {
93
+ test("should call env.subscribeTo with correct events", () => {
94
+ // Given
95
+ const options = {
96
+ showReasoning: true,
97
+ showToolCalls: false,
98
+ showToolResults: false,
99
+ };
100
+
101
+ // When
102
+ queryExecutor.subscribeToEvents(options);
103
+
104
+ // Then
105
+ expect(mockEnv.subscribeTo).toHaveBeenCalledWith(
106
+ expect.arrayContaining([
107
+ "llm.start",
108
+ "llm.text",
109
+ "llm.reasoning",
110
+ "llm.tool_call",
111
+ "llm.completed",
112
+ "llm.error",
113
+ "tool.result",
114
+ "tool.error",
115
+ "context.threshold_exceeded",
116
+ "context.compacting",
117
+ "context.compacted",
118
+ ]),
119
+ expect.any(Function)
120
+ );
121
+ });
122
+
123
+ test("should only subscribe once", () => {
124
+ // Given
125
+ const options = {
126
+ showReasoning: true,
127
+ showToolCalls: false,
128
+ showToolResults: false,
129
+ };
130
+
131
+ // When
132
+ queryExecutor.subscribeToEvents(options);
133
+ queryExecutor.subscribeToEvents(options);
134
+
135
+ // Then
136
+ expect(mockEnv.subscribeTo).toHaveBeenCalledTimes(1);
137
+ });
138
+ });
139
+
140
+ describe("dispose()", () => {
141
+ test("should call unsubscribe when disposed", () => {
142
+ // Given
143
+ const options = {
144
+ showReasoning: false,
145
+ showToolCalls: false,
146
+ showToolResults: false,
147
+ };
148
+ queryExecutor.subscribeToEvents(options);
149
+
150
+ // When
151
+ queryExecutor.dispose();
152
+
153
+ // Then
154
+ expect(mockEnv.subscribeTo).toHaveBeenCalled(); // unsubscribe was called via dispose
155
+ });
156
+
157
+ test("should handle dispose when not subscribed", () => {
158
+ // When & Then - should not throw
159
+ expect(() => queryExecutor.dispose()).not.toThrow();
160
+ });
161
+ });
162
+
163
+ describe("getFullText()", () => {
164
+ test("should return empty string when no stream service", () => {
165
+ // When
166
+ const result = queryExecutor.getFullText();
167
+
168
+ // Then
169
+ expect(result).toBe("");
170
+ });
171
+ });
172
+ });
173
+
174
+ describe("QueryExecutor compact", () => {
175
+ let queryExecutor: QueryExecutor;
176
+ let mockEnv: {
177
+ subscribeTo: ReturnType<typeof vi.fn>;
178
+ getComponent: ReturnType<typeof vi.fn>;
179
+ };
180
+ let mockSessionComponent: {
181
+ setSummaryComponents: ReturnType<typeof vi.fn>;
182
+ compact: ReturnType<typeof vi.fn>;
183
+ };
184
+ let mockOutput: OutputService;
185
+
186
+ beforeEach(() => {
187
+ const mockUnsubscribe = vi.fn();
188
+ mockEnv = {
189
+ subscribeTo: vi.fn().mockReturnValue(mockUnsubscribe),
190
+ getComponent: vi.fn().mockReturnValue({
191
+ pushEnvEvent: vi.fn(),
192
+ }),
193
+ };
194
+
195
+ mockSessionComponent = {
196
+ setSummaryComponents: vi.fn(),
197
+ compact: vi.fn().mockResolvedValue({
198
+ checkpoint: {
199
+ id: "cp_test_123",
200
+ sessionId: "s_test",
201
+ summary: "Test summary",
202
+ createdAt: new Date().toISOString(),
203
+ },
204
+ deletedMessageCount: 5,
205
+ }),
206
+ };
207
+
208
+ mockOutput = new OutputService();
209
+ mockOutput.configure({ quiet: true });
210
+
211
+ queryExecutor = new QueryExecutor({
212
+ env: mockEnv as any,
213
+ sessionComponent: mockSessionComponent as any,
214
+ output: mockOutput,
215
+ quiet: true,
216
+ });
217
+ });
218
+
219
+ afterEach(() => {
220
+ vi.restoreAllMocks();
221
+ queryExecutor.dispose();
222
+ });
223
+
224
+ describe("compact()", () => {
225
+ test("should call sessionComponent.compact with sessionId", async () => {
226
+ // Given
227
+ const sessionId = "s_test_123";
228
+
229
+ // When
230
+ await queryExecutor.compact(sessionId);
231
+
232
+ // Then
233
+ expect(mockSessionComponent.compact).toHaveBeenCalledWith(
234
+ sessionId,
235
+ expect.objectContaining({
236
+ summary: expect.any(String),
237
+ })
238
+ );
239
+ });
240
+
241
+ test("should publish context.compacting event before compact", async () => {
242
+ // Given
243
+ const sessionId = "s_test_123";
244
+ const pushEnvEvent = vi.fn();
245
+ (mockEnv.getComponent as any).mockReturnValue({
246
+ pushEnvEvent,
247
+ });
248
+
249
+ // When
250
+ await queryExecutor.compact(sessionId);
251
+
252
+ // Then
253
+ expect(pushEnvEvent).toHaveBeenCalledWith(
254
+ expect.objectContaining({
255
+ type: "context.compacting",
256
+ })
257
+ );
258
+ });
259
+
260
+ test("should publish context.compacted event after compact", async () => {
261
+ // Given
262
+ const sessionId = "s_test_123";
263
+ const pushEnvEvent = vi.fn();
264
+ (mockEnv.getComponent as any).mockReturnValue({
265
+ pushEnvEvent,
266
+ });
267
+
268
+ // When
269
+ await queryExecutor.compact(sessionId);
270
+
271
+ // Then
272
+ expect(pushEnvEvent).toHaveBeenCalledWith(
273
+ expect.objectContaining({
274
+ type: "context.compacted",
275
+ payload: expect.objectContaining({
276
+ checkpointId: "cp_test_123",
277
+ messagesCompacted: 5,
278
+ }),
279
+ })
280
+ );
281
+ });
282
+
283
+ test("should return compact result", async () => {
284
+ // Given
285
+ const sessionId = "s_test_123";
286
+
287
+ // When
288
+ const result = await queryExecutor.compact(sessionId);
289
+
290
+ // Then
291
+ expect(result).toEqual({
292
+ success: true,
293
+ checkpoint: expect.objectContaining({
294
+ id: "cp_test_123",
295
+ }),
296
+ deletedMessageCount: 5,
297
+ });
298
+ });
299
+
300
+ test("should return error result when compact fails", async () => {
301
+ // Given
302
+ const sessionId = "s_test_123";
303
+ mockSessionComponent.compact = vi.fn().mockRejectedValue(
304
+ new Error("Compact failed")
305
+ );
306
+
307
+ // When
308
+ const result = await queryExecutor.compact(sessionId);
309
+
310
+ // Then
311
+ expect(result).toEqual({
312
+ success: false,
313
+ error: "Compact failed",
314
+ });
315
+ });
316
+ });
317
+ });
318
+
319
+ describe("QueryExecutor Event Handling", () => {
320
+ let queryExecutor: QueryExecutor;
321
+ let mockEnv: MockEnvironment;
322
+ let mockSessionComponent: MockSessionComponent;
323
+ let mockOutput: OutputService;
324
+ let eventHandler: (event: any) => void;
325
+ let mockUnsubscribe: ReturnType<typeof vi.fn>;
326
+
327
+ beforeEach(() => {
328
+ mockUnsubscribe = vi.fn();
329
+ mockEnv = {
330
+ subscribeTo: vi.fn().mockImplementation((events, handler) => {
331
+ eventHandler = handler;
332
+ return mockUnsubscribe;
333
+ }),
334
+ getComponent: vi.fn(),
335
+ };
336
+
337
+ mockSessionComponent = {
338
+ setSummaryComponents: vi.fn(),
339
+ };
340
+
341
+ mockOutput = new OutputService();
342
+ mockOutput.configure({ quiet: true });
343
+
344
+ vi.spyOn(console, "log").mockImplementation(() => {});
345
+ vi.spyOn(console, "error").mockImplementation(() => {});
346
+
347
+ queryExecutor = new QueryExecutor({
348
+ env: mockEnv as any,
349
+ sessionComponent: mockSessionComponent as any,
350
+ output: mockOutput,
351
+ quiet: true,
352
+ });
353
+ });
354
+
355
+ afterEach(() => {
356
+ vi.restoreAllMocks();
357
+ queryExecutor.dispose();
358
+ });
359
+
360
+ describe("handleEvent", () => {
361
+ test("should handle llm.tool_call event when showToolCalls is true", () => {
362
+ // Given
363
+ queryExecutor.subscribeToEvents({
364
+ showReasoning: false,
365
+ showToolCalls: true,
366
+ showToolResults: false,
367
+ });
368
+
369
+ const event = {
370
+ type: "llm.tool_call",
371
+ payload: {
372
+ toolCall: {
373
+ name: "testTool",
374
+ arguments: '{"input": "test"}',
375
+ },
376
+ },
377
+ };
378
+
379
+ // When
380
+ eventHandler(event);
381
+
382
+ // Then
383
+ expect(console.log).toHaveBeenCalled();
384
+ });
385
+
386
+ test("should not log tool_call when showToolCalls is false", () => {
387
+ // Given
388
+ queryExecutor.subscribeToEvents({
389
+ showReasoning: false,
390
+ showToolCalls: false,
391
+ showToolResults: false,
392
+ });
393
+
394
+ const event = {
395
+ type: "llm.tool_call",
396
+ payload: {
397
+ toolCall: {
398
+ name: "testTool",
399
+ arguments: '{"input": "test"}',
400
+ },
401
+ },
402
+ };
403
+
404
+ // When
405
+ eventHandler(event);
406
+
407
+ // Then
408
+ expect(console.log).not.toHaveBeenCalled();
409
+ });
410
+
411
+ test("should handle tool.error event", () => {
412
+ // Given
413
+ queryExecutor.subscribeToEvents({
414
+ showReasoning: false,
415
+ showToolCalls: false,
416
+ showToolResults: false,
417
+ });
418
+
419
+ const event = {
420
+ type: "tool.error",
421
+ payload: {
422
+ toolName: "failingTool",
423
+ error: "Tool execution failed",
424
+ },
425
+ };
426
+
427
+ // When
428
+ eventHandler(event);
429
+
430
+ // Then
431
+ expect(console.error).toHaveBeenCalled();
432
+ });
433
+ });
434
+ });