@vibescope/mcp-server 0.0.1

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 (170) hide show
  1. package/README.md +98 -0
  2. package/dist/cli.d.ts +34 -0
  3. package/dist/cli.js +356 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +367 -0
  6. package/dist/handlers/__test-utils__.d.ts +72 -0
  7. package/dist/handlers/__test-utils__.js +176 -0
  8. package/dist/handlers/blockers.d.ts +18 -0
  9. package/dist/handlers/blockers.js +81 -0
  10. package/dist/handlers/bodies-of-work.d.ts +34 -0
  11. package/dist/handlers/bodies-of-work.js +614 -0
  12. package/dist/handlers/checkouts.d.ts +37 -0
  13. package/dist/handlers/checkouts.js +377 -0
  14. package/dist/handlers/cost.d.ts +39 -0
  15. package/dist/handlers/cost.js +247 -0
  16. package/dist/handlers/decisions.d.ts +16 -0
  17. package/dist/handlers/decisions.js +64 -0
  18. package/dist/handlers/deployment.d.ts +36 -0
  19. package/dist/handlers/deployment.js +1062 -0
  20. package/dist/handlers/discovery.d.ts +14 -0
  21. package/dist/handlers/discovery.js +870 -0
  22. package/dist/handlers/fallback.d.ts +18 -0
  23. package/dist/handlers/fallback.js +216 -0
  24. package/dist/handlers/findings.d.ts +18 -0
  25. package/dist/handlers/findings.js +110 -0
  26. package/dist/handlers/git-issues.d.ts +22 -0
  27. package/dist/handlers/git-issues.js +247 -0
  28. package/dist/handlers/ideas.d.ts +19 -0
  29. package/dist/handlers/ideas.js +188 -0
  30. package/dist/handlers/index.d.ts +29 -0
  31. package/dist/handlers/index.js +65 -0
  32. package/dist/handlers/knowledge-query.d.ts +22 -0
  33. package/dist/handlers/knowledge-query.js +253 -0
  34. package/dist/handlers/knowledge.d.ts +12 -0
  35. package/dist/handlers/knowledge.js +108 -0
  36. package/dist/handlers/milestones.d.ts +20 -0
  37. package/dist/handlers/milestones.js +179 -0
  38. package/dist/handlers/organizations.d.ts +36 -0
  39. package/dist/handlers/organizations.js +428 -0
  40. package/dist/handlers/progress.d.ts +14 -0
  41. package/dist/handlers/progress.js +149 -0
  42. package/dist/handlers/project.d.ts +20 -0
  43. package/dist/handlers/project.js +278 -0
  44. package/dist/handlers/requests.d.ts +16 -0
  45. package/dist/handlers/requests.js +131 -0
  46. package/dist/handlers/roles.d.ts +30 -0
  47. package/dist/handlers/roles.js +281 -0
  48. package/dist/handlers/session.d.ts +20 -0
  49. package/dist/handlers/session.js +791 -0
  50. package/dist/handlers/tasks.d.ts +52 -0
  51. package/dist/handlers/tasks.js +1111 -0
  52. package/dist/handlers/tasks.test.d.ts +1 -0
  53. package/dist/handlers/tasks.test.js +431 -0
  54. package/dist/handlers/types.d.ts +94 -0
  55. package/dist/handlers/types.js +1 -0
  56. package/dist/handlers/validation.d.ts +16 -0
  57. package/dist/handlers/validation.js +188 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2707 -0
  60. package/dist/knowledge.d.ts +6 -0
  61. package/dist/knowledge.js +121 -0
  62. package/dist/tools.d.ts +2 -0
  63. package/dist/tools.js +2498 -0
  64. package/dist/utils.d.ts +149 -0
  65. package/dist/utils.js +317 -0
  66. package/dist/utils.test.d.ts +1 -0
  67. package/dist/utils.test.js +532 -0
  68. package/dist/validators.d.ts +35 -0
  69. package/dist/validators.js +111 -0
  70. package/dist/validators.test.d.ts +1 -0
  71. package/dist/validators.test.js +176 -0
  72. package/package.json +44 -0
  73. package/src/cli.test.ts +442 -0
  74. package/src/cli.ts +439 -0
  75. package/src/handlers/__test-utils__.ts +217 -0
  76. package/src/handlers/blockers.test.ts +390 -0
  77. package/src/handlers/blockers.ts +110 -0
  78. package/src/handlers/bodies-of-work.test.ts +1276 -0
  79. package/src/handlers/bodies-of-work.ts +783 -0
  80. package/src/handlers/cost.test.ts +436 -0
  81. package/src/handlers/cost.ts +322 -0
  82. package/src/handlers/decisions.test.ts +401 -0
  83. package/src/handlers/decisions.ts +86 -0
  84. package/src/handlers/deployment.test.ts +516 -0
  85. package/src/handlers/deployment.ts +1289 -0
  86. package/src/handlers/discovery.test.ts +254 -0
  87. package/src/handlers/discovery.ts +969 -0
  88. package/src/handlers/fallback.test.ts +687 -0
  89. package/src/handlers/fallback.ts +260 -0
  90. package/src/handlers/findings.test.ts +565 -0
  91. package/src/handlers/findings.ts +153 -0
  92. package/src/handlers/ideas.test.ts +753 -0
  93. package/src/handlers/ideas.ts +247 -0
  94. package/src/handlers/index.ts +69 -0
  95. package/src/handlers/milestones.test.ts +584 -0
  96. package/src/handlers/milestones.ts +217 -0
  97. package/src/handlers/organizations.test.ts +997 -0
  98. package/src/handlers/organizations.ts +550 -0
  99. package/src/handlers/progress.test.ts +369 -0
  100. package/src/handlers/progress.ts +188 -0
  101. package/src/handlers/project.test.ts +562 -0
  102. package/src/handlers/project.ts +352 -0
  103. package/src/handlers/requests.test.ts +531 -0
  104. package/src/handlers/requests.ts +150 -0
  105. package/src/handlers/session.test.ts +459 -0
  106. package/src/handlers/session.ts +912 -0
  107. package/src/handlers/tasks.test.ts +602 -0
  108. package/src/handlers/tasks.ts +1393 -0
  109. package/src/handlers/types.ts +88 -0
  110. package/src/handlers/validation.test.ts +880 -0
  111. package/src/handlers/validation.ts +223 -0
  112. package/src/index.ts +3205 -0
  113. package/src/knowledge.ts +132 -0
  114. package/src/tmpclaude-0078-cwd +1 -0
  115. package/src/tmpclaude-0ee1-cwd +1 -0
  116. package/src/tmpclaude-2dd5-cwd +1 -0
  117. package/src/tmpclaude-344c-cwd +1 -0
  118. package/src/tmpclaude-3860-cwd +1 -0
  119. package/src/tmpclaude-4b63-cwd +1 -0
  120. package/src/tmpclaude-5c73-cwd +1 -0
  121. package/src/tmpclaude-5ee3-cwd +1 -0
  122. package/src/tmpclaude-6795-cwd +1 -0
  123. package/src/tmpclaude-709e-cwd +1 -0
  124. package/src/tmpclaude-9839-cwd +1 -0
  125. package/src/tmpclaude-d829-cwd +1 -0
  126. package/src/tmpclaude-e072-cwd +1 -0
  127. package/src/tmpclaude-f6ee-cwd +1 -0
  128. package/src/utils.test.ts +681 -0
  129. package/src/utils.ts +375 -0
  130. package/src/validators.test.ts +223 -0
  131. package/src/validators.ts +122 -0
  132. package/tmpclaude-0439-cwd +1 -0
  133. package/tmpclaude-132f-cwd +1 -0
  134. package/tmpclaude-15bb-cwd +1 -0
  135. package/tmpclaude-165a-cwd +1 -0
  136. package/tmpclaude-1ba9-cwd +1 -0
  137. package/tmpclaude-21a3-cwd +1 -0
  138. package/tmpclaude-2a38-cwd +1 -0
  139. package/tmpclaude-2adf-cwd +1 -0
  140. package/tmpclaude-2f56-cwd +1 -0
  141. package/tmpclaude-3626-cwd +1 -0
  142. package/tmpclaude-3727-cwd +1 -0
  143. package/tmpclaude-40bc-cwd +1 -0
  144. package/tmpclaude-436f-cwd +1 -0
  145. package/tmpclaude-4783-cwd +1 -0
  146. package/tmpclaude-4b6d-cwd +1 -0
  147. package/tmpclaude-4ba4-cwd +1 -0
  148. package/tmpclaude-51e6-cwd +1 -0
  149. package/tmpclaude-5ecf-cwd +1 -0
  150. package/tmpclaude-6f97-cwd +1 -0
  151. package/tmpclaude-7fb2-cwd +1 -0
  152. package/tmpclaude-825c-cwd +1 -0
  153. package/tmpclaude-8baf-cwd +1 -0
  154. package/tmpclaude-8d9f-cwd +1 -0
  155. package/tmpclaude-975c-cwd +1 -0
  156. package/tmpclaude-9983-cwd +1 -0
  157. package/tmpclaude-a045-cwd +1 -0
  158. package/tmpclaude-ac4a-cwd +1 -0
  159. package/tmpclaude-b593-cwd +1 -0
  160. package/tmpclaude-b891-cwd +1 -0
  161. package/tmpclaude-c032-cwd +1 -0
  162. package/tmpclaude-cf43-cwd +1 -0
  163. package/tmpclaude-d040-cwd +1 -0
  164. package/tmpclaude-dcdd-cwd +1 -0
  165. package/tmpclaude-dcee-cwd +1 -0
  166. package/tmpclaude-e16b-cwd +1 -0
  167. package/tmpclaude-ecd2-cwd +1 -0
  168. package/tmpclaude-f48d-cwd +1 -0
  169. package/tsconfig.json +16 -0
  170. package/vitest.config.ts +13 -0
