@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,501 @@
1
+ import { describe, test, expect, beforeEach } from "bun:test";
2
+ import { ContextHandlerService, type HandleQueryOptions } from "./context-handler.service";
3
+ import { ContextError, ErrorCodes, getTracerProvider, resetTracerProvider, type OTelTracer } from "@ai-setting/roy-agent-core";
4
+ import type { Environment, AgentContext } from "@ai-setting/roy-agent-core";
5
+ import type { SessionComponent, SessionCheckpoint } from "@ai-setting/roy-agent-core";
6
+
7
+ // Mock checkpoint
8
+ const mockCheckpoint: SessionCheckpoint = {
9
+ id: "cp_test123",
10
+ messageIndex: 10,
11
+ title: "Test Checkpoint",
12
+ summary: "Summary of conversation",
13
+ processKeyPoints: ["Point 1", "Point 2"],
14
+ currentState: "Current working state",
15
+ nextSteps: ["Next step 1", "Next step 2"],
16
+ userIntents: [],
17
+ messageCountBefore: 15,
18
+ createdAt: Date.now(),
19
+ type: "compact",
20
+ };
21
+
22
+ describe("ContextHandlerService", () => {
23
+ let mockEnv: Environment;
24
+ let mockSessionComponent: SessionComponent;
25
+ let contextHandler: ContextHandlerService;
26
+ let handleQueryMock: any;
27
+ let pushEnvEventMock: any;
28
+ let compactMock: any;
29
+
30
+ beforeEach(() => {
31
+ // 清除环境变量中的 trace context,避免继承外部上下文
32
+ delete process.env['TRACEPARENT'];
33
+ delete process.env['TRACE_ID'];
34
+ delete process.env['LOG_TRACE_REQUEST_ID'];
35
+
36
+ // 重置 tracer provider,确保干净的上下文
37
+ resetTracerProvider();
38
+
39
+ // Create fresh mocks
40
+ handleQueryMock = async () => { throw new Error("Not mocked"); };
41
+ pushEnvEventMock = () => {};
42
+ compactMock = async () => { throw new Error("Not mocked"); };
43
+
44
+ mockEnv = {
45
+ handle_query: handleQueryMock,
46
+ pushEnvEvent: pushEnvEventMock,
47
+ getComponent: () => undefined,
48
+ subscribe: () => () => {},
49
+ subscribeTo: () => () => {},
50
+ } as unknown as Environment;
51
+
52
+ // Mock generateCompactHint for two-phase compact
53
+ const generateCompactHintMock = async () => "Generated scenario hint for testing";
54
+
55
+ mockSessionComponent = {
56
+ compact: compactMock,
57
+ generateCompactHint: generateCompactHintMock,
58
+ get: async () => undefined,
59
+ addMessage: async () => undefined,
60
+ } as unknown as SessionComponent;
61
+
62
+ contextHandler = new ContextHandlerService(
63
+ mockEnv,
64
+ mockSessionComponent,
65
+ { maxRetries: 1, autoCompact: true }
66
+ );
67
+ });
68
+
69
+ describe("handleQueryWithContext", () => {
70
+ test("should return result when handle_query succeeds", async () => {
71
+ handleQueryMock = async () => "Query completed";
72
+ mockEnv.handle_query = handleQueryMock;
73
+
74
+ const result = await contextHandler.handleQueryWithContext(
75
+ "Test query",
76
+ { sessionId: "session-123" }
77
+ );
78
+
79
+ expect(result).toBe("Query completed");
80
+ });
81
+
82
+ test("should throw non-ContextError as-is", async () => {
83
+ handleQueryMock = async () => { throw new Error("Some other error"); };
84
+ mockEnv.handle_query = handleQueryMock;
85
+
86
+ await expect(
87
+ contextHandler.handleQueryWithContext("Test query")
88
+ ).rejects.toThrow("Some other error");
89
+ });
90
+
91
+ test("should compact and retry when ContextError is thrown", async () => {
92
+ let callCount = 0;
93
+ handleQueryMock = async () => {
94
+ callCount++;
95
+ if (callCount === 1) {
96
+ throw new ContextError(
97
+ "Context threshold exceeded",
98
+ ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
99
+ "session-123",
100
+ { promptTokens: 10000, completionTokens: 500, totalTokens: 10500 },
101
+ 128000
102
+ );
103
+ }
104
+ return "Retry result";
105
+ };
106
+ mockEnv.handle_query = handleQueryMock;
107
+
108
+ compactMock = async () => ({
109
+ checkpoint: mockCheckpoint,
110
+ deletedMessageCount: 15,
111
+ remainingMessageCount: 5,
112
+ checkpointCount: 2,
113
+ });
114
+ mockSessionComponent.compact = compactMock;
115
+
116
+ const result = await contextHandler.handleQueryWithContext(
117
+ "Test query",
118
+ { sessionId: "session-123" }
119
+ );
120
+
121
+ expect(result).toBe("Retry result");
122
+ expect(callCount).toBe(2);
123
+ });
124
+
125
+ test("should publish events when compacting", async () => {
126
+ const events: any[] = [];
127
+ pushEnvEventMock = (event: any) => events.push(event);
128
+ mockEnv.pushEnvEvent = pushEnvEventMock;
129
+
130
+ let callCount = 0;
131
+ handleQueryMock = async () => {
132
+ callCount++;
133
+ if (callCount === 1) {
134
+ throw new ContextError(
135
+ "Context threshold exceeded",
136
+ ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
137
+ "session-123"
138
+ );
139
+ }
140
+ return "Retry result";
141
+ };
142
+ mockEnv.handle_query = handleQueryMock;
143
+
144
+ compactMock = async () => ({
145
+ checkpoint: mockCheckpoint,
146
+ deletedMessageCount: 15,
147
+ remainingMessageCount: 5,
148
+ checkpointCount: 2,
149
+ });
150
+ mockSessionComponent.compact = compactMock;
151
+
152
+ await contextHandler.handleQueryWithContext(
153
+ "Test query",
154
+ { sessionId: "session-123" }
155
+ );
156
+
157
+ // Verify events were published
158
+ expect(events.some(e => e.type === "context.threshold_exceeded")).toBe(true);
159
+ expect(events.some(e => e.type === "context.compacting")).toBe(true);
160
+ expect(events.some(e => e.type === "context.compacted")).toBe(true);
161
+ });
162
+
163
+ test("should throw after max retries exceeded", async () => {
164
+ handleQueryMock = async () => {
165
+ throw new ContextError(
166
+ "Context threshold exceeded",
167
+ ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
168
+ "session-123"
169
+ );
170
+ };
171
+ mockEnv.handle_query = handleQueryMock;
172
+
173
+ compactMock = async () => ({
174
+ checkpoint: mockCheckpoint,
175
+ deletedMessageCount: 15,
176
+ remainingMessageCount: 5,
177
+ checkpointCount: 2,
178
+ });
179
+ mockSessionComponent.compact = compactMock;
180
+
181
+ let error: any;
182
+ try {
183
+ await contextHandler.handleQueryWithContext("Test query", { sessionId: "session-123" });
184
+ } catch (e) {
185
+ error = e;
186
+ }
187
+
188
+ expect(error).toBeInstanceOf(ContextError);
189
+ });
190
+
191
+ test("should not compact when autoCompact is disabled", async () => {
192
+ contextHandler = new ContextHandlerService(
193
+ mockEnv,
194
+ mockSessionComponent,
195
+ { maxRetries: 1, autoCompact: false }
196
+ );
197
+
198
+ handleQueryMock = async () => {
199
+ throw new ContextError(
200
+ "Context threshold exceeded",
201
+ ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
202
+ "session-123"
203
+ );
204
+ };
205
+ mockEnv.handle_query = handleQueryMock;
206
+
207
+ let compactCalled = false;
208
+ compactMock = async () => {
209
+ compactCalled = true;
210
+ return {
211
+ checkpoint: mockCheckpoint,
212
+ deletedMessageCount: 15,
213
+ remainingMessageCount: 5,
214
+ checkpointCount: 2,
215
+ };
216
+ };
217
+ mockSessionComponent.compact = compactMock;
218
+
219
+ let error: any;
220
+ try {
221
+ await contextHandler.handleQueryWithContext("Test query");
222
+ } catch (e) {
223
+ error = e;
224
+ }
225
+
226
+ expect(error).toBeInstanceOf(ContextError);
227
+ expect(compactCalled).toBe(false);
228
+ });
229
+ });
230
+
231
+ describe("compactSession", () => {
232
+ test("should return error when no sessionId provided", async () => {
233
+ const result = await contextHandler.compactSession(undefined);
234
+
235
+ expect(result.success).toBe(false);
236
+ expect(result.error).toBe("No session ID provided");
237
+ });
238
+
239
+ test("should compact session and return new query", async () => {
240
+ compactMock = async () => ({
241
+ checkpoint: mockCheckpoint,
242
+ deletedMessageCount: 15,
243
+ remainingMessageCount: 5,
244
+ checkpointCount: 2,
245
+ });
246
+ mockSessionComponent.compact = compactMock;
247
+
248
+ const result = await contextHandler.compactSession("session-123", "User's new query");
249
+
250
+ expect(result.success).toBe(true);
251
+ expect(result.checkpoint).toEqual(mockCheckpoint);
252
+ expect(result.newQuery).toContain("【会话历史摘要】");
253
+ expect(result.newQuery).toContain("Summary of conversation");
254
+ expect(result.newQuery).toContain("【已完成的工作】");
255
+ expect(result.newQuery).toContain("【用户新输入】");
256
+ expect(result.newQuery).toContain("User's new query");
257
+ });
258
+
259
+ test("should handle compact failure", async () => {
260
+ compactMock = async () => { throw new Error("Compact failed"); };
261
+ mockSessionComponent.compact = compactMock;
262
+
263
+ const result = await contextHandler.compactSession("session-123");
264
+
265
+ expect(result.success).toBe(false);
266
+ expect(result.error).toBe("Compact failed");
267
+ });
268
+
269
+ test("should construct query without original query", async () => {
270
+ compactMock = async () => ({
271
+ checkpoint: mockCheckpoint,
272
+ deletedMessageCount: 15,
273
+ remainingMessageCount: 5,
274
+ checkpointCount: 2,
275
+ });
276
+ mockSessionComponent.compact = compactMock;
277
+
278
+ const result = await contextHandler.compactSession("session-123");
279
+
280
+ expect(result.newQuery).not.toContain("【用户新输入】");
281
+ });
282
+ });
283
+
284
+ describe("Two-Phase Compact", () => {
285
+ test("should call generateCompactHint before compact in two-phase flow", async () => {
286
+ const events: any[] = [];
287
+ pushEnvEventMock = (event: any) => events.push(event);
288
+ mockEnv.pushEnvEvent = pushEnvEventMock;
289
+
290
+ let generateHintCalled = false;
291
+ let hintValue = "Scenario hint for task execution";
292
+ const generateCompactHintMock = async () => {
293
+ generateHintCalled = true;
294
+ return hintValue;
295
+ };
296
+ mockSessionComponent.generateCompactHint = generateCompactHintMock;
297
+
298
+ compactMock = async (sessionId: string, options: any) => {
299
+ // Verify scenarioHint was passed to compact
300
+ return {
301
+ checkpoint: {
302
+ ...mockCheckpoint,
303
+ recentMessages: [
304
+ { role: "user", content: "Test question" },
305
+ { role: "assistant", content: "Test answer" },
306
+ ],
307
+ },
308
+ deletedMessageCount: 15,
309
+ remainingMessageCount: 5,
310
+ checkpointCount: 2,
311
+ };
312
+ };
313
+ mockSessionComponent.compact = compactMock;
314
+
315
+ const result = await contextHandler.compactSessionWithTwoPhases("session-123", "Test query");
316
+
317
+ expect(result.success).toBe(true);
318
+ expect(generateHintCalled).toBe(true);
319
+
320
+ // Verify events were published
321
+ expect(events.some(e => e.type === "context.compacting")).toBe(true);
322
+ expect(events.some(e => e.type === "context.hint_generated")).toBe(true);
323
+ expect(events.some(e => e.type === "context.compacted")).toBe(true);
324
+
325
+ // Verify hint_generated event contains hint info
326
+ const hintEvent = events.find(e => e.type === "context.hint_generated");
327
+ expect(hintEvent?.payload?.hintLength).toBe(hintValue.length);
328
+ });
329
+
330
+ test("should continue with empty hint when generateCompactHint fails", async () => {
331
+ const events: any[] = [];
332
+ pushEnvEventMock = (event: any) => events.push(event);
333
+ mockEnv.pushEnvEvent = pushEnvEventMock;
334
+
335
+ // Make generateCompactHint fail
336
+ const generateCompactHintMock = async () => {
337
+ throw new Error("LLM not available");
338
+ };
339
+ mockSessionComponent.generateCompactHint = generateCompactHintMock;
340
+
341
+ compactMock = async () => ({
342
+ checkpoint: mockCheckpoint,
343
+ deletedMessageCount: 15,
344
+ remainingMessageCount: 5,
345
+ checkpointCount: 2,
346
+ });
347
+ mockSessionComponent.compact = compactMock;
348
+
349
+ const result = await contextHandler.compactSessionWithTwoPhases("session-123", "Test query");
350
+
351
+ expect(result.success).toBe(true);
352
+
353
+ // Verify hint_generated event was still published (with empty hint)
354
+ const hintEvent = events.find(e => e.type === "context.hint_generated");
355
+ expect(hintEvent?.payload?.hintLength).toBe(0);
356
+ });
357
+
358
+ test("should include scenarioHintUsed in compacted event", async () => {
359
+ const events: any[] = [];
360
+ pushEnvEventMock = (event: any) => events.push(event);
361
+ mockEnv.pushEnvEvent = pushEnvEventMock;
362
+
363
+ const generateCompactHintMock = async () => "Test scenario hint";
364
+ mockSessionComponent.generateCompactHint = generateCompactHintMock;
365
+
366
+ compactMock = async () => ({
367
+ checkpoint: mockCheckpoint,
368
+ deletedMessageCount: 15,
369
+ remainingMessageCount: 5,
370
+ checkpointCount: 2,
371
+ });
372
+ mockSessionComponent.compact = compactMock;
373
+
374
+ await contextHandler.compactSessionWithTwoPhases("session-123");
375
+
376
+ const compactedEvent = events.find(e => e.type === "context.compacted");
377
+ expect(compactedEvent?.payload?.scenarioHintUsed).toBe(true);
378
+ });
379
+
380
+ test("compactSession should call compactSessionWithTwoPhases", async () => {
381
+ let twoPhaseCalled = false;
382
+ const originalCompactSessionWithTwoPhases = (contextHandler as any).compactSessionWithTwoPhases.bind(contextHandler);
383
+ (contextHandler as any).compactSessionWithTwoPhases = async (sessionId?: string, originalQuery?: string) => {
384
+ twoPhaseCalled = true;
385
+ return originalCompactSessionWithTwoPhases(sessionId, originalQuery);
386
+ };
387
+
388
+ const generateCompactHintMock = async () => "Test hint";
389
+ mockSessionComponent.generateCompactHint = generateCompactHintMock;
390
+ mockSessionComponent.compact = async () => ({
391
+ checkpoint: mockCheckpoint,
392
+ deletedMessageCount: 15,
393
+ remainingMessageCount: 5,
394
+ checkpointCount: 2,
395
+ });
396
+
397
+ await contextHandler.compactSession("session-123");
398
+
399
+ expect(twoPhaseCalled).toBe(true);
400
+ });
401
+ });
402
+
403
+ describe("Trace Integration", () => {
404
+ test("should generate trace id when handleQueryWithContext is called", async () => {
405
+ // Get tracer from provider
406
+ const provider = getTracerProvider();
407
+ await provider.initialize();
408
+ const tracer = provider.getTracer("roy-tracer");
409
+
410
+ handleQueryMock = async () => "Success";
411
+ mockEnv.handle_query = handleQueryMock;
412
+
413
+ // 在调用期间捕获上下文
414
+ let capturedContext: any;
415
+ const originalHandleQuery = handleQueryMock;
416
+ handleQueryMock = async () => {
417
+ capturedContext = tracer.getCurrentContext();
418
+ return originalHandleQuery();
419
+ };
420
+ mockEnv.handle_query = handleQueryMock;
421
+
422
+ // Execute query
423
+ const result = await contextHandler.handleQueryWithContext(
424
+ "Test query",
425
+ { sessionId: "session-123" }
426
+ );
427
+
428
+ expect(result).toBe("Success");
429
+
430
+ // Verify tracer created spans
431
+ expect(tracer).toBeDefined();
432
+ expect(tracer.name).toBe("roy-tracer");
433
+
434
+ // Verify context was captured during execution
435
+ expect(capturedContext).toBeDefined();
436
+ expect(capturedContext?.traceId).toBeDefined();
437
+ expect(typeof capturedContext?.traceId).toBe("string");
438
+ console.log("Generated trace ID:", capturedContext?.traceId);
439
+ });
440
+
441
+ test("should use provided trace id in context", async () => {
442
+ // Get tracer from provider
443
+ const provider = getTracerProvider();
444
+ await provider.initialize();
445
+ const tracer = provider.getTracer("roy-tracer");
446
+
447
+ // 在调用期间捕获上下文
448
+ let capturedContext: any;
449
+ handleQueryMock = async () => {
450
+ capturedContext = tracer.getCurrentContext();
451
+ return "Success with custom trace";
452
+ };
453
+ mockEnv.handle_query = handleQueryMock;
454
+
455
+ // Execute with custom trace id in metadata
456
+ const result = await contextHandler.handleQueryWithContext(
457
+ "Test query",
458
+ {
459
+ sessionId: "session-123",
460
+ metadata: {
461
+ traceId: "custom_trace_12345"
462
+ }
463
+ }
464
+ );
465
+
466
+ expect(result).toBe("Success with custom trace");
467
+
468
+ // Verify context was captured during execution
469
+ expect(capturedContext).toBeDefined();
470
+ expect(capturedContext?.traceId).toBeDefined();
471
+ });
472
+
473
+ test("should start span for query execution", async () => {
474
+ // Get tracer from provider
475
+ const provider = getTracerProvider();
476
+ await provider.initialize();
477
+ const tracer = provider.getTracer("roy-tracer");
478
+
479
+ // 在调用期间捕获上下文
480
+ let capturedContext: any;
481
+ handleQueryMock = async () => {
482
+ capturedContext = tracer.getCurrentContext();
483
+ return "Span test";
484
+ };
485
+ mockEnv.handle_query = handleQueryMock;
486
+
487
+ // Execute query
488
+ await contextHandler.handleQueryWithContext(
489
+ "Test query",
490
+ { sessionId: "session-123" }
491
+ );
492
+
493
+ // Verify span was created during execution
494
+ expect(capturedContext).toBeDefined();
495
+ expect(capturedContext?.traceId).toBeDefined();
496
+ expect(capturedContext?.spanId).toBeDefined();
497
+
498
+ console.log(`Created span ${capturedContext?.spanId} for trace ${capturedContext?.traceId}`);
499
+ });
500
+ });
501
+ });