@vellumai/assistant 0.4.15 → 0.4.17

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 (73) hide show
  1. package/Dockerfile +6 -6
  2. package/README.md +1 -2
  3. package/package.json +1 -1
  4. package/src/__tests__/approval-routes-http.test.ts +383 -254
  5. package/src/__tests__/call-controller.test.ts +1074 -751
  6. package/src/__tests__/call-routes-http.test.ts +329 -279
  7. package/src/__tests__/channel-approval-routes.test.ts +2 -13
  8. package/src/__tests__/channel-approvals.test.ts +227 -182
  9. package/src/__tests__/channel-guardian.test.ts +1 -0
  10. package/src/__tests__/conversation-attention-telegram.test.ts +157 -114
  11. package/src/__tests__/conversation-routes-guardian-reply.test.ts +164 -104
  12. package/src/__tests__/conversation-routes.test.ts +71 -41
  13. package/src/__tests__/daemon-server-session-init.test.ts +258 -191
  14. package/src/__tests__/deterministic-verification-control-plane.test.ts +183 -134
  15. package/src/__tests__/extract-email.test.ts +42 -0
  16. package/src/__tests__/gateway-only-enforcement.test.ts +467 -368
  17. package/src/__tests__/gateway-only-guard.test.ts +54 -55
  18. package/src/__tests__/gmail-integration.test.ts +48 -46
  19. package/src/__tests__/guardian-action-followup-executor.test.ts +215 -150
  20. package/src/__tests__/guardian-outbound-http.test.ts +334 -208
  21. package/src/__tests__/guardian-routing-invariants.test.ts +680 -613
  22. package/src/__tests__/guardian-routing-state.test.ts +257 -209
  23. package/src/__tests__/guardian-verification-voice-binding.test.ts +47 -40
  24. package/src/__tests__/handle-user-message-secret-resume.test.ts +44 -21
  25. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +269 -195
  26. package/src/__tests__/inbound-invite-redemption.test.ts +194 -151
  27. package/src/__tests__/ingress-reconcile.test.ts +184 -142
  28. package/src/__tests__/non-member-access-request.test.ts +291 -247
  29. package/src/__tests__/notification-telegram-adapter.test.ts +60 -46
  30. package/src/__tests__/pairing-concurrent.test.ts +78 -0
  31. package/src/__tests__/recording-intent-handler.test.ts +422 -291
  32. package/src/__tests__/runtime-attachment-metadata.test.ts +107 -69
  33. package/src/__tests__/runtime-events-sse.test.ts +67 -50
  34. package/src/__tests__/send-endpoint-busy.test.ts +314 -232
  35. package/src/__tests__/session-approval-overrides.test.ts +93 -91
  36. package/src/__tests__/sms-messaging-provider.test.ts +74 -47
  37. package/src/__tests__/trusted-contact-approval-notifier.test.ts +339 -274
  38. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +484 -372
  39. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +261 -239
  40. package/src/__tests__/trusted-contact-multichannel.test.ts +179 -140
  41. package/src/__tests__/twilio-config.test.ts +49 -41
  42. package/src/__tests__/twilio-routes-elevenlabs.test.ts +189 -162
  43. package/src/__tests__/twilio-routes.test.ts +389 -280
  44. package/src/calls/call-controller.ts +1 -1
  45. package/src/calls/guardian-action-sweep.ts +6 -6
  46. package/src/calls/twilio-routes.ts +2 -4
  47. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +29 -4
  48. package/src/config/bundled-skills/messaging/SKILL.md +5 -4
  49. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +69 -4
  50. package/src/config/env.ts +39 -29
  51. package/src/daemon/handlers/config-inbox.ts +5 -5
  52. package/src/daemon/handlers/skills.ts +18 -10
  53. package/src/daemon/ipc-contract/messages.ts +1 -0
  54. package/src/daemon/ipc-contract/surfaces.ts +7 -1
  55. package/src/daemon/pairing-store.ts +15 -2
  56. package/src/daemon/session-agent-loop-handlers.ts +5 -0
  57. package/src/daemon/session-agent-loop.ts +1 -1
  58. package/src/daemon/session-process.ts +1 -1
  59. package/src/daemon/session-slash.ts +4 -4
  60. package/src/daemon/session-surfaces.ts +42 -2
  61. package/src/runtime/auth/token-service.ts +95 -45
  62. package/src/runtime/channel-retry-sweep.ts +2 -2
  63. package/src/runtime/http-server.ts +8 -7
  64. package/src/runtime/http-types.ts +1 -1
  65. package/src/runtime/routes/conversation-routes.ts +1 -1
  66. package/src/runtime/routes/guardian-bootstrap-routes.ts +3 -2
  67. package/src/runtime/routes/guardian-expiry-sweep.ts +5 -5
  68. package/src/runtime/routes/pairing-routes.ts +4 -1
  69. package/src/sequence/reply-matcher.ts +14 -4
  70. package/src/skills/frontmatter.ts +9 -6
  71. package/src/tools/ui-surface/definitions.ts +3 -1
  72. package/src/util/platform.ts +0 -12
  73. package/docs/architecture/http-token-refresh.md +0 -274
@@ -1,30 +1,36 @@
1
+ import * as net from "node:net";
1
2
 
2
- import * as net from 'node:net';
3
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
3
4
 
4
- import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test';
5
+ mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
5
6
 
6
7
  // ─── Mocks (must be before any imports that depend on them) ─────────────────
7
8
 
8
9
  const noop = () => {};
9
10
  const noopLogger = {
10
- info: noop, warn: noop, error: noop, debug: noop, trace: noop, fatal: noop,
11
+ info: noop,
12
+ warn: noop,
13
+ error: noop,
14
+ debug: noop,
15
+ trace: noop,
16
+ fatal: noop,
11
17
  child: () => noopLogger,
12
18
  };
13
19
 