@@ -0,0 +1,459 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { SupabaseClient } from '@supabase/supabase-js';
3
+ import type { HandlerContext, TokenUsage } from './types.js';
4
+ import {
5
+ heartbeat,
6
+ endWorkSession,
7
+ getHelp,
8
+ getTokenUsage,
9
+ } from './session.js';
10
+
11
+ // ============================================================================
12
+ // Test Utilities
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Creates a mock Supabase client with chainable methods
17
+ */
18
+ function createMockSupabase(overrides: {
19
+ selectResult?: { data: unknown; error: unknown };
20
+ insertResult?: { data: unknown; error: unknown };
21
+ updateResult?: { data: unknown; error: unknown };
22
+ } = {}) {
23
+ const defaultResult = { data: null, error: null };
24
+
25
+ let currentOperation = 'select';
26
+ let insertThenSelect = false;
27
+
28
+ const mock = {
29
+ from: vi.fn().mockReturnThis(),
30
+ select: vi.fn(() => {
31
+ if (currentOperation === 'insert') {
32
+ insertThenSelect = true;
33
+ } else {
34
+ currentOperation = 'select';
35
+ insertThenSelect = false;
36
+ }
37
+ return mock;
38
+ }),
39
+ insert: vi.fn(() => {
40
+ currentOperation = 'insert';
41
+ insertThenSelect = false;
42
+ return mock;
43
+ }),
44
+ update: vi.fn(() => {
45
+ currentOperation = 'update';
46
+ insertThenSelect = false;
47
+ return mock;
48
+ }),
49
+ delete: vi.fn(() => {
50
+ currentOperation = 'delete';
51
+ insertThenSelect = false;
52
+ return mock;
53
+ }),
54
+ eq: vi.fn().mockReturnThis(),
55
+ neq: vi.fn().mockReturnThis(),
56
+ in: vi.fn().mockReturnThis(),
57
+ is: vi.fn().mockReturnThis(),
58
+ not: vi.fn().mockReturnThis(),
59
+ or: vi.fn().mockReturnThis(),
60
+ gte: vi.fn().mockReturnThis(),
61
+ lt: vi.fn().mockReturnThis(),
62
+ order: vi.fn().mockReturnThis(),
63
+ limit: vi.fn().mockReturnThis(),
64
+ single: vi.fn(() => {
65
+ if (currentOperation === 'insert' || insertThenSelect) {
66
+ return Promise.resolve(overrides.insertResult ?? defaultResult);
67
+ }
68
+ if (currentOperation === 'select') {
69
+ return Promise.resolve(overrides.selectResult ?? defaultResult);
70
+ }
71
+ if (currentOperation === 'update') {
72
+ return Promise.resolve(overrides.updateResult ?? defaultResult);
73
+ }
74
+ return Promise.resolve(defaultResult);
75
+ }),
76
+ maybeSingle: vi.fn(() => {
77
+ return Promise.resolve(overrides.selectResult ?? defaultResult);
78
+ }),
79
+ then: vi.fn((resolve: (value: unknown) => void) => {
80
+ if (currentOperation === 'insert' || insertThenSelect) {
81
+ return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
82
+ }
83
+ if (currentOperation === 'select') {
84
+ return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
85
+ }
86
+ if (currentOperation === 'update') {
87
+ return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
88
+ }
89
+ return Promise.resolve(defaultResult).then(resolve);
90
+ }),
91
+ };
92
+
93
+ return mock as unknown as SupabaseClient;
94
+ }
95
+
96
+ /**
97
+ * Creates a mock handler context with full session state
98
+ */
99
+ function createMockContext(
100
+ supabase: SupabaseClient,
101
+ options: {
102
+ sessionId?: string | null;
103
+ tokenUsage?: TokenUsage;
104
+ } = {}
105
+ ): HandlerContext {
106
+ const defaultTokenUsage: TokenUsage = {
107
+ callCount: 10,
108
+ totalTokens: 5000,
109
+ byTool: {
110
+ get_tasks: { calls: 3, tokens: 1500 },
111
+ update_task: { calls: 4, tokens: 2000 },
112
+ complete_task: { calls: 3, tokens: 1500 },
113
+ },
114
+ };
115
+
116
+ // Use 'in' check to allow explicit null values for sessionId
117
+ const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
118
+
119
+ return {
120
+ supabase,
121
+ auth: {
122
+ userId: 'user-123',
123
+ apiKeyId: 'api-key-123',
124
+ },
125
+ session: {
126
+ instanceId: 'instance-abc123',
127
+ currentSessionId: sessionId,
128
+ currentPersona: 'Wave',
129
+ tokenUsage: options.tokenUsage ?? defaultTokenUsage,
130
+ },
131
+ updateSession: vi.fn(),
132
+ };
133
+ }
134
+
135
+ // ============================================================================
136
+ // heartbeat Tests
137
+ // ============================================================================
138
+
139
+ describe('heartbeat', () => {
140
+ beforeEach(() => {
141
+ vi.clearAllMocks();
142
+ });
143
+
144
+ it('should record heartbeat successfully', async () => {
145
+ const supabase = createMockSupabase({
146
+ insertResult: { data: null, error: null },
147
+ updateResult: { data: null, error: null },
148
+ });
149
+ const ctx = createMockContext(supabase);
150
+
151
+ const result = await heartbeat({}, ctx);
152
+
153
+ expect(result.result).toMatchObject({
154
+ success: true,
155
+ session_id: 'session-123',
156
+ });
157
+ expect(result.result).toHaveProperty('timestamp');
158
+ expect(supabase.from).toHaveBeenCalledWith('agent_heartbeats');
159
+ expect(supabase.insert).toHaveBeenCalled();
160
+ });
161
+
162
+ it('should use provided session_id over current session', async () => {
163
+ const supabase = createMockSupabase({
164
+ insertResult: { data: null, error: null },
165
+ updateResult: { data: null, error: null },
166
+ });
167
+ const ctx = createMockContext(supabase);
168
+
169
+ const result = await heartbeat({ session_id: 'other-session-456' }, ctx);
170
+
171
+ expect(result.result).toMatchObject({
172
+ success: true,
173
+ session_id: 'other-session-456',
174
+ });
175
+ });
176
+
177
+ it('should return error when no active session', async () => {
178
+ const supabase = createMockSupabase();
179
+ const ctx = createMockContext(supabase, { sessionId: null });
180
+
181
+ const result = await heartbeat({}, ctx);
182
+
183
+ expect(result.result).toMatchObject({
184
+ error: 'No active session. Call start_work_session first.',
185
+ });
186
+ });
187
+
188
+ it('should update session with token usage', async () => {
189
+ const supabase = createMockSupabase({
190
+ insertResult: { data: null, error: null },
191
+ updateResult: { data: null, error: null },
192
+ });
193
+ const ctx = createMockContext(supabase);
194
+
195
+ await heartbeat({}, ctx);
196
+
197
+ expect(supabase.from).toHaveBeenCalledWith('agent_sessions');
198
+ expect(supabase.update).toHaveBeenCalledWith(
199
+ expect.objectContaining({
200
+ status: 'active',
201
+ total_tokens: 5000,
202
+ })
203
+ );
204
+ });
205
+ });
206
+
207
+ // ============================================================================
208
+ // getHelp Tests
209
+ // ============================================================================
210
+
211
+ describe('getHelp', () => {
212
+ beforeEach(() => {
213
+ vi.clearAllMocks();
214
+ });
215
+
216
+ it('should return help content for valid topic', async () => {
217
+ const supabase = createMockSupabase();
218
+ const ctx = createMockContext(supabase);
219
+
220
+ const result = await getHelp({ topic: 'tasks' }, ctx);
221
+
222
+ expect(result.result).toHaveProperty('topic', 'tasks');
223
+ expect(result.result).toHaveProperty('content');
224
+ });
225
+
226
+ it('should return getting_started help', async () => {
227
+ const supabase = createMockSupabase();
228
+ const ctx = createMockContext(supabase);
229
+
230
+ const result = await getHelp({ topic: 'getting_started' }, ctx);
231
+
232
+ expect(result.result).toHaveProperty('topic', 'getting_started');
233
+ expect(result.result).toHaveProperty('content');
234
+ });
235
+
236
+ it('should return error for unknown topic', async () => {
237
+ const supabase = createMockSupabase();
238
+ const ctx = createMockContext(supabase);
239
+
240
+ const result = await getHelp({ topic: 'unknown_topic' }, ctx);
241
+
242
+ expect(result.result).toMatchObject({
243
+ error: 'Unknown topic: unknown_topic',
244
+ });
245
+ expect(result.result).toHaveProperty('available');
246
+ });
247
+
248
+ it('should list available topics for unknown topic', async () => {
249
+ const supabase = createMockSupabase();
250
+ const ctx = createMockContext(supabase);
251
+
252
+ const result = await getHelp({ topic: 'nonexistent' }, ctx);
253
+
254
+ const available = (result.result as { available: string[] }).available;
255
+ expect(Array.isArray(available)).toBe(true);
256
+ expect(available.length).toBeGreaterThan(0);
257
+ });
258
+ });
259
+
260
+ // ============================================================================
261
+ // getTokenUsage Tests
262
+ // ============================================================================
263
+
264
+ describe('getTokenUsage', () => {
265
+ beforeEach(() => {
266
+ vi.clearAllMocks();
267
+ });
268
+
269
+ it('should return token usage stats', async () => {
270
+ const supabase = createMockSupabase();
271
+ const ctx = createMockContext(supabase);
272
+
273
+ const result = await getTokenUsage({}, ctx);
274
+
275
+ expect(result.result).toHaveProperty('session');
276
+ expect(result.result).toHaveProperty('top_tools');
277
+ expect(result.result).toHaveProperty('note');
278
+ });
279
+
280
+ it('should return correct session stats', async () => {
281
+ const supabase = createMockSupabase();
282
+ const ctx = createMockContext(supabase, {
283
+ tokenUsage: {
284
+ callCount: 10,
285
+ totalTokens: 5000,
286
+ byTool: {},
287
+ },
288
+ });
289
+
290
+ const result = await getTokenUsage({}, ctx);
291
+ const session = (result.result as { session: { calls: number; tokens: number; avg_per_call: number } }).session;
292
+
293
+ expect(session.calls).toBe(10);
294
+ expect(session.tokens).toBe(5000);
295
+ expect(session.avg_per_call).toBe(500);
296
+ });
297
+
298
+ it('should return top tools sorted by tokens', async () => {
299
+ const supabase = createMockSupabase();
300
+ const ctx = createMockContext(supabase, {
301
+ tokenUsage: {
302
+ callCount: 10,
303
+ totalTokens: 5000,
304
+ byTool: {
305
+ tool_a: { calls: 2, tokens: 1000 },
306
+ tool_b: { calls: 5, tokens: 3000 },
307
+ tool_c: { calls: 3, tokens: 1000 },
308
+ },
309
+ },
310
+ });
311
+
312
+ const result = await getTokenUsage({}, ctx);
313
+ const topTools = (result.result as { top_tools: Array<{ tool: string; tokens: number }> }).top_tools;
314
+
315
+ // Should be sorted by tokens descending
316
+ expect(topTools[0].tool).toBe('tool_b');
317
+ expect(topTools[0].tokens).toBe(3000);
318
+ });
319
+
320
+ it('should handle zero calls gracefully', async () => {
321
+ const supabase = createMockSupabase();
322
+ const ctx = createMockContext(supabase, {
323
+ tokenUsage: {
324
+ callCount: 0,
325
+ totalTokens: 0,
326
+ byTool: {},
327
+ },
328
+ });
329
+
330
+ const result = await getTokenUsage({}, ctx);
331
+ const session = (result.result as { session: { calls: number; tokens: number; avg_per_call: number } }).session;
332
+
333
+ expect(session.calls).toBe(0);
334
+ expect(session.tokens).toBe(0);
335
+ expect(session.avg_per_call).toBe(0);
336
+ });
337
+
338
+ it('should limit top_tools to 5', async () => {
339
+ const supabase = createMockSupabase();
340
+ const ctx = createMockContext(supabase, {
341
+ tokenUsage: {
342
+ callCount: 20,
343
+ totalTokens: 10000,
344
+ byTool: {
345
+ tool_1: { calls: 1, tokens: 100 },
346
+ tool_2: { calls: 2, tokens: 200 },
347
+ tool_3: { calls: 3, tokens: 300 },
348
+ tool_4: { calls: 4, tokens: 400 },
349
+ tool_5: { calls: 5, tokens: 500 },
350
+ tool_6: { calls: 6, tokens: 600 },
351
+ tool_7: { calls: 7, tokens: 700 },
352
+ },
353
+ },
354
+ });
355
+
356
+ const result = await getTokenUsage({}, ctx);
357
+ const topTools = (result.result as { top_tools: Array<{ tool: string }> }).top_tools;
358
+
359
+ expect(topTools.length).toBe(5);
360
+ });
361
+ });
362
+
363
+ // ============================================================================
364
+ // endWorkSession Tests
365
+ // ============================================================================
366
+
367
+ describe('endWorkSession', () => {
368
+ beforeEach(() => {
369
+ vi.clearAllMocks();
370
+ });
371
+
372
+ it('should handle no active session gracefully', async () => {
373
+ const supabase = createMockSupabase();
374
+ const ctx = createMockContext(supabase, { sessionId: null });
375
+
376
+ const result = await endWorkSession({}, ctx);
377
+
378
+ expect(result.result).toMatchObject({
379
+ success: true,
380
+ message: 'No active session to end',
381
+ });
382
+ });
383
+
384
+ it('should use provided session_id over current session', async () => {
385
+ const supabase = createMockSupabase({
386
+ selectResult: {
387
+ data: {
388
+ project_id: 'project-123',
389
+ agent_name: 'Wave',
390
+ started_at: new Date().toISOString()
391
+ },
392
+ error: null
393
+ },
394
+ });
395
+ const ctx = createMockContext(supabase);
396
+
397
+ const result = await endWorkSession({ session_id: 'other-session-456' }, ctx);
398
+
399
+ expect(result.result).toHaveProperty('ended_session_id', 'other-session-456');
400
+ });
401
+
402
+ it('should call updateSession to clear current session', async () => {
403
+ const supabase = createMockSupabase({
404
+ selectResult: {
405
+ data: {
406
+ project_id: 'project-123',
407
+ agent_name: 'Wave',
408
+ started_at: new Date().toISOString()
409
+ },
410
+ error: null
411
+ },
412
+ });
413
+ const ctx = createMockContext(supabase);
414
+
415
+ await endWorkSession({}, ctx);
416
+
417
+ expect(ctx.updateSession).toHaveBeenCalledWith({ currentSessionId: null });
418
+ });
419
+
420
+ it('should return session summary', async () => {
421
+ const supabase = createMockSupabase({
422
+ selectResult: {
423
+ data: {
424
+ project_id: 'project-123',
425
+ agent_name: 'Wave',
426
+ started_at: new Date().toISOString()
427
+ },
428
+ error: null
429
+ },
430
+ });
431
+ const ctx = createMockContext(supabase);
432
+
433
+ const result = await endWorkSession({}, ctx);
434
+
435
+ expect(result.result).toHaveProperty('session_summary');
436
+ const summary = (result.result as { session_summary: { agent_name: string; token_usage: unknown } }).session_summary;
437
+ expect(summary.agent_name).toBe('Wave');
438
+ expect(summary).toHaveProperty('token_usage');
439
+ });
440
+
441
+ it('should not call updateSession when ending a different session', async () => {
442
+ const supabase = createMockSupabase({
443
+ selectResult: {
444
+ data: {
445
+ project_id: 'project-123',
446
+ agent_name: 'Wave',
447
+ started_at: new Date().toISOString()
448
+ },
449
+ error: null
450
+ },
451
+ });
452
+ const ctx = createMockContext(supabase, { sessionId: 'session-123' });
453
+
454
+ await endWorkSession({ session_id: 'different-session' }, ctx);
455
+
456
+ // Should NOT clear the current session since we're ending a different one
457
+ expect(ctx.updateSession).not.toHaveBeenCalledWith({ currentSessionId: null });
458
+ });
459
+ });