@vellumai/assistant 0.4.13 → 0.4.15
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.
- package/ARCHITECTURE.md +77 -38
- package/README.md +10 -12
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +108 -522
- package/src/__tests__/channel-approval-routes.test.ts +92 -239
- package/src/__tests__/channel-approval.test.ts +100 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -6
- package/src/__tests__/conversation-routes.test.ts +11 -4
- package/src/__tests__/guardian-actions-endpoint.test.ts +26 -19
- package/src/__tests__/mcp-health-check.test.ts +65 -0
- package/src/__tests__/permission-types.test.ts +33 -0
- package/src/__tests__/scan-result-store.test.ts +121 -0
- package/src/__tests__/session-agent-loop.test.ts +120 -0
- package/src/__tests__/session-approval-overrides.test.ts +205 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +38 -0
- package/src/amazon/client.ts +8 -5
- package/src/approvals/guardian-decision-primitive.ts +14 -9
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/call-controller.ts +2 -2
- package/src/calls/twilio-routes.ts +2 -2
- package/src/cli/mcp.ts +3 -3
- package/src/cli.ts +24 -0
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +19 -130
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +8 -6
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +49 -14
- package/src/config/bundled-skills/messaging/TOOLS.json +52 -9
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +35 -11
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +3 -1
- package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +5 -6
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +10 -2
- package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +20 -0
- package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +3 -4
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +76 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +10 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +11 -3
- package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +86 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/skills-catalog/SKILL.md +31 -8
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +79 -24
- package/src/config/bundled-skills/sms-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/telegram-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/approval-generators.ts +6 -3
- package/src/daemon/handlers/config-ingress.ts +2 -6
- package/src/daemon/handlers/guardian-actions.ts +1 -1
- package/src/daemon/handlers/sessions.ts +4 -1
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/skills.ts +32 -0
- package/src/daemon/ipc-contract/messages.ts +3 -1
- package/src/daemon/ipc-handler.ts +24 -0
- package/src/daemon/ipc-validate.ts +1 -1
- package/src/daemon/lifecycle.ts +6 -8
- package/src/daemon/server.ts +8 -3
- package/src/daemon/session-agent-loop.ts +19 -1
- package/src/daemon/session-attachments.ts +2 -1
- package/src/daemon/session-history.ts +2 -2
- package/src/daemon/session-process.ts +5 -9
- package/src/daemon/session-surfaces.ts +17 -1
- package/src/daemon/session-tool-setup.ts +216 -69
- package/src/daemon/session.ts +24 -1
- package/src/events/domain-events.ts +1 -1
- package/src/events/tool-domain-event-publisher.ts +5 -10
- package/src/influencer/client.ts +8 -7
- package/src/messaging/providers/gmail/client.ts +33 -1
- package/src/messaging/providers/gmail/mime-builder.ts +5 -1
- package/src/messaging/providers/sms/adapter.ts +3 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +3 -7
- package/src/messaging/providers/whatsapp/adapter.ts +3 -7
- package/src/notifications/adapters/sms.ts +2 -2
- package/src/notifications/adapters/telegram.ts +2 -2
- package/src/permissions/prompter.ts +2 -0
- package/src/permissions/types.ts +11 -1
- package/src/runtime/approval-conversation-turn.ts +4 -0
- package/src/runtime/auth/__tests__/context.test.ts +130 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +277 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +289 -0
- package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +71 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +239 -0
- package/src/runtime/auth/__tests__/policy.test.ts +29 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +166 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +109 -0
- package/src/runtime/auth/__tests__/subject.test.ts +149 -0
- package/src/runtime/auth/__tests__/token-service.test.ts +263 -0
- package/src/runtime/auth/context.ts +62 -0
- package/src/runtime/{actor-refresh-token-service.ts → auth/credential-service.ts} +112 -79
- package/src/runtime/auth/external-assistant-id.ts +69 -0
- package/src/runtime/auth/index.ts +37 -0
- package/src/runtime/auth/middleware.ts +127 -0
- package/src/runtime/auth/policy.ts +17 -0
- package/src/runtime/auth/route-policy.ts +261 -0
- package/src/runtime/auth/scopes.ts +64 -0
- package/src/runtime/auth/subject.ts +68 -0
- package/src/runtime/auth/token-service.ts +275 -0
- package/src/runtime/auth/types.ts +79 -0
- package/src/runtime/channel-approval-parser.ts +11 -5
- package/src/runtime/channel-approval-types.ts +1 -1
- package/src/runtime/channel-approvals.ts +22 -1
- package/src/runtime/guardian-action-followup-executor.ts +2 -2
- package/src/runtime/guardian-context-resolver.ts +15 -0
- package/src/runtime/guardian-decision-types.ts +23 -6
- package/src/runtime/guardian-outbound-actions.ts +4 -22
- package/src/runtime/guardian-reply-router.ts +5 -3
- package/src/runtime/http-server.ts +210 -182
- package/src/runtime/http-types.ts +11 -1
- package/src/runtime/local-actor-identity.ts +25 -0
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/approval-routes.ts +42 -59
- package/src/runtime/routes/channel-route-shared.ts +9 -41
- package/src/runtime/routes/channel-routes.ts +0 -2
- package/src/runtime/routes/conversation-routes.ts +39 -49
- package/src/runtime/routes/events-routes.ts +15 -22
- package/src/runtime/routes/guardian-action-routes.ts +46 -51
- package/src/runtime/routes/guardian-approval-interception.ts +6 -5
- package/src/runtime/routes/guardian-bootstrap-routes.ts +12 -8
- package/src/runtime/routes/guardian-refresh-routes.ts +2 -2
- package/src/runtime/routes/inbound-message-handler.ts +39 -45
- package/src/runtime/routes/pairing-routes.ts +9 -9
- package/src/runtime/routes/secret-routes.ts +90 -45
- package/src/runtime/routes/surface-action-routes.ts +12 -2
- package/src/runtime/routes/trust-rules-routes.ts +13 -0
- package/src/runtime/routes/twilio-routes.ts +3 -3
- package/src/runtime/session-approval-overrides.ts +86 -0
- package/src/security/keychain-to-encrypted-migration.ts +8 -1
- package/src/skills/frontmatter.ts +44 -1
- package/src/tools/permission-checker.ts +226 -74
- package/src/runtime/actor-token-service.ts +0 -234
- package/src/runtime/middleware/actor-token.ts +0 -265
|
@@ -80,16 +80,17 @@ import {
|
|
|
80
80
|
} from '../memory/channel-guardian-store.js';
|
|
81
81
|
import { getDb, initializeDb, resetDb } from '../memory/db.js';
|
|
82
82
|
import { conversations, externalConversationBindings } from '../memory/schema.js';
|
|
83
|
+
import { initAuthSigningKey } from '../runtime/auth/token-service.js';
|
|
83
84
|
import * as gatewayClient from '../runtime/gateway-client.js';
|
|
84
85
|
import * as pendingInteractions from '../runtime/pending-interactions.js';
|
|
85
86
|
import {
|
|
86
87
|
_setTestPollMaxWait,
|
|
87
88
|
handleChannelInbound,
|
|
88
89
|
sweepExpiredGuardianApprovals,
|
|
89
|
-
verifyGatewayOrigin,
|
|
90
90
|
} from '../runtime/routes/channel-routes.js';
|
|
91
91
|
|
|
92
92
|
initializeDb();
|
|
93
|
+
initAuthSigningKey(Buffer.from('test-signing-key-at-least-32-bytes-long'));
|
|
93
94
|
|
|
94
95
|
afterAll(() => {
|
|
95
96
|
resetDb();
|
|
@@ -174,8 +175,9 @@ function registerPendingInteraction(
|
|
|
174
175
|
return handleConfirmationResponse;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
/**
|
|
178
|
-
*
|
|
178
|
+
/** Legacy bearer token constant — retained for use in X-Gateway-Origin
|
|
179
|
+
* headers in test request construction. Gateway-origin proof is now
|
|
180
|
+
* handled by JWT auth; these headers are ignored by the handler. */
|
|
179
181
|
const TEST_BEARER_TOKEN = 'token';
|
|
180
182
|
|
|
181
183
|
function makeInboundRequest(overrides: Record<string, unknown> = {}): Request {
|
|
@@ -255,7 +257,7 @@ describe('inbound callback metadata triggers decision handling', () => {
|
|
|
255
257
|
|
|
256
258
|
// Establish the conversation to get a conversationId mapping
|
|
257
259
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
258
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
260
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
259
261
|
|
|
260
262
|
const db = getDb();
|
|
261
263
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -272,7 +274,7 @@ describe('inbound callback metadata triggers decision handling', () => {
|
|
|
272
274
|
callbackData: 'apr:req-cb-1:approve_once',
|
|
273
275
|
});
|
|
274
276
|
|
|
275
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
277
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
276
278
|
const body = await res.json() as Record<string, unknown>;
|
|
277
279
|
|
|
278
280
|
expect(body.accepted).toBe(true);
|
|
@@ -286,7 +288,7 @@ describe('inbound callback metadata triggers decision handling', () => {
|
|
|
286
288
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
287
289
|
|
|
288
290
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
289
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
291
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
290
292
|
|
|
291
293
|
const db = getDb();
|
|
292
294
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -300,7 +302,7 @@ describe('inbound callback metadata triggers decision handling', () => {
|
|
|
300
302
|
callbackData: 'apr:req-cb-2:reject',
|
|
301
303
|
});
|
|
302
304
|
|
|
303
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
305
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
304
306
|
const body = await res.json() as Record<string, unknown>;
|
|
305
307
|
|
|
306
308
|
expect(body.accepted).toBe(true);
|
|
@@ -330,7 +332,7 @@ describe('inbound text matching approval phrases triggers decision handling', ()
|
|
|
330
332
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
331
333
|
|
|
332
334
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
333
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
335
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
334
336
|
|
|
335
337
|
const db = getDb();
|
|
336
338
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -340,7 +342,7 @@ describe('inbound text matching approval phrases triggers decision handling', ()
|
|
|
340
342
|
const sessionMock = registerPendingInteraction('req-txt-1', conversationId!, 'shell');
|
|
341
343
|
|
|
342
344
|
const req = makeInboundRequest({ content: 'approve' });
|
|
343
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
345
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
344
346
|
const body = await res.json() as Record<string, unknown>;
|
|
345
347
|
|
|
346
348
|
expect(body.accepted).toBe(true);
|
|
@@ -354,7 +356,7 @@ describe('inbound text matching approval phrases triggers decision handling', ()
|
|
|
354
356
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
355
357
|
|
|
356
358
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
357
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
359
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
358
360
|
|
|
359
361
|
const db = getDb();
|
|
360
362
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -364,7 +366,7 @@ describe('inbound text matching approval phrases triggers decision handling', ()
|
|
|
364
366
|
const sessionMock = registerPendingInteraction('req-txt-2', conversationId!, 'shell');
|
|
365
367
|
|
|
366
368
|
const req = makeInboundRequest({ content: 'always' });
|
|
367
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
369
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
368
370
|
const body = await res.json() as Record<string, unknown>;
|
|
369
371
|
|
|
370
372
|
expect(body.accepted).toBe(true);
|
|
@@ -394,7 +396,7 @@ describe('non-decision messages during pending approval (legacy fallback)', () =
|
|
|
394
396
|
const replySpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
395
397
|
|
|
396
398
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
397
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
399
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
398
400
|
|
|
399
401
|
const db = getDb();
|
|
400
402
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -405,7 +407,7 @@ describe('non-decision messages during pending approval (legacy fallback)', () =
|
|
|
405
407
|
|
|
406
408
|
// Send a message that is NOT a decision
|
|
407
409
|
const req = makeInboundRequest({ content: 'what is the weather?' });
|
|
408
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
410
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
409
411
|
const body = await res.json() as Record<string, unknown>;
|
|
410
412
|
|
|
411
413
|
expect(body.accepted).toBe(true);
|
|
@@ -432,7 +434,7 @@ describe('non-decision messages during pending approval (legacy fallback)', () =
|
|
|
432
434
|
describe('messages without pending approval proceed normally', () => {
|
|
433
435
|
test('proceeds to normal processing when no pending approval exists', async () => {
|
|
434
436
|
const req = makeInboundRequest({ content: 'hello world' });
|
|
435
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
437
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
436
438
|
const body = await res.json() as Record<string, unknown>;
|
|
437
439
|
|
|
438
440
|
expect(body.accepted).toBe(true);
|
|
@@ -441,7 +443,7 @@ describe('messages without pending approval proceed normally', () => {
|
|
|
441
443
|
|
|
442
444
|
test('text "approve" is processed normally when no pending approval exists', async () => {
|
|
443
445
|
const req = makeInboundRequest({ content: 'approve' });
|
|
444
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
446
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
445
447
|
const body = await res.json() as Record<string, unknown>;
|
|
446
448
|
|
|
447
449
|
expect(body.accepted).toBe(true);
|
|
@@ -476,7 +478,7 @@ describe('empty content with callbackData bypasses validation', () => {
|
|
|
476
478
|
test('allows empty content when callbackData is present', async () => {
|
|
477
479
|
// Establish the conversation first
|
|
478
480
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
479
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
481
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
480
482
|
|
|
481
483
|
const db = getDb();
|
|
482
484
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -492,7 +494,7 @@ describe('empty content with callbackData bypasses validation', () => {
|
|
|
492
494
|
callbackData: 'apr:req-empty-1:approve_once',
|
|
493
495
|
});
|
|
494
496
|
|
|
495
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
497
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
496
498
|
expect(res.status).toBe(200);
|
|
497
499
|
const body = await res.json() as Record<string, unknown>;
|
|
498
500
|
expect(body.accepted).toBe(true);
|
|
@@ -505,7 +507,7 @@ describe('empty content with callbackData bypasses validation', () => {
|
|
|
505
507
|
test('allows undefined content when callbackData is present', async () => {
|
|
506
508
|
// Establish the conversation first
|
|
507
509
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
508
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
510
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
509
511
|
|
|
510
512
|
const db = getDb();
|
|
511
513
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -535,7 +537,7 @@ describe('empty content with callbackData bypasses validation', () => {
|
|
|
535
537
|
body: JSON.stringify(reqBody),
|
|
536
538
|
});
|
|
537
539
|
|
|
538
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
540
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
539
541
|
expect(res.status).toBe(200);
|
|
540
542
|
const resBody = await res.json() as Record<string, unknown>;
|
|
541
543
|
expect(resBody.accepted).toBe(true);
|
|
@@ -563,7 +565,7 @@ describe('callback requestId validation', () => {
|
|
|
563
565
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
564
566
|
|
|
565
567
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
566
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
568
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
567
569
|
|
|
568
570
|
const db = getDb();
|
|
569
571
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -579,7 +581,7 @@ describe('callback requestId validation', () => {
|
|
|
579
581
|
callbackData: 'apr:stale-request-id:approve_once',
|
|
580
582
|
});
|
|
581
583
|
|
|
582
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
584
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
583
585
|
const body = await res.json() as Record<string, unknown>;
|
|
584
586
|
|
|
585
587
|
expect(body.accepted).toBe(true);
|
|
@@ -594,7 +596,7 @@ describe('callback requestId validation', () => {
|
|
|
594
596
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
595
597
|
|
|
596
598
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
597
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
599
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
598
600
|
|
|
599
601
|
const db = getDb();
|
|
600
602
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -609,7 +611,7 @@ describe('callback requestId validation', () => {
|
|
|
609
611
|
callbackData: 'apr:req-match:approve_once',
|
|
610
612
|
});
|
|
611
613
|
|
|
612
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
614
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
613
615
|
const body = await res.json() as Record<string, unknown>;
|
|
614
616
|
|
|
615
617
|
expect(body.accepted).toBe(true);
|
|
@@ -623,7 +625,7 @@ describe('callback requestId validation', () => {
|
|
|
623
625
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
624
626
|
|
|
625
627
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
626
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
628
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
627
629
|
|
|
628
630
|
const db = getDb();
|
|
629
631
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -634,7 +636,7 @@ describe('callback requestId validation', () => {
|
|
|
634
636
|
|
|
635
637
|
// Send plain text "yes" — no requestId in the parsed result
|
|
636
638
|
const req = makeInboundRequest({ content: 'yes' });
|
|
637
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
639
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
638
640
|
const body = await res.json() as Record<string, unknown>;
|
|
639
641
|
|
|
640
642
|
expect(body.accepted).toBe(true);
|
|
@@ -664,7 +666,7 @@ describe('no immediate reply after approval decision', () => {
|
|
|
664
666
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
665
667
|
|
|
666
668
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
667
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
669
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
668
670
|
|
|
669
671
|
const db = getDb();
|
|
670
672
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -682,7 +684,7 @@ describe('no immediate reply after approval decision', () => {
|
|
|
682
684
|
callbackData: 'apr:req-noreply-1:approve_once',
|
|
683
685
|
});
|
|
684
686
|
|
|
685
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
687
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
686
688
|
const body = await res.json() as Record<string, unknown>;
|
|
687
689
|
|
|
688
690
|
expect(body.approval).toBe('decision_applied');
|
|
@@ -698,7 +700,7 @@ describe('no immediate reply after approval decision', () => {
|
|
|
698
700
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
699
701
|
|
|
700
702
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
701
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
703
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
702
704
|
|
|
703
705
|
const db = getDb();
|
|
704
706
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -711,7 +713,7 @@ describe('no immediate reply after approval decision', () => {
|
|
|
711
713
|
|
|
712
714
|
// Send a plain-text approval
|
|
713
715
|
const req = makeInboundRequest({ content: 'approve' });
|
|
714
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
716
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
715
717
|
const body = await res.json() as Record<string, unknown>;
|
|
716
718
|
|
|
717
719
|
expect(body.approval).toBe('decision_applied');
|
|
@@ -733,7 +735,7 @@ describe('stale callback handling', () => {
|
|
|
733
735
|
callbackData: 'apr:stale-req:approve_once',
|
|
734
736
|
});
|
|
735
737
|
|
|
736
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
738
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
737
739
|
const body = await res.json() as Record<string, unknown>;
|
|
738
740
|
|
|
739
741
|
expect(body.accepted).toBe(true);
|
|
@@ -748,7 +750,7 @@ describe('stale callback handling', () => {
|
|
|
748
750
|
callbackData: 'apr:stale-req:approve_once',
|
|
749
751
|
});
|
|
750
752
|
|
|
751
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
753
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
752
754
|
const body = await res.json() as Record<string, unknown>;
|
|
753
755
|
|
|
754
756
|
expect(body.accepted).toBe(true);
|
|
@@ -759,7 +761,7 @@ describe('stale callback handling', () => {
|
|
|
759
761
|
// Regular text message (no callbackData) should proceed normally
|
|
760
762
|
const req = makeInboundRequest({ content: 'hello world' });
|
|
761
763
|
|
|
762
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
764
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
763
765
|
const body = await res.json() as Record<string, unknown>;
|
|
764
766
|
|
|
765
767
|
expect(body.accepted).toBe(true);
|
|
@@ -809,7 +811,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
809
811
|
|
|
810
812
|
// Establish the conversation via SMS
|
|
811
813
|
const initReq = makeSmsInboundRequest({ content: 'init' });
|
|
812
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
814
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
813
815
|
|
|
814
816
|
const db = getDb();
|
|
815
817
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -819,7 +821,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
819
821
|
const sessionMock = registerPendingInteraction('req-sms-1', conversationId!, 'shell');
|
|
820
822
|
|
|
821
823
|
const req = makeSmsInboundRequest({ content: 'yes' });
|
|
822
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
824
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
823
825
|
const body = await res.json() as Record<string, unknown>;
|
|
824
826
|
|
|
825
827
|
expect(body.accepted).toBe(true);
|
|
@@ -833,7 +835,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
833
835
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
834
836
|
|
|
835
837
|
const initReq = makeSmsInboundRequest({ content: 'init' });
|
|
836
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
838
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
837
839
|
|
|
838
840
|
const db = getDb();
|
|
839
841
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -843,7 +845,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
843
845
|
const sessionMock = registerPendingInteraction('req-sms-2', conversationId!, 'shell');
|
|
844
846
|
|
|
845
847
|
const req = makeSmsInboundRequest({ content: 'no' });
|
|
846
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
848
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
847
849
|
const body = await res.json() as Record<string, unknown>;
|
|
848
850
|
|
|
849
851
|
expect(body.accepted).toBe(true);
|
|
@@ -858,7 +860,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
858
860
|
const approvalSpy = spyOn(gatewayClient, 'deliverApprovalPrompt').mockResolvedValue(undefined);
|
|
859
861
|
|
|
860
862
|
const initReq = makeSmsInboundRequest({ content: 'init' });
|
|
861
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
863
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
862
864
|
|
|
863
865
|
const db = getDb();
|
|
864
866
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -868,7 +870,7 @@ describe('SMS channel approval decisions', () => {
|
|
|
868
870
|
registerPendingInteraction('req-sms-3', conversationId!, 'shell');
|
|
869
871
|
|
|
870
872
|
const req = makeSmsInboundRequest({ content: 'what is happening?' });
|
|
871
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
873
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
872
874
|
const body = await res.json() as Record<string, unknown>;
|
|
873
875
|
|
|
874
876
|
expect(body.accepted).toBe(true);
|
|
@@ -919,7 +921,7 @@ describe('SMS guardian verify intercept', () => {
|
|
|
919
921
|
}),
|
|
920
922
|
});
|
|
921
923
|
|
|
922
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
924
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
923
925
|
const body = await res.json() as Record<string, unknown>;
|
|
924
926
|
|
|
925
927
|
expect(body.accepted).toBe(true);
|
|
@@ -960,7 +962,7 @@ describe('SMS guardian verify intercept', () => {
|
|
|
960
962
|
}),
|
|
961
963
|
});
|
|
962
964
|
|
|
963
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
965
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
964
966
|
const body = await res.json() as Record<string, unknown>;
|
|
965
967
|
|
|
966
968
|
expect(body.accepted).toBe(true);
|
|
@@ -1013,7 +1015,7 @@ describe('SMS guardian verify intercept', () => {
|
|
|
1013
1015
|
}),
|
|
1014
1016
|
});
|
|
1015
1017
|
|
|
1016
|
-
const res = await handleChannelInbound(req, processMessage
|
|
1018
|
+
const res = await handleChannelInbound(req, processMessage);
|
|
1017
1019
|
const body = await res.json() as Record<string, unknown>;
|
|
1018
1020
|
|
|
1019
1021
|
expect(body.accepted).toBe(true);
|
|
@@ -1080,7 +1082,7 @@ describe('guardian decision scoping — multiple pending approvals', () => {
|
|
|
1080
1082
|
actorExternalId: 'guardian-scope-user',
|
|
1081
1083
|
});
|
|
1082
1084
|
|
|
1083
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
1085
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
1084
1086
|
const body = await res.json() as Record<string, unknown>;
|
|
1085
1087
|
|
|
1086
1088
|
expect(body.accepted).toBe(true);
|
|
@@ -1159,8 +1161,7 @@ describe('ambiguous plain-text decision with multiple pending requests', () => {
|
|
|
1159
1161
|
});
|
|
1160
1162
|
|
|
1161
1163
|
const res = await handleChannelInbound(
|
|
1162
|
-
req, noopProcessMessage, '
|
|
1163
|
-
undefined, mockConversationGenerator,
|
|
1164
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1164
1165
|
);
|
|
1165
1166
|
const body = await res.json() as Record<string, unknown>;
|
|
1166
1167
|
|
|
@@ -1319,7 +1320,7 @@ describe('assistant-scoped guardian verification via handleChannelInbound', () =
|
|
|
1319
1320
|
actorExternalId: 'user-default-asst',
|
|
1320
1321
|
});
|
|
1321
1322
|
|
|
1322
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
1323
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
1323
1324
|
const body = await res.json() as Record<string, unknown>;
|
|
1324
1325
|
|
|
1325
1326
|
expect(body.accepted).toBe(true);
|
|
@@ -1342,7 +1343,7 @@ describe('assistant-scoped guardian verification via handleChannelInbound', () =
|
|
|
1342
1343
|
actorExternalId: 'user-for-asst-x',
|
|
1343
1344
|
});
|
|
1344
1345
|
|
|
1345
|
-
const res = await handleChannelInbound(req, noopProcessMessage, '
|
|
1346
|
+
const res = await handleChannelInbound(req, noopProcessMessage, 'asst-route-X');
|
|
1346
1347
|
const body = await res.json() as Record<string, unknown>;
|
|
1347
1348
|
|
|
1348
1349
|
expect(body.accepted).toBe(true);
|
|
@@ -1368,7 +1369,7 @@ describe('assistant-scoped guardian verification via handleChannelInbound', () =
|
|
|
1368
1369
|
actorExternalId: 'user-cross-test',
|
|
1369
1370
|
});
|
|
1370
1371
|
|
|
1371
|
-
const res = await handleChannelInbound(req, noopProcessMessage, '
|
|
1372
|
+
const res = await handleChannelInbound(req, noopProcessMessage, 'asst-B-cross');
|
|
1372
1373
|
const body = await res.json() as Record<string, unknown>;
|
|
1373
1374
|
|
|
1374
1375
|
expect(body.accepted).toBe(true);
|
|
@@ -1396,7 +1397,7 @@ describe('assistant-scoped guardian verification via handleChannelInbound', () =
|
|
|
1396
1397
|
actorExternalId: 'incoming-user',
|
|
1397
1398
|
});
|
|
1398
1399
|
|
|
1399
|
-
const res = await handleChannelInbound(req, noopProcessMessage, '
|
|
1400
|
+
const res = await handleChannelInbound(req, noopProcessMessage, 'asst-non-self');
|
|
1400
1401
|
expect(res.status).toBe(200);
|
|
1401
1402
|
|
|
1402
1403
|
const binding = db
|
|
@@ -1409,142 +1410,9 @@ describe('assistant-scoped guardian verification via handleChannelInbound', () =
|
|
|
1409
1410
|
});
|
|
1410
1411
|
});
|
|
1411
1412
|
|
|
1412
|
-
//
|
|
1413
|
-
//
|
|
1414
|
-
//
|
|
1415
|
-
|
|
1416
|
-
describe('verifyGatewayOrigin with dedicated gateway-origin secret', () => {
|
|
1417
|
-
function makeReqWithHeader(value?: string): Request {
|
|
1418
|
-
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
1419
|
-
if (value !== undefined) {
|
|
1420
|
-
headers['X-Gateway-Origin'] = value;
|
|
1421
|
-
}
|
|
1422
|
-
return new Request('http://localhost/channels/inbound', {
|
|
1423
|
-
method: 'POST',
|
|
1424
|
-
headers,
|
|
1425
|
-
body: '{}',
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
test('returns true when no secrets configured (local dev)', () => {
|
|
1430
|
-
expect(verifyGatewayOrigin(makeReqWithHeader(), undefined, undefined)).toBe(true);
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
test('falls back to bearerToken when no dedicated secret is set', () => {
|
|
1434
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('my-bearer'), 'my-bearer', undefined)).toBe(true);
|
|
1435
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('wrong'), 'my-bearer', undefined)).toBe(false);
|
|
1436
|
-
expect(verifyGatewayOrigin(makeReqWithHeader(), 'my-bearer', undefined)).toBe(false);
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
|
-
test('uses dedicated secret when set, ignoring bearer token', () => {
|
|
1440
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('dedicated-secret'), 'bearer-token', 'dedicated-secret')).toBe(true);
|
|
1441
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('bearer-token'), 'bearer-token', 'dedicated-secret')).toBe(false);
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
test('validates dedicated secret even when bearer token is not configured', () => {
|
|
1445
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('my-secret'), undefined, 'my-secret')).toBe(true);
|
|
1446
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('wrong'), undefined, 'my-secret')).toBe(false);
|
|
1447
|
-
});
|
|
1448
|
-
|
|
1449
|
-
test('rejects missing header when any secret is configured', () => {
|
|
1450
|
-
expect(verifyGatewayOrigin(makeReqWithHeader(), 'bearer', undefined)).toBe(false);
|
|
1451
|
-
expect(verifyGatewayOrigin(makeReqWithHeader(), undefined, 'secret')).toBe(false);
|
|
1452
|
-
expect(verifyGatewayOrigin(makeReqWithHeader(), 'bearer', 'secret')).toBe(false);
|
|
1453
|
-
});
|
|
1454
|
-
|
|
1455
|
-
test('rejects mismatched length headers (constant-time comparison guard)', () => {
|
|
1456
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('short'), 'a-much-longer-secret', undefined)).toBe(false);
|
|
1457
|
-
expect(verifyGatewayOrigin(makeReqWithHeader('a-much-longer-secret'), 'short', undefined)).toBe(false);
|
|
1458
|
-
});
|
|
1459
|
-
});
|
|
1460
|
-
|
|
1461
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1462
|
-
// 29. handleChannelInbound passes gatewayOriginSecret to verifyGatewayOrigin
|
|
1463
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
1464
|
-
|
|
1465
|
-
describe('handleChannelInbound gatewayOriginSecret integration', () => {
|
|
1466
|
-
test('rejects request when bearer token matches but dedicated secret does not', async () => {
|
|
1467
|
-
const bearerToken = 'my-bearer';
|
|
1468
|
-
const gwOriginToken = 'dedicated-gw-test-value';
|
|
1469
|
-
|
|
1470
|
-
const req = new Request('http://localhost/channels/inbound', {
|
|
1471
|
-
method: 'POST',
|
|
1472
|
-
headers: {
|
|
1473
|
-
'Content-Type': 'application/json',
|
|
1474
|
-
'X-Gateway-Origin': bearerToken,
|
|
1475
|
-
},
|
|
1476
|
-
body: JSON.stringify({
|
|
1477
|
-
sourceChannel: 'telegram',
|
|
1478
|
-
interface: 'telegram',
|
|
1479
|
-
conversationExternalId: 'chat-gw-secret-test',
|
|
1480
|
-
externalMessageId: `msg-${Date.now()}-${Math.random()}`,
|
|
1481
|
-
content: 'hello',
|
|
1482
|
-
}),
|
|
1483
|
-
});
|
|
1484
|
-
|
|
1485
|
-
const res = await handleChannelInbound(
|
|
1486
|
-
req, noopProcessMessage, bearerToken, 'self', gwOriginToken,
|
|
1487
|
-
);
|
|
1488
|
-
expect(res.status).toBe(403);
|
|
1489
|
-
const body = await res.json() as { code: string };
|
|
1490
|
-
expect(body.code).toBe('GATEWAY_ORIGIN_REQUIRED');
|
|
1491
|
-
});
|
|
1492
|
-
|
|
1493
|
-
test('accepts request when dedicated secret matches', async () => {
|
|
1494
|
-
const bearerToken = 'my-bearer';
|
|
1495
|
-
const gwOriginToken = 'dedicated-gw-test-value';
|
|
1496
|
-
|
|
1497
|
-
const req = new Request('http://localhost/channels/inbound', {
|
|
1498
|
-
method: 'POST',
|
|
1499
|
-
headers: {
|
|
1500
|
-
'Content-Type': 'application/json',
|
|
1501
|
-
'X-Gateway-Origin': gwOriginToken,
|
|
1502
|
-
},
|
|
1503
|
-
body: JSON.stringify({
|
|
1504
|
-
sourceChannel: 'telegram',
|
|
1505
|
-
interface: 'telegram',
|
|
1506
|
-
conversationExternalId: 'chat-gw-secret-pass',
|
|
1507
|
-
externalMessageId: `msg-${Date.now()}-${Math.random()}`,
|
|
1508
|
-
content: 'hello',
|
|
1509
|
-
actorExternalId: 'telegram-user-default',
|
|
1510
|
-
}),
|
|
1511
|
-
});
|
|
1512
|
-
|
|
1513
|
-
const res = await handleChannelInbound(
|
|
1514
|
-
req, noopProcessMessage, bearerToken, 'self', gwOriginToken,
|
|
1515
|
-
);
|
|
1516
|
-
expect(res.status).toBe(200);
|
|
1517
|
-
const body = await res.json() as Record<string, unknown>;
|
|
1518
|
-
expect(body.accepted).toBe(true);
|
|
1519
|
-
});
|
|
1520
|
-
|
|
1521
|
-
test('falls back to bearer token when no dedicated secret is set', async () => {
|
|
1522
|
-
const bearerToken = 'my-bearer';
|
|
1523
|
-
|
|
1524
|
-
const req = new Request('http://localhost/channels/inbound', {
|
|
1525
|
-
method: 'POST',
|
|
1526
|
-
headers: {
|
|
1527
|
-
'Content-Type': 'application/json',
|
|
1528
|
-
'X-Gateway-Origin': bearerToken,
|
|
1529
|
-
},
|
|
1530
|
-
body: JSON.stringify({
|
|
1531
|
-
sourceChannel: 'telegram',
|
|
1532
|
-
interface: 'telegram',
|
|
1533
|
-
conversationExternalId: 'chat-gw-fallback',
|
|
1534
|
-
externalMessageId: `msg-${Date.now()}-${Math.random()}`,
|
|
1535
|
-
content: 'hello',
|
|
1536
|
-
actorExternalId: 'telegram-user-default',
|
|
1537
|
-
}),
|
|
1538
|
-
});
|
|
1539
|
-
|
|
1540
|
-
const res = await handleChannelInbound(
|
|
1541
|
-
req, noopProcessMessage, bearerToken, 'self',
|
|
1542
|
-
);
|
|
1543
|
-
expect(res.status).toBe(200);
|
|
1544
|
-
const body = await res.json() as Record<string, unknown>;
|
|
1545
|
-
expect(body.accepted).toBe(true);
|
|
1546
|
-
});
|
|
1547
|
-
});
|
|
1413
|
+
// Sections 28-29 (verifyGatewayOrigin / gatewayOriginSecret integration) removed:
|
|
1414
|
+
// gateway-origin proof is now handled by JWT auth — the gateway proves its
|
|
1415
|
+
// identity by minting a daemon-audience token with the shared signing key.
|
|
1548
1416
|
|
|
1549
1417
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1550
1418
|
// Conversational approval engine — standard path
|
|
@@ -1565,7 +1433,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1565
1433
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
1566
1434
|
|
|
1567
1435
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
1568
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1436
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
1569
1437
|
|
|
1570
1438
|
const db = getDb();
|
|
1571
1439
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -1583,8 +1451,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1583
1451
|
|
|
1584
1452
|
const req = makeInboundRequest({ content: 'what does this command do?' });
|
|
1585
1453
|
const res = await handleChannelInbound(
|
|
1586
|
-
req, noopProcessMessage, '
|
|
1587
|
-
undefined, mockConversationGenerator,
|
|
1454
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1588
1455
|
);
|
|
1589
1456
|
const body = await res.json() as Record<string, unknown>;
|
|
1590
1457
|
|
|
@@ -1608,7 +1475,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1608
1475
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
1609
1476
|
|
|
1610
1477
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
1611
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1478
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
1612
1479
|
|
|
1613
1480
|
const db = getDb();
|
|
1614
1481
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -1626,8 +1493,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1626
1493
|
|
|
1627
1494
|
const req = makeInboundRequest({ content: 'yeah go ahead and run it' });
|
|
1628
1495
|
const res = await handleChannelInbound(
|
|
1629
|
-
req, noopProcessMessage, '
|
|
1630
|
-
undefined, mockConversationGenerator,
|
|
1496
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1631
1497
|
);
|
|
1632
1498
|
const body = await res.json() as Record<string, unknown>;
|
|
1633
1499
|
|
|
@@ -1644,7 +1510,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1644
1510
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
1645
1511
|
|
|
1646
1512
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
1647
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1513
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
1648
1514
|
|
|
1649
1515
|
const db = getDb();
|
|
1650
1516
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -1662,8 +1528,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1662
1528
|
|
|
1663
1529
|
const req = makeInboundRequest({ content: 'nevermind, don\'t run that' });
|
|
1664
1530
|
const res = await handleChannelInbound(
|
|
1665
|
-
req, noopProcessMessage, '
|
|
1666
|
-
undefined, mockConversationGenerator,
|
|
1531
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1667
1532
|
);
|
|
1668
1533
|
const body = await res.json() as Record<string, unknown>;
|
|
1669
1534
|
|
|
@@ -1679,7 +1544,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1679
1544
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
1680
1545
|
|
|
1681
1546
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
1682
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1547
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
1683
1548
|
|
|
1684
1549
|
const db = getDb();
|
|
1685
1550
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -1700,8 +1565,7 @@ describe('conversational approval engine — standard path', () => {
|
|
|
1700
1565
|
});
|
|
1701
1566
|
|
|
1702
1567
|
const res = await handleChannelInbound(
|
|
1703
|
-
req, noopProcessMessage, '
|
|
1704
|
-
undefined, mockConversationGenerator,
|
|
1568
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1705
1569
|
);
|
|
1706
1570
|
const body = await res.json() as Record<string, unknown>;
|
|
1707
1571
|
|
|
@@ -1761,8 +1625,7 @@ describe('guardian conversational approval via conversation engine', () => {
|
|
|
1761
1625
|
});
|
|
1762
1626
|
|
|
1763
1627
|
const res = await handleChannelInbound(
|
|
1764
|
-
req, noopProcessMessage, '
|
|
1765
|
-
undefined, mockConversationGenerator,
|
|
1628
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1766
1629
|
);
|
|
1767
1630
|
const body = await res.json() as Record<string, unknown>;
|
|
1768
1631
|
|
|
@@ -1826,8 +1689,7 @@ describe('guardian conversational approval via conversation engine', () => {
|
|
|
1826
1689
|
});
|
|
1827
1690
|
|
|
1828
1691
|
const res = await handleChannelInbound(
|
|
1829
|
-
req, noopProcessMessage, '
|
|
1830
|
-
undefined, mockConversationGenerator,
|
|
1692
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1831
1693
|
);
|
|
1832
1694
|
const body = await res.json() as Record<string, unknown>;
|
|
1833
1695
|
|
|
@@ -1886,7 +1748,7 @@ describe('guardian conversational approval via conversation engine', () => {
|
|
|
1886
1748
|
});
|
|
1887
1749
|
|
|
1888
1750
|
const res = await handleChannelInbound(
|
|
1889
|
-
req, noopProcessMessage, '
|
|
1751
|
+
req, noopProcessMessage, 'self',
|
|
1890
1752
|
);
|
|
1891
1753
|
const body = await res.json() as Record<string, unknown>;
|
|
1892
1754
|
|
|
@@ -1956,8 +1818,7 @@ describe('guardian conversational approval via conversation engine', () => {
|
|
|
1956
1818
|
});
|
|
1957
1819
|
|
|
1958
1820
|
const res = await handleChannelInbound(
|
|
1959
|
-
req, noopProcessMessage, '
|
|
1960
|
-
undefined, mockConversationGenerator,
|
|
1821
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
1961
1822
|
);
|
|
1962
1823
|
const body = await res.json() as Record<string, unknown>;
|
|
1963
1824
|
|
|
@@ -2003,7 +1864,7 @@ describe('keep_pending remains conversational — standard path', () => {
|
|
|
2003
1864
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
2004
1865
|
|
|
2005
1866
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
2006
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1867
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2007
1868
|
|
|
2008
1869
|
const db = getDb();
|
|
2009
1870
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2019,8 +1880,7 @@ describe('keep_pending remains conversational — standard path', () => {
|
|
|
2019
1880
|
|
|
2020
1881
|
const req = makeInboundRequest({ content: 'approve' });
|
|
2021
1882
|
const res = await handleChannelInbound(
|
|
2022
|
-
req, noopProcessMessage, '
|
|
2023
|
-
undefined, mockConversationGenerator,
|
|
1883
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2024
1884
|
);
|
|
2025
1885
|
const body = await res.json() as Record<string, unknown>;
|
|
2026
1886
|
|
|
@@ -2078,8 +1938,7 @@ describe('keep_pending remains conversational — guardian path', () => {
|
|
|
2078
1938
|
actorExternalId: 'guardian-user-fb',
|
|
2079
1939
|
});
|
|
2080
1940
|
const res = await handleChannelInbound(
|
|
2081
|
-
guardianReq, noopProcessMessage, '
|
|
2082
|
-
undefined, mockConversationGenerator,
|
|
1941
|
+
guardianReq, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2083
1942
|
);
|
|
2084
1943
|
const body = await res.json() as Record<string, unknown>;
|
|
2085
1944
|
|
|
@@ -2120,7 +1979,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2120
1979
|
conversationExternalId: 'requester-cancel-chat',
|
|
2121
1980
|
actorExternalId: 'requester-cancel-user',
|
|
2122
1981
|
});
|
|
2123
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
1982
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2124
1983
|
|
|
2125
1984
|
const db = getDb();
|
|
2126
1985
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2156,8 +2015,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2156
2015
|
actorExternalId: 'requester-cancel-user',
|
|
2157
2016
|
});
|
|
2158
2017
|
const res = await handleChannelInbound(
|
|
2159
|
-
req, noopProcessMessage, '
|
|
2160
|
-
undefined, mockConversationGenerator,
|
|
2018
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2161
2019
|
);
|
|
2162
2020
|
const body = await res.json() as Record<string, unknown>;
|
|
2163
2021
|
|
|
@@ -2188,7 +2046,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2188
2046
|
conversationExternalId: 'requester-cancel-chat',
|
|
2189
2047
|
actorExternalId: 'requester-cancel-user',
|
|
2190
2048
|
});
|
|
2191
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2049
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2192
2050
|
|
|
2193
2051
|
const db = getDb();
|
|
2194
2052
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2224,8 +2082,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2224
2082
|
actorExternalId: 'requester-cancel-user',
|
|
2225
2083
|
});
|
|
2226
2084
|
const res = await handleChannelInbound(
|
|
2227
|
-
req, noopProcessMessage, '
|
|
2228
|
-
undefined, mockConversationGenerator,
|
|
2085
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2229
2086
|
);
|
|
2230
2087
|
const body = await res.json() as Record<string, unknown>;
|
|
2231
2088
|
|
|
@@ -2249,7 +2106,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2249
2106
|
conversationExternalId: 'requester-cancel-chat',
|
|
2250
2107
|
actorExternalId: 'requester-cancel-user',
|
|
2251
2108
|
});
|
|
2252
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2109
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2253
2110
|
|
|
2254
2111
|
const db = getDb();
|
|
2255
2112
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2285,8 +2142,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2285
2142
|
actorExternalId: 'requester-cancel-user',
|
|
2286
2143
|
});
|
|
2287
2144
|
const res = await handleChannelInbound(
|
|
2288
|
-
req, noopProcessMessage, '
|
|
2289
|
-
undefined, mockConversationGenerator,
|
|
2145
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2290
2146
|
);
|
|
2291
2147
|
const body = await res.json() as Record<string, unknown>;
|
|
2292
2148
|
|
|
@@ -2310,7 +2166,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2310
2166
|
conversationExternalId: 'requester-cancel-chat',
|
|
2311
2167
|
actorExternalId: 'requester-cancel-user',
|
|
2312
2168
|
});
|
|
2313
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2169
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2314
2170
|
|
|
2315
2171
|
const db = getDb();
|
|
2316
2172
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2341,7 +2197,7 @@ describe('requester cancel of guardian-gated pending request', () => {
|
|
|
2341
2197
|
conversationExternalId: 'requester-cancel-chat',
|
|
2342
2198
|
actorExternalId: 'requester-cancel-user',
|
|
2343
2199
|
});
|
|
2344
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
2200
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
2345
2201
|
const body = await res.json() as Record<string, unknown>;
|
|
2346
2202
|
|
|
2347
2203
|
expect(body.accepted).toBe(true);
|
|
@@ -2371,7 +2227,7 @@ describe('engine decision race condition — standard path', () => {
|
|
|
2371
2227
|
const deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
2372
2228
|
|
|
2373
2229
|
const initReq = makeInboundRequest({ content: 'init' });
|
|
2374
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2230
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2375
2231
|
|
|
2376
2232
|
const db = getDb();
|
|
2377
2233
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2394,8 +2250,7 @@ describe('engine decision race condition — standard path', () => {
|
|
|
2394
2250
|
|
|
2395
2251
|
const req = makeInboundRequest({ content: 'go ahead' });
|
|
2396
2252
|
const res = await handleChannelInbound(
|
|
2397
|
-
req, noopProcessMessage, '
|
|
2398
|
-
undefined, mockConversationGenerator,
|
|
2253
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2399
2254
|
);
|
|
2400
2255
|
const body = await res.json() as Record<string, unknown>;
|
|
2401
2256
|
|
|
@@ -2466,8 +2321,7 @@ describe('engine decision race condition — guardian path', () => {
|
|
|
2466
2321
|
actorExternalId: 'guardian-race-user',
|
|
2467
2322
|
});
|
|
2468
2323
|
const res = await handleChannelInbound(
|
|
2469
|
-
guardianReq, noopProcessMessage, '
|
|
2470
|
-
undefined, mockConversationGenerator,
|
|
2324
|
+
guardianReq, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2471
2325
|
);
|
|
2472
2326
|
const body = await res.json() as Record<string, unknown>;
|
|
2473
2327
|
|
|
@@ -2517,7 +2371,7 @@ describe('non-decision status reply for different channels', () => {
|
|
|
2517
2371
|
|
|
2518
2372
|
// Establish the conversation using sms (non-rich channel)
|
|
2519
2373
|
const initReq = makeInboundRequest({ content: 'init', sourceChannel: 'sms' });
|
|
2520
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2374
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2521
2375
|
|
|
2522
2376
|
const db = getDb();
|
|
2523
2377
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2528,7 +2382,7 @@ describe('non-decision status reply for different channels', () => {
|
|
|
2528
2382
|
|
|
2529
2383
|
// Send a non-decision message
|
|
2530
2384
|
const req = makeInboundRequest({ content: 'what is happening?', sourceChannel: 'sms' });
|
|
2531
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
2385
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
2532
2386
|
const body = await res.json() as Record<string, unknown>;
|
|
2533
2387
|
|
|
2534
2388
|
expect(body.accepted).toBe(true);
|
|
@@ -2551,7 +2405,7 @@ describe('non-decision status reply for different channels', () => {
|
|
|
2551
2405
|
|
|
2552
2406
|
// Establish the conversation using telegram (rich channel)
|
|
2553
2407
|
const initReq = makeInboundRequest({ content: 'init', sourceChannel: 'telegram' });
|
|
2554
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2408
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2555
2409
|
|
|
2556
2410
|
const db = getDb();
|
|
2557
2411
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2562,7 +2416,7 @@ describe('non-decision status reply for different channels', () => {
|
|
|
2562
2416
|
|
|
2563
2417
|
// Send a non-decision message
|
|
2564
2418
|
const req = makeInboundRequest({ content: 'what is happening?', sourceChannel: 'telegram' });
|
|
2565
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
2419
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
2566
2420
|
const body = await res.json() as Record<string, unknown>;
|
|
2567
2421
|
|
|
2568
2422
|
expect(body.accepted).toBe(true);
|
|
@@ -2623,7 +2477,7 @@ describe('background channel processing approval prompts', () => {
|
|
|
2623
2477
|
externalMessageId: 'msg-bg-1',
|
|
2624
2478
|
});
|
|
2625
2479
|
|
|
2626
|
-
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage
|
|
2480
|
+
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage);
|
|
2627
2481
|
const body = await res.json() as Record<string, unknown>;
|
|
2628
2482
|
expect(body.accepted).toBe(true);
|
|
2629
2483
|
|
|
@@ -2677,7 +2531,7 @@ describe('background channel processing approval prompts', () => {
|
|
|
2677
2531
|
externalMessageId: 'msg-bg-format-1',
|
|
2678
2532
|
});
|
|
2679
2533
|
|
|
2680
|
-
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage
|
|
2534
|
+
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage);
|
|
2681
2535
|
const body = await res.json() as Record<string, unknown>;
|
|
2682
2536
|
expect(body.accepted).toBe(true);
|
|
2683
2537
|
|
|
@@ -2722,7 +2576,7 @@ describe('background channel processing approval prompts', () => {
|
|
|
2722
2576
|
externalMessageId: 'msg-ng-1',
|
|
2723
2577
|
});
|
|
2724
2578
|
|
|
2725
|
-
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage
|
|
2579
|
+
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage);
|
|
2726
2580
|
const body = await res.json() as Record<string, unknown>;
|
|
2727
2581
|
expect(body.accepted).toBe(true);
|
|
2728
2582
|
|
|
@@ -2765,7 +2619,7 @@ describe('background channel processing approval prompts', () => {
|
|
|
2765
2619
|
externalMessageId: 'msg-bg-unverified-1',
|
|
2766
2620
|
});
|
|
2767
2621
|
|
|
2768
|
-
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage
|
|
2622
|
+
const res = await handleChannelInbound(req, processMessage as unknown as typeof noopProcessMessage);
|
|
2769
2623
|
const body = await res.json() as Record<string, unknown>;
|
|
2770
2624
|
expect(body.accepted).toBe(true);
|
|
2771
2625
|
|
|
@@ -2838,7 +2692,7 @@ describe('NL approval routing via destination-scoped canonical requests', () =>
|
|
|
2838
2692
|
content: 'yes',
|
|
2839
2693
|
externalMessageId: `msg-nl-approve-${Date.now()}`,
|
|
2840
2694
|
});
|
|
2841
|
-
const res = await handleChannelInbound(req, noopProcessMessage as any
|
|
2695
|
+
const res = await handleChannelInbound(req, noopProcessMessage as any);
|
|
2842
2696
|
const body = await res.json() as Record<string, unknown>;
|
|
2843
2697
|
|
|
2844
2698
|
expect(body.accepted).toBe(true);
|
|
@@ -2890,7 +2744,7 @@ describe('NL approval routing via destination-scoped canonical requests', () =>
|
|
|
2890
2744
|
content: 'approve',
|
|
2891
2745
|
externalMessageId: `msg-nl-mismatch-${Date.now()}`,
|
|
2892
2746
|
});
|
|
2893
|
-
const res = await handleChannelInbound(req, noopProcessMessage as any
|
|
2747
|
+
const res = await handleChannelInbound(req, noopProcessMessage as any);
|
|
2894
2748
|
const body = await res.json() as Record<string, unknown>;
|
|
2895
2749
|
|
|
2896
2750
|
expect(body.accepted).toBe(true);
|
|
@@ -2931,7 +2785,7 @@ describe('trusted-contact self-approval blocked before guardian approval row exi
|
|
|
2931
2785
|
conversationExternalId: 'tc-selfapproval-chat',
|
|
2932
2786
|
actorExternalId: 'tc-selfapproval-user',
|
|
2933
2787
|
});
|
|
2934
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2788
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2935
2789
|
|
|
2936
2790
|
const db = getDb();
|
|
2937
2791
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -2960,8 +2814,7 @@ describe('trusted-contact self-approval blocked before guardian approval row exi
|
|
|
2960
2814
|
actorExternalId: 'tc-selfapproval-user',
|
|
2961
2815
|
});
|
|
2962
2816
|
const res = await handleChannelInbound(
|
|
2963
|
-
req, noopProcessMessage, '
|
|
2964
|
-
undefined, mockConversationGenerator,
|
|
2817
|
+
req, noopProcessMessage, 'self', undefined, mockConversationGenerator,
|
|
2965
2818
|
);
|
|
2966
2819
|
const body = await res.json() as Record<string, unknown>;
|
|
2967
2820
|
|
|
@@ -2987,7 +2840,7 @@ describe('trusted-contact self-approval blocked before guardian approval row exi
|
|
|
2987
2840
|
conversationExternalId: 'tc-selfapproval-chat',
|
|
2988
2841
|
actorExternalId: 'tc-selfapproval-user',
|
|
2989
2842
|
});
|
|
2990
|
-
await handleChannelInbound(initReq, noopProcessMessage
|
|
2843
|
+
await handleChannelInbound(initReq, noopProcessMessage);
|
|
2991
2844
|
|
|
2992
2845
|
const db = getDb();
|
|
2993
2846
|
const events = db.$client.prepare('SELECT conversation_id FROM channel_inbound_events').all() as Array<{ conversation_id: string }>;
|
|
@@ -3006,7 +2859,7 @@ describe('trusted-contact self-approval blocked before guardian approval row exi
|
|
|
3006
2859
|
conversationExternalId: 'tc-selfapproval-chat',
|
|
3007
2860
|
actorExternalId: 'tc-selfapproval-user',
|
|
3008
2861
|
});
|
|
3009
|
-
const res = await handleChannelInbound(req, noopProcessMessage
|
|
2862
|
+
const res = await handleChannelInbound(req, noopProcessMessage);
|
|
3010
2863
|
const body = await res.json() as Record<string, unknown>;
|
|
3011
2864
|
|
|
3012
2865
|
expect(body.accepted).toBe(true);
|