14
- mock.module('../util/logger.js', () => ({
20
+ mock.module("../util/logger.js", () => ({
15
21
  getLogger: () => noopLogger,
16
22
  isDebug: () => false,
17
23
  truncateForLog: (v: string) => v,
18
24
  }));
19
25
 
20
- mock.module('../config/loader.js', () => ({
26
+ mock.module("../config/loader.js", () => ({
21
27
  getConfig: () => ({
22
28
  ui: {},
23
-
29
+
24
30
  daemon: { standaloneRecording: true },
25
- provider: 'mock-provider',
26
- model: 'mock-model',
27
- permissions: { mode: 'legacy' },
31
+ provider: "mock-provider",
32
+ model: "mock-model",
33
+ permissions: { mode: "legacy" },
28
34
  apiKeys: {},
29
35
  sandbox: { enabled: false },
30
36
  timeouts: { toolExecutionTimeoutSec: 30, permissionTimeoutSec: 5 },
@@ -53,7 +59,7 @@ mock.module('../config/loader.js', () => ({
53
59
 
54
60
  let mockAssistantName: string | null = null;
55
61
 
56
- mock.module('../daemon/identity-helpers.js', () => ({
62
+ mock.module("../daemon/identity-helpers.js", () => ({
57
63
  getAssistantName: () => mockAssistantName,
58
64
  }));
59
65
 
@@ -68,26 +74,27 @@ mock.module('../daemon/identity-helpers.js', () => ({
68
74
  // transparently delegates to the real implementation.
69
75
 
70
76
  type RecordingIntentResult =
71
- | { kind: 'none' }
72
- | { kind: 'start_only' }
73
- | { kind: 'stop_only' }
74
- | { kind: 'start_with_remainder'; remainder: string }
75
- | { kind: 'stop_with_remainder'; remainder: string }
76
- | { kind: 'start_and_stop_only' }
77
- | { kind: 'start_and_stop_with_remainder'; remainder: string }
78
- | { kind: 'restart_only' }
79
- | { kind: 'restart_with_remainder'; remainder: string }
80
- | { kind: 'pause_only' }
81
- | { kind: 'resume_only' };
82
-
83
- let mockIntentResult: RecordingIntentResult = { kind: 'none' };
77
+ | { kind: "none" }
78
+ | { kind: "start_only" }
79
+ | { kind: "stop_only" }
80
+ | { kind: "start_with_remainder"; remainder: string }
81
+ | { kind: "stop_with_remainder"; remainder: string }
82
+ | { kind: "start_and_stop_only" }
83
+ | { kind: "start_and_stop_with_remainder"; remainder: string }
84
+ | { kind: "restart_only" }
85
+ | { kind: "restart_with_remainder"; remainder: string }
86
+ | { kind: "pause_only" }
87
+ | { kind: "resume_only" };
88
+
89
+ let mockIntentResult: RecordingIntentResult = { kind: "none" };
84
90
 
85
91
  // Capture real function references BEFORE mock.module replaces the module.
86
92
  // require() at this point returns the real module since mock.module has not
87
93
  // been called yet for this specifier.
88
94
  // eslint-disable-next-line @typescript-eslint/no-require-imports
89
- const _realRecordingIntentMod = require('../daemon/recording-intent.js');
90
- const _realResolveRecordingIntent = _realRecordingIntentMod.resolveRecordingIntent;
95
+ const _realRecordingIntentMod = require("../daemon/recording-intent.js");
96
+ const _realResolveRecordingIntent =
97
+ _realRecordingIntentMod.resolveRecordingIntent;
91
98
  const _realStripDynamicNames = _realRecordingIntentMod.stripDynamicNames;
92
99
 
93
100
  // Flag: when true, the mock returns controlled test values; when false, it
@@ -95,7 +102,7 @@ const _realStripDynamicNames = _realRecordingIntentMod.stripDynamicNames;
95
102
  // bleeds to other test files, those files get the real behavior.
96
103
  (globalThis as any).__riHandlerUseMockIntent = false;
97
104
 
98
- mock.module('../daemon/recording-intent.js', () => ({
105
+ mock.module("../daemon/recording-intent.js", () => ({
99
106
  resolveRecordingIntent: (...args: any[]) => {
100
107
  if ((globalThis as any).__riHandlerUseMockIntent) return mockIntentResult;
101
108
  return _realResolveRecordingIntent(...args);
@@ -129,20 +136,21 @@ let executorCalled = false;
129
136
  let _realExecuteRecordingIntent: ((...args: any[]) => any) | null = null;
130
137
  try {
131
138
  // eslint-disable-next-line @typescript-eslint/no-require-imports
132
- const _mod = require('../daemon/recording-executor.js');
139
+ const _mod = require("../daemon/recording-executor.js");
133
140
  _realExecuteRecordingIntent = _mod.executeRecordingIntent;
134
141
  } catch {
135
142
  // Transitive dependency loading may fail when this file runs alone;
136
143
  // the controlled mock will be used exclusively in that case.
137
144
  }
138
145
 
139
- mock.module('../daemon/recording-executor.js', () => ({
146
+ mock.module("../daemon/recording-executor.js", () => ({
140
147
  executeRecordingIntent: (...args: any[]) => {
141
148
  if ((globalThis as any).__riHandlerUseMockIntent) {
142
149
  executorCalled = true;
143
150
  return mockExecuteResult;
144
151
  }
145
- if (_realExecuteRecordingIntent) return _realExecuteRecordingIntent(...args);
152
+ if (_realExecuteRecordingIntent)
153
+ return _realExecuteRecordingIntent(...args);
146
154
  // Fallback if real function was not captured
147
155
  return { handled: false };
148
156
  },
@@ -172,7 +180,7 @@ let _realResetRecordingState: ((...args: any[]) => any) | null = null;
172
180
 
173
181
  try {
174
182
  // eslint-disable-next-line @typescript-eslint/no-require-imports
175
- const _mod = require('../daemon/handlers/recording.js');
183
+ const _mod = require("../daemon/handlers/recording.js");
176
184
  _realHandleRecordingStart = _mod.handleRecordingStart;
177
185
  _realHandleRecordingStop = _mod.handleRecordingStop;
178
186
  _realHandleRecordingRestart = _mod.handleRecordingRestart;
@@ -185,39 +193,43 @@ try {
185
193
  // Same as above — controlled mock will be used exclusively.
186
194
  }
187
195
 
188
- mock.module('../daemon/handlers/recording.js', () => ({
196
+ mock.module("../daemon/handlers/recording.js", () => ({
189
197
  handleRecordingStart: (...args: any[]) => {
190
198
  if ((globalThis as any).__riHandlerUseMockIntent) {
191
199
  recordingStartCalled = true;
192
- return 'mock-recording-id';
200
+ return "mock-recording-id";
193
201
  }
194
202
  return _realHandleRecordingStart?.(...args);
195
203
  },
196
204
  handleRecordingStop: (...args: any[]) => {
197
205
  if ((globalThis as any).__riHandlerUseMockIntent) {
198
206
  _recordingStopCalled = true;
199
- return 'mock-recording-id';
207
+ return "mock-recording-id";
200
208
  }
201
209
  return _realHandleRecordingStop?.(...args);
202
210
  },
203
211
  handleRecordingRestart: (...args: any[]) => {
204
212
  if ((globalThis as any).__riHandlerUseMockIntent) {
205
213
  recordingRestartCalled = true;
206
- return { initiated: true, responseText: 'Restarting screen recording.', operationToken: 'mock-token' };
214
+ return {
215
+ initiated: true,
216
+ responseText: "Restarting screen recording.",
217
+ operationToken: "mock-token",
218
+ };
207
219
  }
208
220
  return _realHandleRecordingRestart?.(...args);
209
221
  },
210
222
  handleRecordingPause: (...args: any[]) => {
211
223
  if ((globalThis as any).__riHandlerUseMockIntent) {
212
224
  recordingPauseCalled = true;
213
- return 'mock-recording-id';
225
+ return "mock-recording-id";
214
226
  }
215
227
  return _realHandleRecordingPause?.(...args);
216
228
  },
217
229
  handleRecordingResume: (...args: any[]) => {
218
230
  if ((globalThis as any).__riHandlerUseMockIntent) {
219
231
  recordingResumeCalled = true;
220
- return 'mock-recording-id';
232
+ return "mock-recording-id";
221
233
  }
222
234
  return _realHandleRecordingResume?.(...args);
223
235
  },
@@ -234,22 +246,28 @@ mock.module('../daemon/handlers/recording.js', () => ({
234
246
 
235
247
  // ── Mock conversation store ────────────────────────────────────────────────
236
248
 
237
- mock.module('../memory/conversation-store.js', () => ({
238
- getConversationThreadType: () => 'default',
249
+ mock.module("../memory/conversation-store.js", () => ({
250
+ getConversationThreadType: () => "default",
239
251
  setConversationOriginChannelIfUnset: () => {},
240
252
  updateConversationContextWindow: () => {},
241
253
  deleteMessageById: () => {},
242
254
  updateConversationUsage: () => {},
243
- provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
255
+ provenanceFromGuardianContext: () => ({
256
+ source: "user",
257
+ guardianContext: undefined,
258
+ }),
244
259
  getConversationOriginInterface: () => null,
245
260
  getConversationOriginChannel: () => null,
246
261
  getMessages: () => [],
247
- addMessage: () => ({ id: 'msg-mock', role: 'assistant', content: '' }),
262
+ addMessage: () => ({ id: "msg-mock", role: "assistant", content: "" }),
248
263
  createConversation: (titleOrOpts?: string | { title?: string }) => {
249
- const title = typeof titleOrOpts === 'string' ? titleOrOpts : titleOrOpts?.title ?? 'Untitled';
250
- return { id: 'conv-mock', title };
264
+ const title =
265
+ typeof titleOrOpts === "string"
266
+ ? titleOrOpts
267
+ : (titleOrOpts?.title ?? "Untitled");
268
+ return { id: "conv-mock", title };
251
269
  },
252
- getConversation: () => ({ id: 'conv-mock' }),
270
+ getConversation: () => ({ id: "conv-mock" }),
253
271
  updateConversationTitle: noop,
254
272
  clearAll: noop,
255
273
  listConversations: () => [],
@@ -258,26 +276,26 @@ mock.module('../memory/conversation-store.js', () => ({
258
276
  deleteConversation: noop,
259
277
  }));
260
278
 
261
- mock.module('../memory/conversation-title-service.js', () => ({
262
- GENERATING_TITLE: '(generating\u2026)',
279
+ mock.module("../memory/conversation-title-service.js", () => ({
280
+ GENERATING_TITLE: "(generating\u2026)",
263
281
  queueGenerateConversationTitle: noop,
264
- UNTITLED_FALLBACK: 'Untitled',
282
+ UNTITLED_FALLBACK: "Untitled",
265
283
  }));
266
284
 
267
- mock.module('../memory/attachments-store.js', () => ({
285
+ mock.module("../memory/attachments-store.js", () => ({
268
286
  getAttachmentsForMessage: () => [],
269
- uploadFileBackedAttachment: () => ({ id: 'att-mock' }),
287
+ uploadFileBackedAttachment: () => ({ id: "att-mock" }),
270
288
  linkAttachmentToMessage: noop,
271
289
  setAttachmentThumbnail: noop,
272
290
  }));
273
291
 
274
292
  // ── Mock security ──────────────────────────────────────────────────────────
275
293
 
276
- mock.module('../security/secret-ingress.js', () => ({
294
+ mock.module("../security/secret-ingress.js", () => ({
277
295
  checkIngressForSecrets: () => ({ blocked: false }),
278
296
  }));
279
297
 
280
- mock.module('../security/secret-scanner.js', () => ({
298
+ mock.module("../security/secret-scanner.js", () => ({
281
299
  redactSecrets: (text: string) => text,
282
300
  compileCustomPatterns: () => [],
283
301
  }));
@@ -286,44 +304,47 @@ mock.module('../security/secret-scanner.js', () => ({
286
304
 
287
305
  let classifierCalled = false;
288
306
 
289
- mock.module('../daemon/classifier.js', () => ({
307
+ mock.module("../daemon/classifier.js", () => ({
290
308
  classifyInteraction: async () => {
291
309
  classifierCalled = true;
292
- return 'text_qa';
310
+ return "text_qa";
293
311
  },
294
312
  }));
295
313
 
296
314
  // ── Mock slash commands ────────────────────────────────────────────────────
297
315
 
298
- mock.module('../skills/slash-commands.js', () => ({
299
- parseSlashCandidate: () => ({ kind: 'none' }),
316
+ mock.module("../skills/slash-commands.js", () => ({
317
+ parseSlashCandidate: () => ({ kind: "none" }),
300
318
  }));
301
319
 
302
320
  // ── Mock computer-use handler ──────────────────────────────────────────────
303
321
 
304
- mock.module('../daemon/handlers/computer-use.js', () => ({
322
+ mock.module("../daemon/handlers/computer-use.js", () => ({
305
323
  handleCuSessionCreate: noop,
306
324
  }));
307
325
 
308
326
  // ── Mock provider ──────────────────────────────────────────────────────────
309
327
 
310
- mock.module('../providers/provider-send-message.js', () => ({
328
+ mock.module("../providers/provider-send-message.js", () => ({
311
329
  getConfiguredProvider: () => null,
312
- extractText: (_response: unknown) => '',
313
- createTimeout: (_ms: number) => ({ signal: new AbortController().signal, cleanup: () => {} }),
314
- userMessage: (text: string) => ({ role: 'user', content: text }),
330
+ extractText: (_response: unknown) => "",
331
+ createTimeout: (_ms: number) => ({
332
+ signal: new AbortController().signal,
333
+ cleanup: () => {},
334
+ }),
335
+ userMessage: (text: string) => ({ role: "user", content: text }),
315
336
  }));
316
337
 
317
338
  // ── Mock external conversation store ───────────────────────────────────────
318
339
 
319
- mock.module('../memory/external-conversation-store.js', () => ({
340
+ mock.module("../memory/external-conversation-store.js", () => ({
320
341
  getBindingsForConversations: () => new Map(),
321
342
  upsertBinding: () => {},
322
343
  }));
323
344
 
324
345
  // ── Mock subagent manager ──────────────────────────────────────────────────
325
346
 
326
- mock.module('../subagent/index.js', () => ({
347
+ mock.module("../subagent/index.js", () => ({
327
348
  getSubagentManager: () => ({
328
349
  abortAllForParent: noop,
329
350
  }),
@@ -331,43 +352,47 @@ mock.module('../subagent/index.js', () => ({
331
352
 
332
353
  // ── Mock IPC protocol helpers ──────────────────────────────────────────────
333
354
 
334
- mock.module('../daemon/ipc-protocol.js', () => ({
335
- normalizeThreadType: (t: string) => t ?? 'primary',
355
+ mock.module("../daemon/ipc-protocol.js", () => ({
356
+ normalizeThreadType: (t: string) => t ?? "primary",
336
357
  }));
337
358
 
338
359
  // ── Mock session error helpers ─────────────────────────────────────────────
339
360
 
340
- mock.module('../daemon/session-error.js', () => ({
341
- classifySessionError: () => ({ code: 'UNKNOWN', userMessage: 'error', retryable: false }),
342
- buildSessionErrorMessage: () => ({ type: 'error', message: 'error' }),
361
+ mock.module("../daemon/session-error.js", () => ({
362
+ classifySessionError: () => ({
363
+ code: "UNKNOWN",
364
+ userMessage: "error",
365
+ retryable: false,
366
+ }),
367
+ buildSessionErrorMessage: () => ({ type: "error", message: "error" }),
343
368
  }));
344
369
 
345
370
  // ── Mock video thumbnail ───────────────────────────────────────────────────
346
371
 
347
- mock.module('../daemon/video-thumbnail.js', () => ({
372
+ mock.module("../daemon/video-thumbnail.js", () => ({
348
373
  generateVideoThumbnail: async () => null,
349
374
  }));
350
375
 
351
376
  // ── Mock IPC blob store ────────────────────────────────────────────────────
352
377
 
353
- mock.module('../daemon/ipc-blob-store.js', () => ({
378
+ mock.module("../daemon/ipc-blob-store.js", () => ({
354
379
  isValidBlobId: () => false,
355
- resolveBlobPath: () => '',
380
+ resolveBlobPath: () => "",
356
381
  deleteBlob: noop,
357
382
  }));
358
383
 
359
384
  // ── Mock channels/types ────────────────────────────────────────────────────
360
385
 
361
- mock.module('../channels/types.js', () => ({
362
- parseChannelId: () => 'vellum',
363
- parseInterfaceId: () => 'vellum',
386
+ mock.module("../channels/types.js", () => ({
387
+ parseChannelId: () => "vellum",
388
+ parseInterfaceId: () => "vellum",
364
389
  isChannelId: () => true,
365
390
  }));
366
391
 
367
392
  // ─── Imports (after mocks) ──────────────────────────────────────────────────
368
393
 
369
- import type { HandlerContext } from '../daemon/handlers/shared.js';
370
- import { DebouncerMap } from '../util/debounce.js';
394
+ import type { HandlerContext } from "../daemon/handlers/shared.js";
395
+ import { DebouncerMap } from "../util/debounce.js";
371
396
 
372
397
  // ─── Helpers ────────────────────────────────────────────────────────────────
373
398
 
@@ -418,7 +443,9 @@ function createCtx(overrides?: Partial<HandlerContext>): {
418
443
  suppressConfigReload: false,
419
444
  setSuppressConfigReload: noop,
420
445
  updateConfigFingerprint: noop,
421
- send: (_socket, msg) => { sent.push(msg as { type: string; [k: string]: unknown }); },
446
+ send: (_socket, msg) => {
447
+ sent.push(msg as { type: string; [k: string]: unknown });
448
+ },
422
449
  broadcast: noop,
423
450
  clearAllSessions: () => 0,
424
451
  getOrCreateSession: async (conversationId: string) => {
@@ -435,7 +462,7 @@ function createCtx(overrides?: Partial<HandlerContext>): {
435
462
  function resetMockState(): void {
436
463
  // Enable mock mode for this file's tests
437
464
  (globalThis as any).__riHandlerUseMockIntent = true;
438
- mockIntentResult = { kind: 'none' };
465
+ mockIntentResult = { kind: "none" };
439
466
  mockExecuteResult = { handled: false };
440
467
  mockAssistantName = null;
441
468
  recordingStartCalled = false;
@@ -455,17 +482,21 @@ afterAll(() => {
455
482
 
456
483
  // ─── Tests ──────────────────────────────────────────────────────────────────
457
484
 
458
- describe('recording intent handler integration — handleTaskSubmit', () => {
485
+ describe("recording intent handler integration — handleTaskSubmit", () => {
459
486
  beforeEach(resetMockState);
460
487
 
461
- test('start_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early', async () => {
462
- mockIntentResult = { kind: 'start_only' };
463
- mockExecuteResult = { handled: true, responseText: 'Starting screen recording.', recordingStarted: true };
488
+ test("start_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early", async () => {
489
+ mockIntentResult = { kind: "start_only" };
490
+ mockExecuteResult = {
491
+ handled: true,
492
+ responseText: "Starting screen recording.",
493
+ recordingStarted: true,
494
+ };
464
495
  const { ctx, sent, fakeSocket } = createCtx();
465
496
 
466
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
497
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
467
498
  await handleTaskSubmit(
468
- { type: 'task_submit', task: 'record my screen', source: 'voice' } as any,
499
+ { type: "task_submit", task: "record my screen", source: "voice" } as any,
469
500
  fakeSocket,
470
501
  ctx,
471
502
  );
@@ -474,22 +505,25 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
474
505
  expect(classifierCalled).toBe(false);
475
506
 
476
507
  const types = sent.map((m) => m.type);
477
- expect(types).toContain('task_routed');
478
- expect(types).toContain('assistant_text_delta');
479
- expect(types).toContain('message_complete');
508
+ expect(types).toContain("task_routed");
509
+ expect(types).toContain("assistant_text_delta");
510
+ expect(types).toContain("message_complete");
480
511
 
481
- const textDelta = sent.find((m) => m.type === 'assistant_text_delta');
482
- expect(textDelta?.text).toBe('Starting screen recording.');
512
+ const textDelta = sent.find((m) => m.type === "assistant_text_delta");
513
+ expect(textDelta?.text).toBe("Starting screen recording.");
483
514
  });
484
515
 
485
- test('stop_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early', async () => {
486
- mockIntentResult = { kind: 'stop_only' };
487
- mockExecuteResult = { handled: true, responseText: 'Stopping the recording.' };
516
+ test("stop_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early", async () => {
517
+ mockIntentResult = { kind: "stop_only" };
518
+ mockExecuteResult = {
519
+ handled: true,
520
+ responseText: "Stopping the recording.",
521
+ };
488
522
  const { ctx, sent, fakeSocket } = createCtx();
489
523
 
490
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
524
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
491
525
  await handleTaskSubmit(
492
- { type: 'task_submit', task: 'stop recording', source: 'voice' } as any,
526
+ { type: "task_submit", task: "stop recording", source: "voice" } as any,
493
527
  fakeSocket,
494
528
  ctx,
495
529
  );
@@ -498,22 +532,33 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
498
532
  expect(classifierCalled).toBe(false);
499
533
 
500
534
  const types = sent.map((m) => m.type);
501
- expect(types).toContain('task_routed');
502
- expect(types).toContain('assistant_text_delta');
503
- expect(types).toContain('message_complete');
535
+ expect(types).toContain("task_routed");
536
+ expect(types).toContain("assistant_text_delta");
537
+ expect(types).toContain("message_complete");
504
538
 
505
- const textDelta = sent.find((m) => m.type === 'assistant_text_delta');
506
- expect(textDelta?.text).toBe('Stopping the recording.');
539
+ const textDelta = sent.find((m) => m.type === "assistant_text_delta");
540
+ expect(textDelta?.text).toBe("Stopping the recording.");
507
541
  });
508
542
 
509
- test('start_with_remainder → defers recording, falls through to classifier with remaining text', async () => {
510
- mockIntentResult = { kind: 'start_with_remainder', remainder: 'open Safari' };
511
- mockExecuteResult = { handled: false, remainderText: 'open Safari', pendingStart: true };
543
+ test("start_with_remainder → defers recording, falls through to classifier with remaining text", async () => {
544
+ mockIntentResult = {
545
+ kind: "start_with_remainder",
546
+ remainder: "open Safari",
547
+ };
548
+ mockExecuteResult = {
549
+ handled: false,
550
+ remainderText: "open Safari",
551
+ pendingStart: true,
552
+ };
512
553
  const { ctx, sent, fakeSocket } = createCtx();
513
554
 
514
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
555
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
515
556
  await handleTaskSubmit(
516
- { type: 'task_submit', task: 'open Safari and record my screen', source: 'voice' } as any,
557
+ {
558
+ type: "task_submit",
559
+ task: "open Safari and record my screen",
560
+ source: "voice",
561
+ } as any,
517
562
  fakeSocket,
518
563
  ctx,
519
564
  );
@@ -522,19 +567,22 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
522
567
 
523
568
  // Should NOT have recording-only messages before the classifier output
524
569
  const recordingSpecific = sent.filter(
525
- (m) => m.type === 'assistant_text_delta' && typeof m.text === 'string' &&
526
- (m.text.includes('Starting screen recording') || m.text.includes('Stopping the recording')),
570
+ (m) =>
571
+ m.type === "assistant_text_delta" &&
572
+ typeof m.text === "string" &&
573
+ (m.text.includes("Starting screen recording") ||
574
+ m.text.includes("Stopping the recording")),
527
575
  );
528
576
  expect(recordingSpecific).toHaveLength(0);
529
577
  });
530
578
 
531
- test('none → does NOT call executeRecordingIntent, falls through to classifier', async () => {
532
- mockIntentResult = { kind: 'none' };
579
+ test("none → does NOT call executeRecordingIntent, falls through to classifier", async () => {
580
+ mockIntentResult = { kind: "none" };
533
581
  const { ctx, sent: _sent, fakeSocket } = createCtx();
534
582
 
535
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
583
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
536
584
  await handleTaskSubmit(
537
- { type: 'task_submit', task: 'hello world', source: 'voice' } as any,
585
+ { type: "task_submit", task: "hello world", source: "voice" } as any,
538
586
  fakeSocket,
539
587
  ctx,
540
588
  );
@@ -543,14 +591,21 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
543
591
  expect(classifierCalled).toBe(true);
544
592
  });
545
593
 
546
- test('restart_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early', async () => {
547
- mockIntentResult = { kind: 'restart_only' };
548
- mockExecuteResult = { handled: true, responseText: 'Restarting screen recording.' };
594
+ test("restart_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early", async () => {
595
+ mockIntentResult = { kind: "restart_only" };
596
+ mockExecuteResult = {
597
+ handled: true,
598
+ responseText: "Restarting screen recording.",
599
+ };
549
600
  const { ctx, sent, fakeSocket } = createCtx();
550
601
 
551
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
602
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
552
603
  await handleTaskSubmit(
553
- { type: 'task_submit', task: 'restart the recording', source: 'voice' } as any,
604
+ {
605
+ type: "task_submit",
606
+ task: "restart the recording",
607
+ source: "voice",
608
+ } as any,
554
609
  fakeSocket,
555
610
  ctx,
556
611
  );
@@ -559,22 +614,29 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
559
614
  expect(classifierCalled).toBe(false);
560
615
 
561
616
  const types = sent.map((m) => m.type);
562
- expect(types).toContain('task_routed');
563
- expect(types).toContain('assistant_text_delta');
564
- expect(types).toContain('message_complete');
617
+ expect(types).toContain("task_routed");
618
+ expect(types).toContain("assistant_text_delta");
619
+ expect(types).toContain("message_complete");
565
620
 
566
- const textDelta = sent.find((m) => m.type === 'assistant_text_delta');
567
- expect(textDelta?.text).toBe('Restarting screen recording.');
621
+ const textDelta = sent.find((m) => m.type === "assistant_text_delta");
622
+ expect(textDelta?.text).toBe("Restarting screen recording.");
568
623
  });
569
624
 
570
- test('pause_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early', async () => {
571
- mockIntentResult = { kind: 'pause_only' };
572
- mockExecuteResult = { handled: true, responseText: 'Pausing the recording.' };
625
+ test("pause_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early", async () => {
626
+ mockIntentResult = { kind: "pause_only" };
627
+ mockExecuteResult = {
628
+ handled: true,
629
+ responseText: "Pausing the recording.",
630
+ };
573
631
  const { ctx, sent, fakeSocket } = createCtx();
574
632
 
575
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
633
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
576
634
  await handleTaskSubmit(
577
- { type: 'task_submit', task: 'pause the recording', source: 'voice' } as any,
635
+ {
636
+ type: "task_submit",
637
+ task: "pause the recording",
638
+ source: "voice",
639
+ } as any,
578
640
  fakeSocket,
579
641
  ctx,
580
642
  );
@@ -583,22 +645,29 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
583
645
  expect(classifierCalled).toBe(false);
584
646
 
585
647
  const types = sent.map((m) => m.type);
586
- expect(types).toContain('task_routed');
587
- expect(types).toContain('assistant_text_delta');
588
- expect(types).toContain('message_complete');
648
+ expect(types).toContain("task_routed");
649
+ expect(types).toContain("assistant_text_delta");
650
+ expect(types).toContain("message_complete");
589
651
 
590
- const textDelta = sent.find((m) => m.type === 'assistant_text_delta');
591
- expect(textDelta?.text).toBe('Pausing the recording.');
652
+ const textDelta = sent.find((m) => m.type === "assistant_text_delta");
653
+ expect(textDelta?.text).toBe("Pausing the recording.");
592
654
  });
593
655
 
594
- test('resume_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early', async () => {
595
- mockIntentResult = { kind: 'resume_only' };
596
- mockExecuteResult = { handled: true, responseText: 'Resuming the recording.' };
656
+ test("resume_only → executeRecordingIntent called, sends task_routed + text_delta + message_complete, returns early", async () => {
657
+ mockIntentResult = { kind: "resume_only" };
658
+ mockExecuteResult = {
659
+ handled: true,
660
+ responseText: "Resuming the recording.",
661
+ };
597
662
  const { ctx, sent, fakeSocket } = createCtx();
598
663
 
599
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
664
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
600
665
  await handleTaskSubmit(
601
- { type: 'task_submit', task: 'resume the recording', source: 'voice' } as any,
666
+ {
667
+ type: "task_submit",
668
+ task: "resume the recording",
669
+ source: "voice",
670
+ } as any,
602
671
  fakeSocket,
603
672
  ctx,
604
673
  );
@@ -607,22 +676,33 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
607
676
  expect(classifierCalled).toBe(false);
608
677
 
609
678
  const types = sent.map((m) => m.type);
610
- expect(types).toContain('task_routed');
611
- expect(types).toContain('assistant_text_delta');
612
- expect(types).toContain('message_complete');
679
+ expect(types).toContain("task_routed");
680
+ expect(types).toContain("assistant_text_delta");
681
+ expect(types).toContain("message_complete");
613
682
 
614
- const textDelta = sent.find((m) => m.type === 'assistant_text_delta');
615
- expect(textDelta?.text).toBe('Resuming the recording.');
683
+ const textDelta = sent.find((m) => m.type === "assistant_text_delta");
684
+ expect(textDelta?.text).toBe("Resuming the recording.");
616
685
  });
617
686
 
618
- test('restart_with_remainder → defers restart, falls through to classifier with remaining text', async () => {
619
- mockIntentResult = { kind: 'restart_with_remainder', remainder: 'open Safari' };
620
- mockExecuteResult = { handled: false, remainderText: 'open Safari', pendingRestart: true };
687
+ test("restart_with_remainder → defers restart, falls through to classifier with remaining text", async () => {
688
+ mockIntentResult = {
689
+ kind: "restart_with_remainder",
690
+ remainder: "open Safari",
691
+ };
692
+ mockExecuteResult = {
693
+ handled: false,
694
+ remainderText: "open Safari",
695
+ pendingRestart: true,
696
+ };
621
697
  const { ctx, sent, fakeSocket } = createCtx();
622
698
 
623
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
699
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
624
700
  await handleTaskSubmit(
625
- { type: 'task_submit', task: 'restart the recording and open Safari', source: 'voice' } as any,
701
+ {
702
+ type: "task_submit",
703
+ task: "restart the recording and open Safari",
704
+ source: "voice",
705
+ } as any,
626
706
  fakeSocket,
627
707
  ctx,
628
708
  );
@@ -631,24 +711,26 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
631
711
 
632
712
  // Should NOT have restart-specific messages before classifier output
633
713
  const recordingSpecific = sent.filter(
634
- (m) => m.type === 'assistant_text_delta' && typeof m.text === 'string' &&
635
- m.text.includes('Restarting screen recording'),
714
+ (m) =>
715
+ m.type === "assistant_text_delta" &&
716
+ typeof m.text === "string" &&
717
+ m.text.includes("Restarting screen recording"),
636
718
  );
637
719
  expect(recordingSpecific).toHaveLength(0);
638
720
  });
639
721
 
640
- test('commandIntent restart → routes directly via handleRecordingRestart, returns early', async () => {
722
+ test("commandIntent restart → routes directly via handleRecordingRestart, returns early", async () => {
641
723
  // commandIntent bypasses text-based intent resolution entirely
642
- mockIntentResult = { kind: 'none' }; // should not matter
724
+ mockIntentResult = { kind: "none" }; // should not matter
643
725
  const { ctx, sent, fakeSocket } = createCtx();
644
726
 
645
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
727
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
646
728
  await handleTaskSubmit(
647
729
  {
648
- type: 'task_submit',
649
- task: 'restart recording',
650
- source: 'voice',
651
- commandIntent: { domain: 'screen_recording', action: 'restart' },
730
+ type: "task_submit",
731
+ task: "restart recording",
732
+ source: "voice",
733
+ commandIntent: { domain: "screen_recording", action: "restart" },
652
734
  } as any,
653
735
  fakeSocket,
654
736
  ctx,
@@ -658,22 +740,22 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
658
740
  expect(classifierCalled).toBe(false);
659
741
 
660
742
  const types = sent.map((m) => m.type);
661
- expect(types).toContain('task_routed');
662
- expect(types).toContain('assistant_text_delta');
663
- expect(types).toContain('message_complete');
743
+ expect(types).toContain("task_routed");
744
+ expect(types).toContain("assistant_text_delta");
745
+ expect(types).toContain("message_complete");
664
746
  });
665
747
 
666
- test('commandIntent pause → routes directly via handleRecordingPause, returns early', async () => {
667
- mockIntentResult = { kind: 'none' };
748
+ test("commandIntent pause → routes directly via handleRecordingPause, returns early", async () => {
749
+ mockIntentResult = { kind: "none" };
668
750
  const { ctx, sent, fakeSocket } = createCtx();
669
751
 
670
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
752
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
671
753
  await handleTaskSubmit(
672
754
  {
673
- type: 'task_submit',
674
- task: 'pause recording',
675
- source: 'voice',
676
- commandIntent: { domain: 'screen_recording', action: 'pause' },
755
+ type: "task_submit",
756
+ task: "pause recording",
757
+ source: "voice",
758
+ commandIntent: { domain: "screen_recording", action: "pause" },
677
759
  } as any,
678
760
  fakeSocket,
679
761
  ctx,
@@ -683,22 +765,22 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
683
765
  expect(classifierCalled).toBe(false);
684
766
 
685
767
  const types = sent.map((m) => m.type);
686
- expect(types).toContain('task_routed');
687
- expect(types).toContain('assistant_text_delta');
688
- expect(types).toContain('message_complete');
768
+ expect(types).toContain("task_routed");
769
+ expect(types).toContain("assistant_text_delta");
770
+ expect(types).toContain("message_complete");
689
771
  });
690
772
 
691
- test('commandIntent resume → routes directly via handleRecordingResume, returns early', async () => {
692
- mockIntentResult = { kind: 'none' };
773
+ test("commandIntent resume → routes directly via handleRecordingResume, returns early", async () => {
774
+ mockIntentResult = { kind: "none" };
693
775
  const { ctx, sent, fakeSocket } = createCtx();
694
776
 
695
- const { handleTaskSubmit } = await import('../daemon/handlers/misc.js');
777
+ const { handleTaskSubmit } = await import("../daemon/handlers/misc.js");
696
778
  await handleTaskSubmit(
697
779
  {
698
- type: 'task_submit',
699
- task: 'resume recording',
700
- source: 'voice',
701
- commandIntent: { domain: 'screen_recording', action: 'resume' },
780
+ type: "task_submit",
781
+ task: "resume recording",
782
+ source: "voice",
783
+ commandIntent: { domain: "screen_recording", action: "resume" },
702
784
  } as any,
703
785
  fakeSocket,
704
786
  ctx,
@@ -708,27 +790,32 @@ describe('recording intent handler integration — handleTaskSubmit', () => {
708
790
  expect(classifierCalled).toBe(false);
709
791
 
710
792
  const types = sent.map((m) => m.type);
711
- expect(types).toContain('task_routed');
712
- expect(types).toContain('assistant_text_delta');
713
- expect(types).toContain('message_complete');
793
+ expect(types).toContain("task_routed");
794
+ expect(types).toContain("assistant_text_delta");
795
+ expect(types).toContain("message_complete");
714
796
  });
715
797
  });
716
798
 
717
- describe('recording intent handler integration — handleUserMessage', () => {
799
+ describe("recording intent handler integration — handleUserMessage", () => {
718
800
  beforeEach(resetMockState);
719
801
 
720
- test('start_only → executeRecordingIntent called, sends text_delta + message_complete, returns early', async () => {
721
- mockIntentResult = { kind: 'start_only' };
722
- mockExecuteResult = { handled: true, responseText: 'Starting screen recording.', recordingStarted: true };
802
+ test("start_only → executeRecordingIntent called, sends text_delta + message_complete, returns early", async () => {
803
+ mockIntentResult = { kind: "start_only" };
804
+ mockExecuteResult = {
805
+ handled: true,
806
+ responseText: "Starting screen recording.",
807
+ recordingStarted: true,
808
+ };
723
809
  const { ctx, sent, fakeSocket } = createCtx();
724
810
 
725
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
811
+ const { handleUserMessage } =
812
+ await import("../daemon/handlers/sessions.js");
726
813
  await handleUserMessage(
727
814
  {
728
- type: 'user_message',
729
- sessionId: 'test-session',
730
- content: 'record my screen',
731
- interface: 'vellum',
815
+ type: "user_message",
816
+ sessionId: "test-session",
817
+ content: "record my screen",
818
+ interface: "vellum",
732
819
  } as any,
733
820
  fakeSocket,
734
821
  ctx,
@@ -737,26 +824,30 @@ describe('recording intent handler integration — handleUserMessage', () => {
737
824
  expect(executorCalled).toBe(true);
738
825
 
739
826
  const types = sent.map((m) => m.type);
740
- expect(types).toContain('assistant_text_delta');
741
- expect(types).toContain('message_complete');
827
+ expect(types).toContain("assistant_text_delta");
828
+ expect(types).toContain("message_complete");
742
829
 
743
830
  // message_complete should be the last message sent (recording returned early)
744
831
  const lastMsg = sent[sent.length - 1];
745
- expect(lastMsg.type).toBe('message_complete');
832
+ expect(lastMsg.type).toBe("message_complete");
746
833
  });
747
834
 
748
- test('stop_only → executeRecordingIntent called, sends text_delta + message_complete, returns early', async () => {
749
- mockIntentResult = { kind: 'stop_only' };
750
- mockExecuteResult = { handled: true, responseText: 'Stopping the recording.' };
835
+ test("stop_only → executeRecordingIntent called, sends text_delta + message_complete, returns early", async () => {
836
+ mockIntentResult = { kind: "stop_only" };
837
+ mockExecuteResult = {
838
+ handled: true,
839
+ responseText: "Stopping the recording.",
840
+ };
751
841
  const { ctx, sent, fakeSocket } = createCtx();
752
842
 
753
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
843
+ const { handleUserMessage } =
844
+ await import("../daemon/handlers/sessions.js");
754
845
  await handleUserMessage(
755
846
  {
756
- type: 'user_message',
757
- sessionId: 'test-session',
758
- content: 'stop recording',
759
- interface: 'vellum',
847
+ type: "user_message",
848
+ sessionId: "test-session",
849
+ content: "stop recording",
850
+ interface: "vellum",
760
851
  } as any,
761
852
  fakeSocket,
762
853
  ctx,
@@ -765,25 +856,33 @@ describe('recording intent handler integration — handleUserMessage', () => {
765
856
  expect(executorCalled).toBe(true);
766
857
 
767
858
  const types = sent.map((m) => m.type);
768
- expect(types).toContain('assistant_text_delta');
769
- expect(types).toContain('message_complete');
859
+ expect(types).toContain("assistant_text_delta");
860
+ expect(types).toContain("message_complete");
770
861
 
771
862
  const lastMsg = sent[sent.length - 1];
772
- expect(lastMsg.type).toBe('message_complete');
863
+ expect(lastMsg.type).toBe("message_complete");
773
864
  });
774
865
 
775
- test('start_with_remainder → does NOT return early, proceeds to normal message processing', async () => {
776
- mockIntentResult = { kind: 'start_with_remainder', remainder: 'open Safari' };
777
- mockExecuteResult = { handled: false, remainderText: 'open Safari', pendingStart: true };
866
+ test("start_with_remainder → does NOT return early, proceeds to normal message processing", async () => {
867
+ mockIntentResult = {
868
+ kind: "start_with_remainder",
869
+ remainder: "open Safari",
870
+ };
871
+ mockExecuteResult = {
872
+ handled: false,
873
+ remainderText: "open Safari",
874
+ pendingStart: true,
875
+ };
778
876
  const { ctx, sent, fakeSocket } = createCtx();
779
877
 
780
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
878
+ const { handleUserMessage } =
879
+ await import("../daemon/handlers/sessions.js");
781
880
  await handleUserMessage(
782
881
  {
783
- type: 'user_message',
784
- sessionId: 'test-session',
785
- content: 'open Safari and record my screen',
786
- interface: 'vellum',
882
+ type: "user_message",
883
+ sessionId: "test-session",
884
+ content: "open Safari and record my screen",
885
+ interface: "vellum",
787
886
  } as any,
788
887
  fakeSocket,
789
888
  ctx,
@@ -794,23 +893,27 @@ describe('recording intent handler integration — handleUserMessage', () => {
794
893
 
795
894
  // Should NOT have recording-specific intercept messages
796
895
  const recordingSpecific = sent.filter(
797
- (m) => m.type === 'assistant_text_delta' && typeof m.text === 'string' &&
798
- (m.text.includes('Starting screen recording') || m.text.includes('Stopping the recording')),
896
+ (m) =>
897
+ m.type === "assistant_text_delta" &&
898
+ typeof m.text === "string" &&
899
+ (m.text.includes("Starting screen recording") ||
900
+ m.text.includes("Stopping the recording")),
799
901
  );
800
902
  expect(recordingSpecific).toHaveLength(0);
801
903
  });
802
904
 
803
- test('none → does NOT intercept, proceeds to normal message processing', async () => {
804
- mockIntentResult = { kind: 'none' };
905
+ test("none → does NOT intercept, proceeds to normal message processing", async () => {
906
+ mockIntentResult = { kind: "none" };
805
907
  const { ctx, sent, fakeSocket } = createCtx();
806
908
 
807
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
909
+ const { handleUserMessage } =
910
+ await import("../daemon/handlers/sessions.js");
808
911
  await handleUserMessage(
809
912
  {
810
- type: 'user_message',
811
- sessionId: 'test-session',
812
- content: 'hello world',
813
- interface: 'vellum',
913
+ type: "user_message",
914
+ sessionId: "test-session",
915
+ content: "hello world",
916
+ interface: "vellum",
814
917
  } as any,
815
918
  fakeSocket,
816
919
  ctx,
@@ -820,24 +923,31 @@ describe('recording intent handler integration — handleUserMessage', () => {
820
923
 
821
924
  // Should NOT have recording-specific messages
822
925
  const recordingSpecific = sent.filter(
823
- (m) => m.type === 'assistant_text_delta' && typeof m.text === 'string' &&
824
- (m.text.includes('Starting screen recording') || m.text.includes('Stopping the recording')),
926
+ (m) =>
927
+ m.type === "assistant_text_delta" &&
928
+ typeof m.text === "string" &&
929
+ (m.text.includes("Starting screen recording") ||
930
+ m.text.includes("Stopping the recording")),
825
931
  );
826
932
  expect(recordingSpecific).toHaveLength(0);
827
933
  });
828
934
 
829
- test('restart_only → executeRecordingIntent called, sends text_delta + message_complete, returns early', async () => {
830
- mockIntentResult = { kind: 'restart_only' };
831
- mockExecuteResult = { handled: true, responseText: 'Restarting screen recording.' };
935
+ test("restart_only → executeRecordingIntent called, sends text_delta + message_complete, returns early", async () => {
936
+ mockIntentResult = { kind: "restart_only" };
937
+ mockExecuteResult = {
938
+ handled: true,
939
+ responseText: "Restarting screen recording.",
940
+ };
832
941
  const { ctx, sent, fakeSocket } = createCtx();
833
942
 
834
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
943
+ const { handleUserMessage } =
944
+ await import("../daemon/handlers/sessions.js");
835
945
  await handleUserMessage(
836
946
  {
837
- type: 'user_message',
838
- sessionId: 'test-session',
839
- content: 'restart the recording',
840
- interface: 'vellum',
947
+ type: "user_message",
948
+ sessionId: "test-session",
949
+ content: "restart the recording",
950
+ interface: "vellum",
841
951
  } as any,
842
952
  fakeSocket,
843
953
  ctx,
@@ -846,25 +956,29 @@ describe('recording intent handler integration — handleUserMessage', () => {
846
956
  expect(executorCalled).toBe(true);
847
957
 
848
958
  const types = sent.map((m) => m.type);
849
- expect(types).toContain('assistant_text_delta');
850
- expect(types).toContain('message_complete');
959
+ expect(types).toContain("assistant_text_delta");
960
+ expect(types).toContain("message_complete");
851
961
 
852
962
  const lastMsg = sent[sent.length - 1];
853
- expect(lastMsg.type).toBe('message_complete');
963
+ expect(lastMsg.type).toBe("message_complete");
854
964
  });
855
965
 
856
- test('pause_only → executeRecordingIntent called, sends text_delta + message_complete, returns early', async () => {
857
- mockIntentResult = { kind: 'pause_only' };
858
- mockExecuteResult = { handled: true, responseText: 'Pausing the recording.' };
966
+ test("pause_only → executeRecordingIntent called, sends text_delta + message_complete, returns early", async () => {
967
+ mockIntentResult = { kind: "pause_only" };
968
+ mockExecuteResult = {
969
+ handled: true,
970
+ responseText: "Pausing the recording.",
971
+ };
859
972
  const { ctx, sent, fakeSocket } = createCtx();
860
973
 
861
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
974
+ const { handleUserMessage } =
975
+ await import("../daemon/handlers/sessions.js");
862
976
  await handleUserMessage(
863
977
  {
864
- type: 'user_message',
865
- sessionId: 'test-session',
866
- content: 'pause the recording',
867
- interface: 'vellum',
978
+ type: "user_message",
979
+ sessionId: "test-session",
980
+ content: "pause the recording",
981
+ interface: "vellum",
868
982
  } as any,
869
983
  fakeSocket,
870
984
  ctx,
@@ -873,25 +987,29 @@ describe('recording intent handler integration — handleUserMessage', () => {
873
987
  expect(executorCalled).toBe(true);
874
988
 
875
989
  const types = sent.map((m) => m.type);
876
- expect(types).toContain('assistant_text_delta');
877
- expect(types).toContain('message_complete');
990
+ expect(types).toContain("assistant_text_delta");
991
+ expect(types).toContain("message_complete");
878
992
 
879
993
  const lastMsg = sent[sent.length - 1];
880
- expect(lastMsg.type).toBe('message_complete');
994
+ expect(lastMsg.type).toBe("message_complete");
881
995
  });
882
996
 
883
- test('resume_only → executeRecordingIntent called, sends text_delta + message_complete, returns early', async () => {
884
- mockIntentResult = { kind: 'resume_only' };
885
- mockExecuteResult = { handled: true, responseText: 'Resuming the recording.' };
997
+ test("resume_only → executeRecordingIntent called, sends text_delta + message_complete, returns early", async () => {
998
+ mockIntentResult = { kind: "resume_only" };
999
+ mockExecuteResult = {
1000
+ handled: true,
1001
+ responseText: "Resuming the recording.",
1002
+ };
886
1003
  const { ctx, sent, fakeSocket } = createCtx();
887
1004
 
888
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
1005
+ const { handleUserMessage } =
1006
+ await import("../daemon/handlers/sessions.js");
889
1007
  await handleUserMessage(
890
1008
  {
891
- type: 'user_message',
892
- sessionId: 'test-session',
893
- content: 'resume the recording',
894
- interface: 'vellum',
1009
+ type: "user_message",
1010
+ sessionId: "test-session",
1011
+ content: "resume the recording",
1012
+ interface: "vellum",
895
1013
  } as any,
896
1014
  fakeSocket,
897
1015
  ctx,
@@ -900,25 +1018,33 @@ describe('recording intent handler integration — handleUserMessage', () => {
900
1018
  expect(executorCalled).toBe(true);
901
1019
 
902
1020
  const types = sent.map((m) => m.type);
903
- expect(types).toContain('assistant_text_delta');
904
- expect(types).toContain('message_complete');
1021
+ expect(types).toContain("assistant_text_delta");
1022
+ expect(types).toContain("message_complete");
905
1023
 
906
1024
  const lastMsg = sent[sent.length - 1];
907
- expect(lastMsg.type).toBe('message_complete');
1025
+ expect(lastMsg.type).toBe("message_complete");
908
1026
  });
909
1027
 
910
- test('restart_with_remainder → defers restart, continues with remaining text', async () => {
911
- mockIntentResult = { kind: 'restart_with_remainder', remainder: 'open Safari' };
912
- mockExecuteResult = { handled: false, remainderText: 'open Safari', pendingRestart: true };
1028
+ test("restart_with_remainder → defers restart, continues with remaining text", async () => {
1029
+ mockIntentResult = {
1030
+ kind: "restart_with_remainder",
1031
+ remainder: "open Safari",
1032
+ };
1033
+ mockExecuteResult = {
1034
+ handled: false,
1035
+ remainderText: "open Safari",
1036
+ pendingRestart: true,
1037
+ };
913
1038
  const { ctx, sent, fakeSocket } = createCtx();
914
1039
 
915
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
1040
+ const { handleUserMessage } =
1041
+ await import("../daemon/handlers/sessions.js");
916
1042
  await handleUserMessage(
917
1043
  {
918
- type: 'user_message',
919
- sessionId: 'test-session',
920
- content: 'restart the recording and open Safari',
921
- interface: 'vellum',
1044
+ type: "user_message",
1045
+ sessionId: "test-session",
1046
+ content: "restart the recording and open Safari",
1047
+ interface: "vellum",
922
1048
  } as any,
923
1049
  fakeSocket,
924
1050
  ctx,
@@ -929,24 +1055,27 @@ describe('recording intent handler integration — handleUserMessage', () => {
929
1055
 
930
1056
  // Should NOT have restart-specific intercept messages
931
1057
  const recordingSpecific = sent.filter(
932
- (m) => m.type === 'assistant_text_delta' && typeof m.text === 'string' &&
933
- m.text.includes('Restarting screen recording'),
1058
+ (m) =>
1059
+ m.type === "assistant_text_delta" &&
1060
+ typeof m.text === "string" &&
1061
+ m.text.includes("Restarting screen recording"),
934
1062
  );
935
1063
  expect(recordingSpecific).toHaveLength(0);
936
1064
  });
937
1065
 
938
- test('commandIntent restart → routes directly via handleRecordingRestart, returns early', async () => {
939
- mockIntentResult = { kind: 'none' };
1066
+ test("commandIntent restart → routes directly via handleRecordingRestart, returns early", async () => {
1067
+ mockIntentResult = { kind: "none" };
940
1068
  const { ctx, sent, fakeSocket } = createCtx();
941
1069
 
942
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
1070
+ const { handleUserMessage } =
1071
+ await import("../daemon/handlers/sessions.js");
943
1072
  await handleUserMessage(
944
1073
  {
945
- type: 'user_message',
946
- sessionId: 'test-session',
947
- content: 'restart recording',
948
- interface: 'vellum',
949
- commandIntent: { domain: 'screen_recording', action: 'restart' },
1074
+ type: "user_message",
1075
+ sessionId: "test-session",
1076
+ content: "restart recording",
1077
+ interface: "vellum",
1078
+ commandIntent: { domain: "screen_recording", action: "restart" },
950
1079
  } as any,
951
1080
  fakeSocket,
952
1081
  ctx,
@@ -955,25 +1084,26 @@ describe('recording intent handler integration — handleUserMessage', () => {
955
1084
  expect(recordingRestartCalled).toBe(true);
956
1085
 
957
1086
  const types = sent.map((m) => m.type);
958
- expect(types).toContain('assistant_text_delta');
959
- expect(types).toContain('message_complete');
1087
+ expect(types).toContain("assistant_text_delta");
1088
+ expect(types).toContain("message_complete");
960
1089
 
961
1090
  const lastMsg = sent[sent.length - 1];
962
- expect(lastMsg.type).toBe('message_complete');
1091
+ expect(lastMsg.type).toBe("message_complete");
963
1092
  });
964
1093
 
965
- test('commandIntent pause → routes directly via handleRecordingPause, returns early', async () => {
966
- mockIntentResult = { kind: 'none' };
1094
+ test("commandIntent pause → routes directly via handleRecordingPause, returns early", async () => {
1095
+ mockIntentResult = { kind: "none" };
967
1096
  const { ctx, sent, fakeSocket } = createCtx();
968
1097
 
969
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
1098
+ const { handleUserMessage } =
1099
+ await import("../daemon/handlers/sessions.js");
970
1100
  await handleUserMessage(
971
1101
  {
972
- type: 'user_message',
973
- sessionId: 'test-session',
974
- content: 'pause recording',
975
- interface: 'vellum',
976
- commandIntent: { domain: 'screen_recording', action: 'pause' },
1102
+ type: "user_message",
1103
+ sessionId: "test-session",
1104
+ content: "pause recording",
1105
+ interface: "vellum",
1106
+ commandIntent: { domain: "screen_recording", action: "pause" },
977
1107
  } as any,
978
1108
  fakeSocket,
979
1109
  ctx,
@@ -982,22 +1112,23 @@ describe('recording intent handler integration — handleUserMessage', () => {
982
1112
  expect(recordingPauseCalled).toBe(true);
983
1113
 
984
1114
  const types = sent.map((m) => m.type);
985
- expect(types).toContain('assistant_text_delta');
986
- expect(types).toContain('message_complete');
1115
+ expect(types).toContain("assistant_text_delta");
1116
+ expect(types).toContain("message_complete");
987
1117
  });
988
1118
 
989
- test('commandIntent resume → routes directly via handleRecordingResume, returns early', async () => {
990
- mockIntentResult = { kind: 'none' };
1119
+ test("commandIntent resume → routes directly via handleRecordingResume, returns early", async () => {
1120
+ mockIntentResult = { kind: "none" };
991
1121
  const { ctx, sent, fakeSocket } = createCtx();
992
1122
 
993
- const { handleUserMessage } = await import('../daemon/handlers/sessions.js');
1123
+ const { handleUserMessage } =
1124
+ await import("../daemon/handlers/sessions.js");
994
1125
  await handleUserMessage(
995
1126
  {
996
- type: 'user_message',
997
- sessionId: 'test-session',
998
- content: 'resume recording',
999
- interface: 'vellum',
1000
- commandIntent: { domain: 'screen_recording', action: 'resume' },
1127
+ type: "user_message",
1128
+ sessionId: "test-session",
1129
+ content: "resume recording",
1130
+ interface: "vellum",
1131
+ commandIntent: { domain: "screen_recording", action: "resume" },
1001
1132
  } as any,
1002
1133
  fakeSocket,
1003
1134
  ctx,
@@ -1006,7 +1137,7 @@ describe('recording intent handler integration — handleUserMessage', () => {
1006
1137
  expect(recordingResumeCalled).toBe(true);
1007
1138
 
1008
1139
  const types = sent.map((m) => m.type);
1009
- expect(types).toContain('assistant_text_delta');
1010
- expect(types).toContain('message_complete');
1140
+ expect(types).toContain("assistant_text_delta");
1141
+ expect(types).toContain("message_complete");
1011
1142
  });
1012
1143
  });