@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
@@ -4,42 +4,45 @@
4
4
  * Tests POST /v1/confirm, POST /v1/secret, and POST /v1/trust-rules
5
5
  * through RuntimeHttpServer with pending-interactions tracking.
6
6
  */
7
- import { mkdtempSync, realpathSync,rmSync } from 'node:fs';
8
- import { tmpdir } from 'node:os';
9
- import { join } from 'node:path';
7
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
10
 
11
- import { afterAll, beforeEach, describe, expect, mock,test } from 'bun:test';
11
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
12
12
 
13
- import type { ServerMessage } from '../daemon/ipc-protocol.js';
14
- import type { Session } from '../daemon/session.js';
13
+ import type { ServerMessage } from "../daemon/ipc-protocol.js";
14
+ import type { Session } from "../daemon/session.js";
15
15
 
16
- const testDir = realpathSync(mkdtempSync(join(tmpdir(), 'approval-routes-http-test-')));
16
+ const testDir = realpathSync(
17
+ mkdtempSync(join(tmpdir(), "approval-routes-http-test-")),
18
+ );
17
19
 
18
- mock.module('../util/platform.js', () => ({
20
+ mock.module("../util/platform.js", () => ({
19
21
  getRootDir: () => testDir,
20
22
  getDataDir: () => testDir,
21
- isMacOS: () => process.platform === 'darwin',
22
- isLinux: () => process.platform === 'linux',
23
- isWindows: () => process.platform === 'win32',
24
- getSocketPath: () => join(testDir, 'test.sock'),
25
- getPidPath: () => join(testDir, 'test.pid'),
26
- getDbPath: () => join(testDir, 'test.db'),
27
- getLogPath: () => join(testDir, 'test.log'),
23
+ isMacOS: () => process.platform === "darwin",
24
+ isLinux: () => process.platform === "linux",
25
+ isWindows: () => process.platform === "win32",
26
+ getSocketPath: () => join(testDir, "test.sock"),
27
+ getPidPath: () => join(testDir, "test.pid"),
28
+ getDbPath: () => join(testDir, "test.db"),
29
+ getLogPath: () => join(testDir, "test.log"),
28
30
  ensureDataDir: () => {},
29
31
  }));
30
32
 
31
- mock.module('../util/logger.js', () => ({
32
- getLogger: () => new Proxy({} as Record<string, unknown>, {
33
- get: () => () => {},
34
- }),
33
+ mock.module("../util/logger.js", () => ({
34
+ getLogger: () =>
35
+ new Proxy({} as Record<string, unknown>, {
36
+ get: () => () => {},
37
+ }),
35
38
  }));
36
39
 
37
- mock.module('../config/loader.js', () => ({
40
+ mock.module("../config/loader.js", () => ({
38
41
  getConfig: () => ({
39
42
  ui: {},
40
-
41
- model: 'test',
42
- provider: 'test',
43
+
44
+ model: "test",
45
+ provider: "test",
43
46
  apiKeys: {},
44
47
  memory: { enabled: false },
45
48
  rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
@@ -48,16 +51,36 @@ mock.module('../config/loader.js', () => ({
48
51
  }),
49
52
  }));
50
53
 
54
+ mock.module("../config/env.js", () => ({
55
+ isHttpAuthDisabled: () => true,
56
+ hasUngatedHttpAuthDisabled: () => false,
57
+ getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
58
+ getGatewayPort: () => 7830,
59
+ getRuntimeHttpPort: () => 7821,
60
+ getRuntimeHttpHost: () => "127.0.0.1",
61
+ getRuntimeProxyBearerToken: () => undefined,
62
+ getRuntimeGatewayOriginSecret: () => undefined,
63
+ getIngressPublicBaseUrl: () => undefined,
64
+ setIngressPublicBaseUrl: () => {},
65
+ }));
66
+
51
67
  // Mock the trust store so addRule doesn't touch disk or require full config
52
- mock.module('../permissions/trust-store.js', () => ({
53
- addRule: () => ({ id: 'test-rule', tool: 'test', pattern: '*', scope: 'everywhere', decision: 'allow', priority: 100 }),
68
+ mock.module("../permissions/trust-store.js", () => ({
69
+ addRule: () => ({
70
+ id: "test-rule",
71
+ tool: "test",
72
+ pattern: "*",
73
+ scope: "everywhere",
74
+ decision: "allow",
75
+ priority: 100,
76
+ }),
54
77
  getRules: () => [],
55
78
  }));
56
79
 
57
- import { getDb, initializeDb, resetDb } from '../memory/db.js';
58
- import { AssistantEventHub } from '../runtime/assistant-event-hub.js';
59
- import { RuntimeHttpServer } from '../runtime/http-server.js';
60
- import * as pendingInteractions from '../runtime/pending-interactions.js';
80
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
81
+ import { AssistantEventHub } from "../runtime/assistant-event-hub.js";
82
+ import { RuntimeHttpServer } from "../runtime/http-server.js";
83
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
61
84
 
62
85
  initializeDb();
63
86
 
@@ -72,11 +95,19 @@ function makeIdleSession(opts?: {
72
95
  let processing = false;
73
96
  return {
74
97
  isProcessing: () => processing,
75
- persistUserMessage: (_content: string, _attachments: unknown[], requestId?: string) => {
98
+ persistUserMessage: (
99
+ _content: string,
100
+ _attachments: unknown[],
101
+ requestId?: string,
102
+ ) => {
76
103
  processing = true;
77
- return requestId ?? 'msg-1';
104
+ return requestId ?? "msg-1";
105
+ },
106
+ memoryPolicy: {
107
+ scopeId: "default",
108
+ includeDefaultFallback: false,
109
+ strictSideEffects: false,
78
110
  },
79
- memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
80
111
  setChannelCapabilities: () => {},
81
112
  setAssistantId: () => {},
82
113
  setGuardianContext: () => {},
@@ -85,17 +116,25 @@ function makeIdleSession(opts?: {
85
116
  setTurnInterfaceContext: () => {},
86
117
  setStateSignalListener: () => {},
87
118
  updateClient: () => {},
88
- enqueueMessage: () => ({ queued: false, requestId: 'noop' }),
119
+ enqueueMessage: () => ({ queued: false, requestId: "noop" }),
89
120
  hasAnyPendingConfirmation: () => false,
90
- runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
91
- onEvent({ type: 'assistant_text_delta', text: 'Hello!' });
92
- onEvent({ type: 'message_complete', sessionId: 'test-session' });
121
+ runAgentLoop: async (
122
+ _content: string,
123
+ _messageId: string,
124
+ onEvent: (msg: ServerMessage) => void,
125
+ ) => {
126
+ onEvent({ type: "assistant_text_delta", text: "Hello!" });
127
+ onEvent({ type: "message_complete", sessionId: "test-session" });
93
128
  processing = false;
94
129
  },
95
130
  handleConfirmationResponse: (requestId: string, decision: string) => {
96
131
  opts?.onConfirmation?.(requestId, decision);
97
132
  },
98
- handleSecretResponse: (requestId: string, value?: string, delivery?: string) => {
133
+ handleSecretResponse: (
134
+ requestId: string,
135
+ value?: string,
136
+ delivery?: string,
137
+ ) => {
99
138
  opts?.onSecret?.(requestId, value, delivery);
100
139
  },
101
140
  } as unknown as Session;
@@ -111,15 +150,23 @@ function makeConfirmationEmittingSession(opts?: {
111
150
  toolName?: string;
112
151
  }): Session {
113
152
  let processing = false;
114
- const reqId = opts?.confirmRequestId ?? 'confirm-req-1';
115
- const tool = opts?.toolName ?? 'shell_command';
153
+ const reqId = opts?.confirmRequestId ?? "confirm-req-1";
154
+ const tool = opts?.toolName ?? "shell_command";
116
155
  return {
117
156
  isProcessing: () => processing,
118
- persistUserMessage: (_content: string, _attachments: unknown[], requestId?: string) => {
157
+ persistUserMessage: (
158
+ _content: string,
159
+ _attachments: unknown[],
160
+ requestId?: string,
161
+ ) => {
119
162
  processing = true;
120
- return requestId ?? 'msg-1';
163
+ return requestId ?? "msg-1";
164
+ },
165
+ memoryPolicy: {
166
+ scopeId: "default",
167
+ includeDefaultFallback: false,
168
+ strictSideEffects: false,
121
169
  },
122
- memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
123
170
  setChannelCapabilities: () => {},
124
171
  setAssistantId: () => {},
125
172
  setGuardianContext: () => {},
@@ -128,23 +175,25 @@ function makeConfirmationEmittingSession(opts?: {
128
175
  setTurnInterfaceContext: () => {},
129
176
  setStateSignalListener: () => {},
130
177
  updateClient: () => {},
131
- enqueueMessage: () => ({ queued: false, requestId: 'noop' }),
178
+ enqueueMessage: () => ({ queued: false, requestId: "noop" }),
132
179
  hasAnyPendingConfirmation: () => false,
133
- runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
180
+ runAgentLoop: async (
181
+ _content: string,
182
+ _messageId: string,
183
+ onEvent: (msg: ServerMessage) => void,
184
+ ) => {
134
185
  // Emit confirmation_request — this triggers the hub publisher to register
135
186
  // the pending interaction
136
187
  onEvent({
137
- type: 'confirmation_request',
188
+ type: "confirmation_request",
138
189
  requestId: reqId,
139
190
  toolName: tool,
140
- input: { command: 'ls' },
141
- riskLevel: 'medium',
191
+ input: { command: "ls" },
192
+ riskLevel: "medium",
142
193
  allowlistOptions: [
143
- { label: 'Allow ls', description: 'Allow ls command', pattern: 'ls' },
144
- ],
145
- scopeOptions: [
146
- { label: 'This session', scope: 'session' },
194
+ { label: "Allow ls", description: "Allow ls command", pattern: "ls" },
147
195
  ],
196
+ scopeOptions: [{ label: "This session", scope: "session" }],
148
197
  persistentDecisionsAllowed: true,
149
198
  });
150
199
  // Hang to simulate waiting for decision
@@ -161,26 +210,30 @@ function makeConfirmationEmittingSession(opts?: {
161
210
  // Tests
162
211
  // ---------------------------------------------------------------------------
163
212
 
164
- const TEST_TOKEN = 'test-bearer-token-approvals';
213
+ const TEST_TOKEN = "test-bearer-token-approvals";
165
214
  const AUTH_HEADERS = { Authorization: `Bearer ${TEST_TOKEN}` };
166
215
 
167
- describe('standalone approval endpoints — HTTP layer', () => {
216
+ describe("standalone approval endpoints — HTTP layer", () => {
168
217
  let server: RuntimeHttpServer;
169
218
  let port: number;
170
219
  let eventHub: AssistantEventHub;
171
220
 
172
221
  beforeEach(() => {
173
222
  const db = getDb();
174
- db.run('DELETE FROM messages');
175
- db.run('DELETE FROM conversations');
176
- db.run('DELETE FROM conversation_keys');
223
+ db.run("DELETE FROM messages");
224
+ db.run("DELETE FROM conversations");
225
+ db.run("DELETE FROM conversation_keys");
177
226
  pendingInteractions.clear();
178
227
  eventHub = new AssistantEventHub();
179
228
  });
180
229
 
181
230
  afterAll(() => {
182
231
  resetDb();
183
- try { rmSync(testDir, { recursive: true, force: true }); } catch { /* best effort */ }
232
+ try {
233
+ rmSync(testDir, { recursive: true, force: true });
234
+ } catch {
235
+ /* best effort */
236
+ }
184
237
  });
185
238
 
186
239
  async function startServer(sessionFactory: () => Session): Promise<void> {
@@ -207,8 +260,8 @@ describe('standalone approval endpoints — HTTP layer', () => {
207
260
 
208
261
  // ── POST /v1/confirm ─────────────────────────────────────────────────
209
262
 
210
- describe('POST /v1/confirm', () => {
211
- test('resolves a pending confirmation by requestId', async () => {
263
+ describe("POST /v1/confirm", () => {
264
+ test("resolves a pending confirmation by requestId", async () => {
212
265
  let confirmedRequestId: string | undefined;
213
266
  let confirmedDecision: string | undefined;
214
267
 
@@ -222,44 +275,44 @@ describe('standalone approval endpoints — HTTP layer', () => {
222
275
  await startServer(() => session);
223
276
 
224
277
  // Manually register a pending interaction
225
- pendingInteractions.register('req-abc', {
278
+ pendingInteractions.register("req-abc", {
226
279
  session,
227
- conversationId: 'conv-1',
228
- kind: 'confirmation',
280
+ conversationId: "conv-1",
281
+ kind: "confirmation",
229
282
  confirmationDetails: {
230
- toolName: 'shell_command',
231
- input: { command: 'ls' },
232
- riskLevel: 'medium',
283
+ toolName: "shell_command",
284
+ input: { command: "ls" },
285
+ riskLevel: "medium",
233
286
  allowlistOptions: [],
234
287
  scopeOptions: [],
235
288
  },
236
289
  });
237
290
 
238
- const res = await fetch(url('confirm'), {
239
- method: 'POST',
240
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
241
- body: JSON.stringify({ requestId: 'req-abc', decision: 'allow' }),
291
+ const res = await fetch(url("confirm"), {
292
+ method: "POST",
293
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
294
+ body: JSON.stringify({ requestId: "req-abc", decision: "allow" }),
242
295
  });
243
- const body = await res.json() as { accepted: boolean };
296
+ const body = (await res.json()) as { accepted: boolean };
244
297
 
245
298
  expect(res.status).toBe(200);
246
299
  expect(body.accepted).toBe(true);
247
- expect(confirmedRequestId).toBe('req-abc');
248
- expect(confirmedDecision).toBe('allow');
300
+ expect(confirmedRequestId).toBe("req-abc");
301
+ expect(confirmedDecision).toBe("allow");
249
302
 
250
303
  // Interaction should be removed after resolution
251
- expect(pendingInteractions.get('req-abc')).toBeUndefined();
304
+ expect(pendingInteractions.get("req-abc")).toBeUndefined();
252
305
 
253
306
  await stopServer();
254
307
  });
255
308
 
256
- test('returns 404 for unknown requestId', async () => {
309
+ test("returns 404 for unknown requestId", async () => {
257
310
  await startServer(() => makeIdleSession());
258
311
 
259
- const res = await fetch(url('confirm'), {
260
- method: 'POST',
261
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
262
- body: JSON.stringify({ requestId: 'nonexistent', decision: 'allow' }),
312
+ const res = await fetch(url("confirm"), {
313
+ method: "POST",
314
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
315
+ body: JSON.stringify({ requestId: "nonexistent", decision: "allow" }),
263
316
  });
264
317
 
265
318
  expect(res.status).toBe(404);
@@ -267,42 +320,42 @@ describe('standalone approval endpoints — HTTP layer', () => {
267
320
  await stopServer();
268
321
  });
269
322
 
270
- test('returns 404 for already-resolved requestId', async () => {
323
+ test("returns 404 for already-resolved requestId", async () => {
271
324
  const session = makeIdleSession();
272
325
  await startServer(() => session);
273
326
 
274
- pendingInteractions.register('req-once', {
327
+ pendingInteractions.register("req-once", {
275
328
  session,
276
- conversationId: 'conv-1',
277
- kind: 'confirmation',
329
+ conversationId: "conv-1",
330
+ kind: "confirmation",
278
331
  });
279
332
 
280
333
  // First resolution succeeds
281
- const res1 = await fetch(url('confirm'), {
282
- method: 'POST',
283
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
284
- body: JSON.stringify({ requestId: 'req-once', decision: 'allow' }),
334
+ const res1 = await fetch(url("confirm"), {
335
+ method: "POST",
336
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
337
+ body: JSON.stringify({ requestId: "req-once", decision: "allow" }),
285
338
  });
286
339
  expect(res1.status).toBe(200);
287
340
 
288
341
  // Second resolution fails (already consumed)
289
- const res2 = await fetch(url('confirm'), {
290
- method: 'POST',
291
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
292
- body: JSON.stringify({ requestId: 'req-once', decision: 'deny' }),
342
+ const res2 = await fetch(url("confirm"), {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
345
+ body: JSON.stringify({ requestId: "req-once", decision: "deny" }),
293
346
  });
294
347
  expect(res2.status).toBe(404);
295
348
 
296
349
  await stopServer();
297
350
  });
298
351
 
299
- test('returns 400 for missing requestId', async () => {
352
+ test("returns 400 for missing requestId", async () => {
300
353
  await startServer(() => makeIdleSession());
301
354
 
302
- const res = await fetch(url('confirm'), {
303
- method: 'POST',
304
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
305
- body: JSON.stringify({ decision: 'allow' }),
355
+ const res = await fetch(url("confirm"), {
356
+ method: "POST",
357
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
358
+ body: JSON.stringify({ decision: "allow" }),
306
359
  });
307
360
 
308
361
  expect(res.status).toBe(400);
@@ -310,13 +363,13 @@ describe('standalone approval endpoints — HTTP layer', () => {
310
363
  await stopServer();
311
364
  });
312
365
 
313
- test('returns 400 for invalid decision', async () => {
366
+ test("returns 400 for invalid decision", async () => {
314
367
  await startServer(() => makeIdleSession());
315
368
 
316
- const res = await fetch(url('confirm'), {
317
- method: 'POST',
318
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
319
- body: JSON.stringify({ requestId: 'req-1', decision: 'maybe' }),
369
+ const res = await fetch(url("confirm"), {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
372
+ body: JSON.stringify({ requestId: "req-1", decision: "maybe" }),
320
373
  });
321
374
 
322
375
  expect(res.status).toBe(400);
@@ -327,8 +380,8 @@ describe('standalone approval endpoints — HTTP layer', () => {
327
380
 
328
381
  // ── POST /v1/secret ──────────────────────────────────────────────────
329
382
 
330
- describe('POST /v1/secret', () => {
331
- test('resolves a pending secret request by requestId', async () => {
383
+ describe("POST /v1/secret", () => {
384
+ test("resolves a pending secret request by requestId", async () => {
332
385
  let secretRequestId: string | undefined;
333
386
  let secretValue: string | undefined;
334
387
  let secretDelivery: string | undefined;
@@ -343,38 +396,42 @@ describe('standalone approval endpoints — HTTP layer', () => {
343
396
 
344
397
  await startServer(() => session);
345
398
 
346
- pendingInteractions.register('secret-req-1', {
399
+ pendingInteractions.register("secret-req-1", {
347
400
  session,
348
- conversationId: 'conv-1',
349
- kind: 'secret',
401
+ conversationId: "conv-1",
402
+ kind: "secret",
350
403
  });
351
404
 
352
- const res = await fetch(url('secret'), {
353
- method: 'POST',
354
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
355
- body: JSON.stringify({ requestId: 'secret-req-1', value: 'my-secret-key', delivery: 'store' }),
405
+ const res = await fetch(url("secret"), {
406
+ method: "POST",
407
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
408
+ body: JSON.stringify({
409
+ requestId: "secret-req-1",
410
+ value: "my-secret-key",
411
+ delivery: "store",
412
+ }),
356
413
  });
357
- const body = await res.json() as { accepted: boolean };
414
+ const body = (await res.json()) as { accepted: boolean };
358
415
 
359
416
  expect(res.status).toBe(200);
360
417
  expect(body.accepted).toBe(true);
361
- expect(secretRequestId).toBe('secret-req-1');
362
- expect(secretValue).toBe('my-secret-key');
363
- expect(secretDelivery).toBe('store');
418
+ expect(secretRequestId).toBe("secret-req-1");
419
+ expect(secretValue).toBe("my-secret-key");
420
+ expect(secretDelivery).toBe("store");
364
421
 
365
422
  // Interaction should be removed after resolution
366
- expect(pendingInteractions.get('secret-req-1')).toBeUndefined();
423
+ expect(pendingInteractions.get("secret-req-1")).toBeUndefined();
367
424
 
368
425
  await stopServer();
369
426
  });
370
427
 
371
- test('returns 404 for unknown requestId', async () => {
428
+ test("returns 404 for unknown requestId", async () => {
372
429
  await startServer(() => makeIdleSession());
373
430
 
374
- const res = await fetch(url('secret'), {
375
- method: 'POST',
376
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
377
- body: JSON.stringify({ requestId: 'nonexistent', value: 'test' }),
431
+ const res = await fetch(url("secret"), {
432
+ method: "POST",
433
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
434
+ body: JSON.stringify({ requestId: "nonexistent", value: "test" }),
378
435
  });
379
436
 
380
437
  expect(res.status).toBe(404);
@@ -382,13 +439,13 @@ describe('standalone approval endpoints — HTTP layer', () => {
382
439
  await stopServer();
383
440
  });
384
441
 
385
- test('returns 400 for missing requestId', async () => {
442
+ test("returns 400 for missing requestId", async () => {
386
443
  await startServer(() => makeIdleSession());
387
444
 
388
- const res = await fetch(url('secret'), {
389
- method: 'POST',
390
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
391
- body: JSON.stringify({ value: 'test' }),
445
+ const res = await fetch(url("secret"), {
446
+ method: "POST",
447
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
448
+ body: JSON.stringify({ value: "test" }),
392
449
  });
393
450
 
394
451
  expect(res.status).toBe(400);
@@ -396,13 +453,17 @@ describe('standalone approval endpoints — HTTP layer', () => {
396
453
  await stopServer();
397
454
  });
398
455
 
399
- test('returns 400 for invalid delivery', async () => {
456
+ test("returns 400 for invalid delivery", async () => {
400
457
  await startServer(() => makeIdleSession());
401
458
 
402
- const res = await fetch(url('secret'), {
403
- method: 'POST',
404
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
405
- body: JSON.stringify({ requestId: 'req-1', value: 'test', delivery: 'invalid' }),
459
+ const res = await fetch(url("secret"), {
460
+ method: "POST",
461
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
462
+ body: JSON.stringify({
463
+ requestId: "req-1",
464
+ value: "test",
465
+ delivery: "invalid",
466
+ }),
406
467
  });
407
468
 
408
469
  expect(res.status).toBe(400);
@@ -413,14 +474,19 @@ describe('standalone approval endpoints — HTTP layer', () => {
413
474
 
414
475
  // ── POST /v1/trust-rules ─────────────────────────────────────────────
415
476
 
416
- describe('POST /v1/trust-rules', () => {
417
- test('returns 404 for unknown requestId', async () => {
477
+ describe("POST /v1/trust-rules", () => {
478
+ test("returns 404 for unknown requestId", async () => {
418
479
  await startServer(() => makeIdleSession());
419
480
 
420
- const res = await fetch(url('trust-rules'), {
421
- method: 'POST',
422
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
423
- body: JSON.stringify({ requestId: 'nonexistent', pattern: 'ls', scope: 'session', decision: 'allow' }),
481
+ const res = await fetch(url("trust-rules"), {
482
+ method: "POST",
483
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
484
+ body: JSON.stringify({
485
+ requestId: "nonexistent",
486
+ pattern: "ls",
487
+ scope: "session",
488
+ decision: "allow",
489
+ }),
424
490
  });
425
491
 
426
492
  expect(res.status).toBe(404);
@@ -428,13 +494,17 @@ describe('standalone approval endpoints — HTTP layer', () => {
428
494
  await stopServer();
429
495
  });
430
496
 
431
- test('returns 400 for missing requestId', async () => {
497
+ test("returns 400 for missing requestId", async () => {
432
498
  await startServer(() => makeIdleSession());
433
499
 
434
- const res = await fetch(url('trust-rules'), {
435
- method: 'POST',
436
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
437
- body: JSON.stringify({ pattern: 'ls', scope: 'session', decision: 'allow' }),
500
+ const res = await fetch(url("trust-rules"), {
501
+ method: "POST",
502
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
503
+ body: JSON.stringify({
504
+ pattern: "ls",
505
+ scope: "session",
506
+ decision: "allow",
507
+ }),
438
508
  });
439
509
 
440
510
  expect(res.status).toBe(400);
@@ -442,13 +512,17 @@ describe('standalone approval endpoints — HTTP layer', () => {
442
512
  await stopServer();
443
513
  });
444
514
 
445
- test('returns 400 for missing pattern', async () => {
515
+ test("returns 400 for missing pattern", async () => {
446
516
  await startServer(() => makeIdleSession());
447
517
 
448
- const res = await fetch(url('trust-rules'), {
449
- method: 'POST',
450
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
451
- body: JSON.stringify({ requestId: 'req-1', scope: 'session', decision: 'allow' }),
518
+ const res = await fetch(url("trust-rules"), {
519
+ method: "POST",
520
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
521
+ body: JSON.stringify({
522
+ requestId: "req-1",
523
+ scope: "session",
524
+ decision: "allow",
525
+ }),
452
526
  });
453
527
 
454
528
  expect(res.status).toBe(400);
@@ -456,13 +530,17 @@ describe('standalone approval endpoints — HTTP layer', () => {
456
530
  await stopServer();
457
531
  });
458
532
 
459
- test('returns 400 for missing scope', async () => {
533
+ test("returns 400 for missing scope", async () => {
460
534
  await startServer(() => makeIdleSession());
461
535
 
462
- const res = await fetch(url('trust-rules'), {
463
- method: 'POST',
464
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
465
- body: JSON.stringify({ requestId: 'req-1', pattern: 'ls', decision: 'allow' }),
536
+ const res = await fetch(url("trust-rules"), {
537
+ method: "POST",
538
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
539
+ body: JSON.stringify({
540
+ requestId: "req-1",
541
+ pattern: "ls",
542
+ decision: "allow",
543
+ }),
466
544
  });
467
545
 
468
546
  expect(res.status).toBe(400);
@@ -470,13 +548,18 @@ describe('standalone approval endpoints — HTTP layer', () => {
470
548
  await stopServer();
471
549
  });
472
550
 
473
- test('returns 400 for invalid decision', async () => {
551
+ test("returns 400 for invalid decision", async () => {
474
552
  await startServer(() => makeIdleSession());
475
553
 
476
- const res = await fetch(url('trust-rules'), {
477
- method: 'POST',
478
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
479
- body: JSON.stringify({ requestId: 'req-1', pattern: 'ls', scope: 'session', decision: 'maybe' }),
554
+ const res = await fetch(url("trust-rules"), {
555
+ method: "POST",
556
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
557
+ body: JSON.stringify({
558
+ requestId: "req-1",
559
+ pattern: "ls",
560
+ scope: "session",
561
+ decision: "maybe",
562
+ }),
480
563
  });
481
564
 
482
565
  expect(res.status).toBe(400);
@@ -484,21 +567,26 @@ describe('standalone approval endpoints — HTTP layer', () => {
484
567
  await stopServer();
485
568
  });
486
569
 
487
- test('returns 409 when no confirmation details available', async () => {
570
+ test("returns 409 when no confirmation details available", async () => {
488
571
  const session = makeIdleSession();
489
572
  await startServer(() => session);
490
573
 
491
574
  // Register without confirmationDetails
492
- pendingInteractions.register('req-no-details', {
575
+ pendingInteractions.register("req-no-details", {
493
576
  session,
494
- conversationId: 'conv-1',
495
- kind: 'secret',
577
+ conversationId: "conv-1",
578
+ kind: "secret",
496
579
  });
497
580
 
498
- const res = await fetch(url('trust-rules'), {
499
- method: 'POST',
500
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
501
- body: JSON.stringify({ requestId: 'req-no-details', pattern: 'ls', scope: 'session', decision: 'allow' }),
581
+ const res = await fetch(url("trust-rules"), {
582
+ method: "POST",
583
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
584
+ body: JSON.stringify({
585
+ requestId: "req-no-details",
586
+ pattern: "ls",
587
+ scope: "session",
588
+ decision: "allow",
589
+ }),
502
590
  });
503
591
 
504
592
  expect(res.status).toBe(409);
@@ -506,28 +594,35 @@ describe('standalone approval endpoints — HTTP layer', () => {
506
594
  await stopServer();
507
595
  });
508
596
 
509
- test('returns 403 when persistent decisions are not allowed', async () => {
597
+ test("returns 403 when persistent decisions are not allowed", async () => {
510
598
  const session = makeIdleSession();
511
599
  await startServer(() => session);
512
600
 
513
- pendingInteractions.register('req-no-persist', {
601
+ pendingInteractions.register("req-no-persist", {
514
602
  session,
515
- conversationId: 'conv-1',
516
- kind: 'confirmation',
603
+ conversationId: "conv-1",
604
+ kind: "confirmation",
517
605
  confirmationDetails: {
518
- toolName: 'shell_command',
519
- input: { command: 'rm -rf' },
520
- riskLevel: 'high',
521
- allowlistOptions: [{ label: 'Allow', description: 'test', pattern: 'rm' }],
522
- scopeOptions: [{ label: 'Session', scope: 'session' }],
606
+ toolName: "shell_command",
607
+ input: { command: "rm -rf" },
608
+ riskLevel: "high",
609
+ allowlistOptions: [
610
+ { label: "Allow", description: "test", pattern: "rm" },
611
+ ],
612
+ scopeOptions: [{ label: "Session", scope: "session" }],
523
613
  persistentDecisionsAllowed: false,
524
614
  },
525
615
  });
526
616
 
527
- const res = await fetch(url('trust-rules'), {
528
- method: 'POST',
529
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
530
- body: JSON.stringify({ requestId: 'req-no-persist', pattern: 'rm', scope: 'session', decision: 'allow' }),
617
+ const res = await fetch(url("trust-rules"), {
618
+ method: "POST",
619
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
620
+ body: JSON.stringify({
621
+ requestId: "req-no-persist",
622
+ pattern: "rm",
623
+ scope: "session",
624
+ decision: "allow",
625
+ }),
531
626
  });
532
627
 
533
628
  expect(res.status).toBe(403);
@@ -535,93 +630,118 @@ describe('standalone approval endpoints — HTTP layer', () => {
535
630
  await stopServer();
536
631
  });
537
632
 
538
- test('returns 403 when pattern does not match allowlist', async () => {
633
+ test("returns 403 when pattern does not match allowlist", async () => {
539
634
  const session = makeIdleSession();
540
635
  await startServer(() => session);
541
636
 
542
- pendingInteractions.register('req-bad-pattern', {
637
+ pendingInteractions.register("req-bad-pattern", {
543
638
  session,
544
- conversationId: 'conv-1',
545
- kind: 'confirmation',
639
+ conversationId: "conv-1",
640
+ kind: "confirmation",
546
641
  confirmationDetails: {
547
- toolName: 'shell_command',
548
- input: { command: 'ls' },
549
- riskLevel: 'medium',
550
- allowlistOptions: [{ label: 'Allow ls', description: 'test', pattern: 'ls' }],
551
- scopeOptions: [{ label: 'Session', scope: 'session' }],
642
+ toolName: "shell_command",
643
+ input: { command: "ls" },
644
+ riskLevel: "medium",
645
+ allowlistOptions: [
646
+ { label: "Allow ls", description: "test", pattern: "ls" },
647
+ ],
648
+ scopeOptions: [{ label: "Session", scope: "session" }],
552
649
  },
553
650
  });
554
651
 
555
- const res = await fetch(url('trust-rules'), {
556
- method: 'POST',
557
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
558
- body: JSON.stringify({ requestId: 'req-bad-pattern', pattern: 'rm', scope: 'session', decision: 'allow' }),
652
+ const res = await fetch(url("trust-rules"), {
653
+ method: "POST",
654
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
655
+ body: JSON.stringify({
656
+ requestId: "req-bad-pattern",
657
+ pattern: "rm",
658
+ scope: "session",
659
+ decision: "allow",
660
+ }),
559
661
  });
560
662
 
561
663
  expect(res.status).toBe(403);
562
- const body = await res.json() as { error: { message: string; code?: string } };
563
- expect(body.error.message).toContain('pattern');
664
+ const body = (await res.json()) as {
665
+ error: { message: string; code?: string };
666
+ };
667
+ expect(body.error.message).toContain("pattern");
564
668
 
565
669
  await stopServer();
566
670
  });
567
671
 
568
- test('returns 403 when scope does not match scope options', async () => {
672
+ test("returns 403 when scope does not match scope options", async () => {
569
673
  const session = makeIdleSession();
570
674
  await startServer(() => session);
571
675
 
572
- pendingInteractions.register('req-bad-scope', {
676
+ pendingInteractions.register("req-bad-scope", {
573
677
  session,
574
- conversationId: 'conv-1',
575
- kind: 'confirmation',
678
+ conversationId: "conv-1",
679
+ kind: "confirmation",
576
680
  confirmationDetails: {
577
- toolName: 'shell_command',
578
- input: { command: 'ls' },
579
- riskLevel: 'medium',
580
- allowlistOptions: [{ label: 'Allow ls', description: 'test', pattern: 'ls' }],
581
- scopeOptions: [{ label: 'Session', scope: 'session' }],
681
+ toolName: "shell_command",
682
+ input: { command: "ls" },
683
+ riskLevel: "medium",
684
+ allowlistOptions: [
685
+ { label: "Allow ls", description: "test", pattern: "ls" },
686
+ ],
687
+ scopeOptions: [{ label: "Session", scope: "session" }],
582
688
  },
583
689
  });
584
690
 
585
- const res = await fetch(url('trust-rules'), {
586
- method: 'POST',
587
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
588
- body: JSON.stringify({ requestId: 'req-bad-scope', pattern: 'ls', scope: 'global', decision: 'allow' }),
691
+ const res = await fetch(url("trust-rules"), {
692
+ method: "POST",
693
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
694
+ body: JSON.stringify({
695
+ requestId: "req-bad-scope",
696
+ pattern: "ls",
697
+ scope: "global",
698
+ decision: "allow",
699
+ }),
589
700
  });
590
701
 
591
702
  expect(res.status).toBe(403);
592
- const body = await res.json() as { error: { message: string; code?: string } };
593
- expect(body.error.message).toContain('scope');
703
+ const body = (await res.json()) as {
704
+ error: { message: string; code?: string };
705
+ };
706
+ expect(body.error.message).toContain("scope");
594
707
 
595
708
  await stopServer();
596
709
  });
597
710
 
598
- test('does not remove the pending interaction after adding trust rule', async () => {
711
+ test("does not remove the pending interaction after adding trust rule", async () => {
599
712
  const session = makeIdleSession();
600
713
  await startServer(() => session);
601
714
 
602
- pendingInteractions.register('req-keep', {
715
+ pendingInteractions.register("req-keep", {
603
716
  session,
604
- conversationId: 'conv-1',
605
- kind: 'confirmation',
717
+ conversationId: "conv-1",
718
+ kind: "confirmation",
606
719
  confirmationDetails: {
607
- toolName: 'shell_command',
608
- input: { command: 'ls' },
609
- riskLevel: 'medium',
610
- allowlistOptions: [{ label: 'Allow ls', description: 'test', pattern: 'ls' }],
611
- scopeOptions: [{ label: 'Session', scope: 'session' }],
720
+ toolName: "shell_command",
721
+ input: { command: "ls" },
722
+ riskLevel: "medium",
723
+ allowlistOptions: [
724
+ { label: "Allow ls", description: "test", pattern: "ls" },
725
+ ],
726
+ scopeOptions: [{ label: "Session", scope: "session" }],
612
727
  },
613
728
  });
614
729
 
615
- const res = await fetch(url('trust-rules'), {
616
- method: 'POST',
617
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
618
- body: JSON.stringify({ requestId: 'req-keep', pattern: 'ls', scope: 'session', decision: 'allow' }),
730
+ const res = await fetch(url("trust-rules"), {
731
+ method: "POST",
732
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
733
+ body: JSON.stringify({
734
+ requestId: "req-keep",
735
+ pattern: "ls",
736
+ scope: "session",
737
+ decision: "allow",
738
+ }),
619
739
  });
620
740
 
621
741
  expect(res.status).toBe(200);
622
742
 
623
743
  // Interaction should still be present (not consumed)
624
- expect(pendingInteractions.get('req-keep')).toBeDefined();
744
+ expect(pendingInteractions.get("req-keep")).toBeDefined();
625
745
 
626
746
  await stopServer();
627
747
  });
@@ -629,13 +749,14 @@ describe('standalone approval endpoints — HTTP layer', () => {
629
749
 
630
750
  // ── Hub publisher integration ────────────────────────────────────────
631
751
 
632
- describe('hub publisher registers pending interactions', () => {
633
- test('confirmation_request events register pending interactions', async () => {
634
- const confirmReceived: Array<{ requestId: string; decision: string }> = [];
752
+ describe("hub publisher registers pending interactions", () => {
753
+ test("confirmation_request events register pending interactions", async () => {
754
+ const confirmReceived: Array<{ requestId: string; decision: string }> =
755
+ [];
635
756
 
636
757
  const session = makeConfirmationEmittingSession({
637
- confirmRequestId: 'auto-req-1',
638
- toolName: 'shell_command',
758
+ confirmRequestId: "auto-req-1",
759
+ toolName: "shell_command",
639
760
  onConfirmation: (reqId, dec) => {
640
761
  confirmReceived.push({ requestId: reqId, decision: dec });
641
762
  },
@@ -644,10 +765,15 @@ describe('standalone approval endpoints — HTTP layer', () => {
644
765
  await startServer(() => session);
645
766
 
646
767
  // Send a message that triggers a confirmation_request
647
- const res = await fetch(url('messages'), {
648
- method: 'POST',
649
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
650
- body: JSON.stringify({ conversationKey: 'conv-auto', content: 'Run ls', sourceChannel: 'vellum', interface: 'macos' }),
768
+ const res = await fetch(url("messages"), {
769
+ method: "POST",
770
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
771
+ body: JSON.stringify({
772
+ conversationKey: "conv-auto",
773
+ content: "Run ls",
774
+ sourceChannel: "vellum",
775
+ interface: "macos",
776
+ }),
651
777
  });
652
778
  expect(res.status).toBe(202);
653
779
 
@@ -655,22 +781,22 @@ describe('standalone approval endpoints — HTTP layer', () => {
655
781
  await new Promise((r) => setTimeout(r, 100));
656
782
 
657
783
  // The pending interaction should have been auto-registered
658
- const interaction = pendingInteractions.get('auto-req-1');
784
+ const interaction = pendingInteractions.get("auto-req-1");
659
785
  expect(interaction).toBeDefined();
660
- expect(interaction!.kind).toBe('confirmation');
661
- expect(interaction!.confirmationDetails?.toolName).toBe('shell_command');
786
+ expect(interaction!.kind).toBe("confirmation");
787
+ expect(interaction!.confirmationDetails?.toolName).toBe("shell_command");
662
788
 
663
789
  // Now resolve it via the confirm endpoint
664
- const confirmRes = await fetch(url('confirm'), {
665
- method: 'POST',
666
- headers: { 'Content-Type': 'application/json', ...AUTH_HEADERS },
667
- body: JSON.stringify({ requestId: 'auto-req-1', decision: 'allow' }),
790
+ const confirmRes = await fetch(url("confirm"), {
791
+ method: "POST",
792
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
793
+ body: JSON.stringify({ requestId: "auto-req-1", decision: "allow" }),
668
794
  });
669
795
  expect(confirmRes.status).toBe(200);
670
796
 
671
797
  expect(confirmReceived).toHaveLength(1);
672
- expect(confirmReceived[0].requestId).toBe('auto-req-1');
673
- expect(confirmReceived[0].decision).toBe('allow');
798
+ expect(confirmReceived[0].requestId).toBe("auto-req-1");
799
+ expect(confirmReceived[0].decision).toBe("allow");
674
800
 
675
801
  await stopServer();
676
802
  });
@@ -678,36 +804,39 @@ describe('standalone approval endpoints — HTTP layer', () => {
678
804
 
679
805
  // ── getByConversation ────────────────────────────────────────────────
680
806
 
681
- describe('getByConversation', () => {
682
- test('returns all pending interactions for a conversation', async () => {
807
+ describe("getByConversation", () => {
808
+ test("returns all pending interactions for a conversation", async () => {
683
809
  const session = makeIdleSession();
684
810
  await startServer(() => session);
685
811
 
686
- pendingInteractions.register('req-a', {
812
+ pendingInteractions.register("req-a", {
687
813
  session,
688
- conversationId: 'conv-x',
689
- kind: 'confirmation',
814
+ conversationId: "conv-x",
815
+ kind: "confirmation",
690
816
  });
691
- pendingInteractions.register('req-b', {
817
+ pendingInteractions.register("req-b", {
692
818
  session,
693
- conversationId: 'conv-x',
694
- kind: 'secret',
819
+ conversationId: "conv-x",
820
+ kind: "secret",
695
821
  });
696
- pendingInteractions.register('req-c', {
822
+ pendingInteractions.register("req-c", {
697
823
  session,
698
- conversationId: 'conv-y',
699
- kind: 'confirmation',
824
+ conversationId: "conv-y",
825
+ kind: "confirmation",
700
826
  });
701
827
 
702
- const results = pendingInteractions.getByConversation('conv-x');
828
+ const results = pendingInteractions.getByConversation("conv-x");
703
829
  expect(results).toHaveLength(2);
704
- expect(results.map((r) => r.requestId).sort()).toEqual(['req-a', 'req-b']);
830
+ expect(results.map((r) => r.requestId).sort()).toEqual([
831
+ "req-a",
832
+ "req-b",
833
+ ]);
705
834
 
706
- const resultsY = pendingInteractions.getByConversation('conv-y');
835
+ const resultsY = pendingInteractions.getByConversation("conv-y");
707
836
  expect(resultsY).toHaveLength(1);
708
- expect(resultsY[0].requestId).toBe('req-c');
837
+ expect(resultsY[0].requestId).toBe("req-c");
709
838
 
710
- const resultsZ = pendingInteractions.getByConversation('conv-z');
839
+ const resultsZ = pendingInteractions.getByConversation("conv-z");
711
840
  expect(resultsZ).toHaveLength(0);
712
841
 
713
842
  await stopServer();