@vellumai/assistant 0.4.2 → 0.4.3
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 +84 -7
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/assistant-id-boundary-guard.test.ts +290 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/channel-guardian.test.ts +6 -5
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +21 -0
- package/src/__tests__/guardian-outbound-http.test.ts +0 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/non-member-access-request.test.ts +28 -1
- package/src/__tests__/notification-decision-strategy.test.ts +44 -0
- package/src/__tests__/relay-server.test.ts +644 -4
- package/src/__tests__/session-init.benchmark.test.ts +0 -1
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +43 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/approvals/guardian-decision-primitive.ts +2 -1
- package/src/approvals/guardian-request-resolvers.ts +42 -3
- package/src/calls/call-constants.ts +8 -0
- package/src/calls/call-controller.ts +2 -1
- package/src/calls/call-domain.ts +5 -4
- package/src/calls/relay-server.ts +513 -116
- package/src/calls/twilio-routes.ts +3 -5
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +164 -1
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +214 -0
- package/src/config/calls-schema.ts +12 -0
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -2
- package/src/daemon/handlers/config-channels.ts +5 -7
- package/src/daemon/handlers/config-inbox.ts +2 -0
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +11 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +3 -1
- package/src/daemon/server.ts +2 -1
- package/src/daemon/session-agent-loop.ts +2 -1
- package/src/daemon/session-runtime-assembly.ts +3 -1
- package/src/daemon/session-surfaces.ts +29 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +4 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +10 -5
- package/src/notifications/copy-composer.ts +11 -1
- package/src/notifications/emit-signal.ts +2 -1
- package/src/runtime/access-request-helper.ts +11 -3
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -4
- package/src/runtime/http-server.ts +11 -20
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +2 -1
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/events-routes.ts +2 -3
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +4 -3
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +2 -1
- package/src/workspace/git-service.ts +19 -0
|
@@ -492,6 +492,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
492
492
|
body: JSON.stringify({
|
|
493
493
|
sourceChannel: 'voice',
|
|
494
494
|
expectedExternalUserId: '+15551234567',
|
|
495
|
+
friendName: 'Alice',
|
|
496
|
+
guardianName: 'Bob',
|
|
495
497
|
maxUses: 3,
|
|
496
498
|
}),
|
|
497
499
|
});
|
|
@@ -514,6 +516,9 @@ describe('voice invite HTTP routes', () => {
|
|
|
514
516
|
expect(invite.voiceCodeDigits).toBe(6);
|
|
515
517
|
// expectedExternalUserId should be recorded
|
|
516
518
|
expect(invite.expectedExternalUserId).toBe('+15551234567');
|
|
519
|
+
// friendName and guardianName should be recorded
|
|
520
|
+
expect(invite.friendName).toBe('Alice');
|
|
521
|
+
expect(invite.guardianName).toBe('Bob');
|
|
517
522
|
});
|
|
518
523
|
|
|
519
524
|
test('voice invite creation requires expectedExternalUserId', async () => {
|
|
@@ -522,6 +527,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
522
527
|
headers: { 'Content-Type': 'application/json' },
|
|
523
528
|
body: JSON.stringify({
|
|
524
529
|
sourceChannel: 'voice',
|
|
530
|
+
friendName: 'Alice',
|
|
531
|
+
guardianName: 'Bob',
|
|
525
532
|
}),
|
|
526
533
|
});
|
|
527
534
|
|
|
@@ -540,6 +547,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
540
547
|
body: JSON.stringify({
|
|
541
548
|
sourceChannel: 'voice',
|
|
542
549
|
expectedExternalUserId: 'not-a-phone-number',
|
|
550
|
+
friendName: 'Alice',
|
|
551
|
+
guardianName: 'Bob',
|
|
543
552
|
}),
|
|
544
553
|
});
|
|
545
554
|
|
|
@@ -551,6 +560,44 @@ describe('voice invite HTTP routes', () => {
|
|
|
551
560
|
expect(body.error).toContain('E.164');
|
|
552
561
|
});
|
|
553
562
|
|
|
563
|
+
test('voice invite creation requires friendName', async () => {
|
|
564
|
+
const req = new Request('http://localhost/v1/ingress/invites', {
|
|
565
|
+
method: 'POST',
|
|
566
|
+
headers: { 'Content-Type': 'application/json' },
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
sourceChannel: 'voice',
|
|
569
|
+
expectedExternalUserId: '+15551234567',
|
|
570
|
+
guardianName: 'Bob',
|
|
571
|
+
}),
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const res = await handleCreateInvite(req);
|
|
575
|
+
const body = await res.json() as Record<string, unknown>;
|
|
576
|
+
|
|
577
|
+
expect(res.status).toBe(400);
|
|
578
|
+
expect(body.ok).toBe(false);
|
|
579
|
+
expect(body.error).toContain('friendName');
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('voice invite creation requires guardianName', async () => {
|
|
583
|
+
const req = new Request('http://localhost/v1/ingress/invites', {
|
|
584
|
+
method: 'POST',
|
|
585
|
+
headers: { 'Content-Type': 'application/json' },
|
|
586
|
+
body: JSON.stringify({
|
|
587
|
+
sourceChannel: 'voice',
|
|
588
|
+
expectedExternalUserId: '+15551234567',
|
|
589
|
+
friendName: 'Alice',
|
|
590
|
+
}),
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const res = await handleCreateInvite(req);
|
|
594
|
+
const body = await res.json() as Record<string, unknown>;
|
|
595
|
+
|
|
596
|
+
expect(res.status).toBe(400);
|
|
597
|
+
expect(body.ok).toBe(false);
|
|
598
|
+
expect(body.error).toContain('guardianName');
|
|
599
|
+
});
|
|
600
|
+
|
|
554
601
|
test('voiceCodeDigits is always 6 — custom values are ignored', async () => {
|
|
555
602
|
const req = new Request('http://localhost/v1/ingress/invites', {
|
|
556
603
|
method: 'POST',
|
|
@@ -558,6 +605,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
558
605
|
body: JSON.stringify({
|
|
559
606
|
sourceChannel: 'voice',
|
|
560
607
|
expectedExternalUserId: '+15551234567',
|
|
608
|
+
friendName: 'Alice',
|
|
609
|
+
guardianName: 'Bob',
|
|
561
610
|
voiceCodeDigits: 8,
|
|
562
611
|
}),
|
|
563
612
|
});
|
|
@@ -579,6 +628,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
579
628
|
body: JSON.stringify({
|
|
580
629
|
sourceChannel: 'voice',
|
|
581
630
|
expectedExternalUserId: '+15551234567',
|
|
631
|
+
friendName: 'Alice',
|
|
632
|
+
guardianName: 'Bob',
|
|
582
633
|
}),
|
|
583
634
|
});
|
|
584
635
|
|
|
@@ -600,6 +651,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
600
651
|
body: JSON.stringify({
|
|
601
652
|
sourceChannel: 'voice',
|
|
602
653
|
expectedExternalUserId: '+15551234567',
|
|
654
|
+
friendName: 'Alice',
|
|
655
|
+
guardianName: 'Bob',
|
|
603
656
|
maxUses: 1,
|
|
604
657
|
}),
|
|
605
658
|
}));
|
|
@@ -648,6 +701,8 @@ describe('voice invite HTTP routes', () => {
|
|
|
648
701
|
body: JSON.stringify({
|
|
649
702
|
sourceChannel: 'voice',
|
|
650
703
|
expectedExternalUserId: '+15551234567',
|
|
704
|
+
friendName: 'Alice',
|
|
705
|
+
guardianName: 'Bob',
|
|
651
706
|
maxUses: 1,
|
|
652
707
|
}),
|
|
653
708
|
}));
|
|
@@ -30,7 +30,6 @@ mock.module('../util/platform.js', () => ({
|
|
|
30
30
|
getDbPath: () => join(testDir, 'test.db'),
|
|
31
31
|
getLogPath: () => join(testDir, 'test.log'),
|
|
32
32
|
ensureDataDir: () => {},
|
|
33
|
-
normalizeAssistantId: (id: string) => id === 'self' ? 'self' : id,
|
|
34
33
|
readHttpToken: () => 'test-bearer-token',
|
|
35
34
|
}));
|
|
36
35
|
|
|
@@ -437,6 +436,34 @@ describe('access-request-helper unit tests', () => {
|
|
|
437
436
|
expect(payload.guardianBindingChannel).toBe('telegram');
|
|
438
437
|
});
|
|
439
438
|
|
|
439
|
+
test('notifyGuardianOfAccessRequest for voice channel includes senderName in contextPayload', () => {
|
|
440
|
+
const result = notifyGuardianOfAccessRequest({
|
|
441
|
+
canonicalAssistantId: 'self',
|
|
442
|
+
sourceChannel: 'voice',
|
|
443
|
+
externalChatId: '+15559998888',
|
|
444
|
+
senderExternalUserId: '+15559998888',
|
|
445
|
+
senderName: 'Alice Caller',
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
expect(result.notified).toBe(true);
|
|
449
|
+
expect(emitSignalCalls.length).toBe(1);
|
|
450
|
+
|
|
451
|
+
const payload = emitSignalCalls[0].contextPayload as Record<string, unknown>;
|
|
452
|
+
expect(payload.sourceChannel).toBe('voice');
|
|
453
|
+
expect(payload.senderName).toBe('Alice Caller');
|
|
454
|
+
expect(payload.senderExternalUserId).toBe('+15559998888');
|
|
455
|
+
expect(payload.senderIdentifier).toBe('Alice Caller');
|
|
456
|
+
|
|
457
|
+
// Canonical request should exist
|
|
458
|
+
const pending = listCanonicalGuardianRequests({
|
|
459
|
+
status: 'pending',
|
|
460
|
+
requesterExternalUserId: '+15559998888',
|
|
461
|
+
sourceChannel: 'voice',
|
|
462
|
+
kind: 'access_request',
|
|
463
|
+
});
|
|
464
|
+
expect(pending.length).toBe(1);
|
|
465
|
+
});
|
|
466
|
+
|
|
440
467
|
test('notifyGuardianOfAccessRequest includes requestCode in contextPayload', () => {
|
|
441
468
|
const result = notifyGuardianOfAccessRequest({
|
|
442
469
|
canonicalAssistantId: 'self',
|
|
@@ -196,6 +196,50 @@ describe('notification decision strategy', () => {
|
|
|
196
196
|
expect(copy.vellum!.body).toContain('open invite flow');
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
test('ingress.access_request template includes caller name for voice-originated requests', () => {
|
|
200
|
+
// In production, senderIdentifier resolves to senderName for voice
|
|
201
|
+
// calls (senderName || senderUsername || senderExternalUserId), so
|
|
202
|
+
// both values are the caller's name. The phone number arrives via
|
|
203
|
+
// senderExternalUserId and should appear in the parenthetical.
|
|
204
|
+
const signal = makeSignal({
|
|
205
|
+
sourceEventName: 'ingress.access_request',
|
|
206
|
+
contextPayload: {
|
|
207
|
+
senderIdentifier: 'Alice Smith',
|
|
208
|
+
senderName: 'Alice Smith',
|
|
209
|
+
senderExternalUserId: '+15559998888',
|
|
210
|
+
sourceChannel: 'voice',
|
|
211
|
+
requestCode: 'V1C2E3',
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const copy = composeFallbackCopy(signal, channels);
|
|
216
|
+
expect(copy.vellum).toBeDefined();
|
|
217
|
+
expect(copy.vellum!.title).toBe('Access Request');
|
|
218
|
+
// Voice-originated requests should include the caller name and phone number in parentheses
|
|
219
|
+
expect(copy.vellum!.body).toContain('Alice Smith');
|
|
220
|
+
expect(copy.vellum!.body).toContain('(+15559998888)');
|
|
221
|
+
expect(copy.vellum!.body).toContain('calling');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('ingress.access_request template falls back to non-voice copy when sourceChannel is not voice', () => {
|
|
225
|
+
const signal = makeSignal({
|
|
226
|
+
sourceEventName: 'ingress.access_request',
|
|
227
|
+
contextPayload: {
|
|
228
|
+
senderIdentifier: 'user-123',
|
|
229
|
+
senderName: 'Bob Jones',
|
|
230
|
+
sourceChannel: 'telegram',
|
|
231
|
+
requestCode: 'T1G2M3',
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const copy = composeFallbackCopy(signal, channels);
|
|
236
|
+
expect(copy.vellum).toBeDefined();
|
|
237
|
+
// Non-voice should use the standard "requesting access" text, not "calling"
|
|
238
|
+
expect(copy.vellum!.body).toContain('user-123');
|
|
239
|
+
expect(copy.vellum!.body).toContain('requesting access');
|
|
240
|
+
expect(copy.vellum!.body).not.toContain('calling');
|
|
241
|
+
});
|
|
242
|
+
|
|
199
243
|
test('ingress.access_request Telegram deliveryText is concise', () => {
|
|
200
244
|
const signal = makeSignal({
|
|
201
245
|
sourceEventName: 'ingress.access_request',
|