@vellumai/assistant 0.3.3 → 0.3.5
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/Dockerfile +2 -0
- package/README.md +45 -18
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +13 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +100 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +391 -1
- package/src/__tests__/call-routes-http.test.ts +27 -2
- package/src/__tests__/channel-approval-routes.test.ts +397 -135
- package/src/__tests__/channel-approvals.test.ts +99 -3
- package/src/__tests__/channel-delivery-store.test.ts +30 -4
- package/src/__tests__/channel-guardian.test.ts +261 -22
- package/src/__tests__/channel-readiness-service.test.ts +257 -0
- package/src/__tests__/config-schema.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-lifecycle.test.ts +636 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +19 -13
- package/src/__tests__/handlers-twilio-config.test.ts +480 -0
- package/src/__tests__/ipc-snapshot.test.ts +63 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +4 -0
- package/src/__tests__/run-orchestrator.test.ts +22 -0
- package/src/__tests__/secret-scanner.test.ts +223 -0
- package/src/__tests__/session-runtime-assembly.test.ts +85 -1
- package/src/__tests__/shell-parser-property.test.ts +357 -2
- package/src/__tests__/sms-messaging-provider.test.ts +125 -0
- package/src/__tests__/system-prompt.test.ts +25 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +34 -1
- package/src/__tests__/twilio-routes.test.ts +39 -3
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +68 -0
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/calls/call-domain.ts +8 -5
- package/src/calls/call-orchestrator.ts +85 -22
- package/src/calls/twilio-config.ts +17 -11
- package/src/calls/twilio-rest.ts +276 -0
- package/src/calls/twilio-routes.ts +39 -1
- package/src/cli/map.ts +6 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +67 -0
- package/src/commands/cc-command-registry.ts +14 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +10 -3
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +199 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +320 -0
- package/src/config/bundled-skills/media-processing/services/capability-registry.ts +137 -0
- package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +280 -0
- package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +144 -0
- package/src/config/bundled-skills/media-processing/services/feedback-store.ts +136 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +261 -0
- package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +95 -0
- package/src/config/bundled-skills/media-processing/services/timeline-service.ts +267 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +301 -0
- package/src/config/bundled-skills/media-processing/tools/detect-events.ts +110 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +190 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +166 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +300 -0
- package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +235 -0
- package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +142 -0
- package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +150 -0
- package/src/config/bundled-skills/messaging/SKILL.md +24 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/defaults.ts +2 -1
- package/src/config/schema.ts +9 -3
- package/src/config/skills.ts +5 -32
- package/src/config/system-prompt.ts +40 -0
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/user-reference.ts +29 -0
- package/src/config/vellum-skills/catalog.json +58 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +118 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +76 -6
- package/src/daemon/auth-manager.ts +103 -0
- package/src/daemon/computer-use-session.ts +8 -1
- package/src/daemon/config-watcher.ts +253 -0
- package/src/daemon/handlers/config.ts +819 -22
- package/src/daemon/handlers/dictation.ts +182 -0
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +2 -0
- package/src/daemon/handlers/sessions.ts +2 -0
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/skills.ts +6 -7
- package/src/daemon/handlers/work-items.ts +15 -7
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +114 -4
- package/src/daemon/ipc-handler.ts +87 -0
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/ride-shotgun-handler.ts +11 -1
- package/src/daemon/server.ts +111 -504
- package/src/daemon/session-agent-loop.ts +10 -15
- package/src/daemon/session-runtime-assembly.ts +115 -44
- package/src/daemon/session-tool-setup.ts +2 -0
- package/src/daemon/session.ts +19 -2
- package/src/inbound/public-ingress-urls.ts +3 -3
- package/src/memory/channel-guardian-store.ts +2 -1
- package/src/memory/db-connection.ts +28 -0
- package/src/memory/db-init.ts +1163 -0
- package/src/memory/db.ts +2 -2007
- package/src/memory/embedding-backend.ts +79 -11
- package/src/memory/indexer.ts +2 -0
- package/src/memory/job-handlers/media-processing.ts +100 -0
- package/src/memory/job-utils.ts +64 -4
- package/src/memory/jobs-store.ts +2 -1
- package/src/memory/jobs-worker.ts +11 -1
- package/src/memory/media-store.ts +759 -0
- package/src/memory/recall-cache.ts +107 -0
- package/src/memory/retriever.ts +36 -2
- package/src/memory/schema-migration.ts +984 -0
- package/src/memory/schema.ts +99 -0
- package/src/memory/search/entity.ts +208 -25
- package/src/memory/search/ranking.ts +6 -1
- package/src/memory/search/types.ts +26 -0
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +204 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/permissions/checker.ts +16 -2
- package/src/permissions/prompter.ts +14 -3
- package/src/permissions/trust-store.ts +7 -0
- package/src/runtime/approval-message-composer.ts +143 -0
- package/src/runtime/channel-approvals.ts +29 -7
- package/src/runtime/channel-guardian-service.ts +44 -18
- package/src/runtime/channel-readiness-service.ts +292 -0
- package/src/runtime/channel-readiness-types.ts +29 -0
- package/src/runtime/gateway-client.ts +2 -1
- package/src/runtime/http-server.ts +65 -28
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-routes.ts +237 -103
- package/src/runtime/routes/run-routes.ts +7 -1
- package/src/runtime/run-orchestrator.ts +43 -3
- package/src/security/secret-scanner.ts +218 -0
- package/src/skills/frontmatter.ts +63 -0
- package/src/skills/slash-commands.ts +23 -0
- package/src/skills/vellum-catalog-remote.ts +107 -0
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/browser/auto-navigate.ts +132 -24
- package/src/tools/browser/browser-manager.ts +67 -61
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/claude-code/claude-code.ts +55 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +10 -2
- package/src/tools/network/web-search.ts +1 -1
- package/src/tools/skills/vellum-catalog.ts +61 -156
- package/src/tools/terminal/parser.ts +21 -5
- package/src/tools/types.ts +2 -0
- package/src/twitter/router.ts +1 -1
- package/src/util/platform.ts +43 -1
- package/src/util/retry.ts +4 -4
|
@@ -523,6 +523,54 @@ describe('Twilio config handler', () => {
|
|
|
523
523
|
}
|
|
524
524
|
});
|
|
525
525
|
|
|
526
|
+
test('getTwilioConfig with assistantId prefers assistant-scoped mapping over global phone number', () => {
|
|
527
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
528
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
529
|
+
rawConfigStore = {
|
|
530
|
+
sms: {
|
|
531
|
+
phoneNumber: '+15551234567',
|
|
532
|
+
assistantPhoneNumbers: {
|
|
533
|
+
'ast-alpha': '+15550000001',
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
ingress: { enabled: true, publicBaseUrl: 'https://test.ngrok.io' },
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const savedEnv = process.env.TWILIO_PHONE_NUMBER;
|
|
540
|
+
delete process.env.TWILIO_PHONE_NUMBER;
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const config = getTwilioConfig('ast-alpha');
|
|
544
|
+
expect(config.phoneNumber).toBe('+15550000001');
|
|
545
|
+
} finally {
|
|
546
|
+
if (savedEnv !== undefined) process.env.TWILIO_PHONE_NUMBER = savedEnv;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test('getTwilioConfig with assistantId falls back to global number when mapping is missing', () => {
|
|
551
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
552
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
553
|
+
rawConfigStore = {
|
|
554
|
+
sms: {
|
|
555
|
+
phoneNumber: '+15551234567',
|
|
556
|
+
assistantPhoneNumbers: {
|
|
557
|
+
'ast-alpha': '+15550000001',
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
ingress: { enabled: true, publicBaseUrl: 'https://test.ngrok.io' },
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const savedEnv = process.env.TWILIO_PHONE_NUMBER;
|
|
564
|
+
delete process.env.TWILIO_PHONE_NUMBER;
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
const config = getTwilioConfig('ast-beta');
|
|
568
|
+
expect(config.phoneNumber).toBe('+15551234567');
|
|
569
|
+
} finally {
|
|
570
|
+
if (savedEnv !== undefined) process.env.TWILIO_PHONE_NUMBER = savedEnv;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
526
574
|
// ── assign_number ───────────────────────────────────────────────────
|
|
527
575
|
|
|
528
576
|
test('assign_number persists phone number to config', async () => {
|
|
@@ -958,6 +1006,79 @@ describe('Twilio config handler', () => {
|
|
|
958
1006
|
expect(body).toContain('new-tunnel.ngrok.io');
|
|
959
1007
|
});
|
|
960
1008
|
|
|
1009
|
+
test('ingress config update reconciles all unique assigned Twilio numbers (legacy + assistant mapping)', async () => {
|
|
1010
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1011
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1012
|
+
rawConfigStore = {
|
|
1013
|
+
sms: {
|
|
1014
|
+
phoneNumber: '+15551234567',
|
|
1015
|
+
assistantPhoneNumbers: {
|
|
1016
|
+
'ast-alpha': '+15551234567', // duplicate of legacy; should only sync once
|
|
1017
|
+
'ast-beta': '+15553333333',
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const fetchCalls: Array<{ url: string; method: string; body?: string }> = [];
|
|
1023
|
+
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
1024
|
+
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
1025
|
+
fetchCalls.push({ url: urlStr, method: init?.method ?? 'GET', body: init?.body?.toString() });
|
|
1026
|
+
|
|
1027
|
+
if (urlStr.includes('/internal/telegram/reconcile')) {
|
|
1028
|
+
return new Response('{}', { status: 200 });
|
|
1029
|
+
}
|
|
1030
|
+
if (urlStr.includes('IncomingPhoneNumbers.json?PhoneNumber=')) {
|
|
1031
|
+
if (urlStr.includes('%2B15551234567')) {
|
|
1032
|
+
return new Response(JSON.stringify({
|
|
1033
|
+
incoming_phone_numbers: [{ sid: 'PN-legacy', phone_number: '+15551234567' }],
|
|
1034
|
+
}), { status: 200 });
|
|
1035
|
+
}
|
|
1036
|
+
if (urlStr.includes('%2B15553333333')) {
|
|
1037
|
+
return new Response(JSON.stringify({
|
|
1038
|
+
incoming_phone_numbers: [{ sid: 'PN-beta', phone_number: '+15553333333' }],
|
|
1039
|
+
}), { status: 200 });
|
|
1040
|
+
}
|
|
1041
|
+
return new Response(JSON.stringify({ incoming_phone_numbers: [] }), { status: 200 });
|
|
1042
|
+
}
|
|
1043
|
+
if (urlStr.includes('IncomingPhoneNumbers/PN-legacy.json') && init?.method === 'POST') {
|
|
1044
|
+
return new Response(JSON.stringify({ sid: 'PN-legacy' }), { status: 200 });
|
|
1045
|
+
}
|
|
1046
|
+
if (urlStr.includes('IncomingPhoneNumbers/PN-beta.json') && init?.method === 'POST') {
|
|
1047
|
+
return new Response(JSON.stringify({ sid: 'PN-beta' }), { status: 200 });
|
|
1048
|
+
}
|
|
1049
|
+
return new Response('{}', { status: 200 });
|
|
1050
|
+
}) as typeof fetch;
|
|
1051
|
+
|
|
1052
|
+
const msg: IngressConfigRequest = {
|
|
1053
|
+
type: 'ingress_config',
|
|
1054
|
+
action: 'set',
|
|
1055
|
+
publicBaseUrl: 'https://multi-number.ngrok.io',
|
|
1056
|
+
enabled: true,
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const { ctx, sent } = createTestContext();
|
|
1060
|
+
await handleIngressConfig(msg, {} as net.Socket, ctx);
|
|
1061
|
+
|
|
1062
|
+
expect(sent).toHaveLength(1);
|
|
1063
|
+
const res = sent[0] as { type: string; success: boolean; enabled: boolean };
|
|
1064
|
+
expect(res.type).toBe('ingress_config_response');
|
|
1065
|
+
expect(res.success).toBe(true);
|
|
1066
|
+
expect(res.enabled).toBe(true);
|
|
1067
|
+
|
|
1068
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1069
|
+
|
|
1070
|
+
const lookupCalls = fetchCalls.filter((c) => c.url.includes('IncomingPhoneNumbers.json?PhoneNumber='));
|
|
1071
|
+
const lookedUpNumbers = lookupCalls
|
|
1072
|
+
.map((c) => decodeURIComponent(c.url.split('PhoneNumber=')[1] ?? ''))
|
|
1073
|
+
.sort();
|
|
1074
|
+
expect(lookedUpNumbers).toEqual(['+15551234567', '+15553333333']);
|
|
1075
|
+
|
|
1076
|
+
const updateCalls = fetchCalls.filter((c) => c.method === 'POST' && c.url.includes('IncomingPhoneNumbers/PN-'));
|
|
1077
|
+
const updatedSids = updateCalls.map((c) => (c.url.includes('PN-legacy') ? 'PN-legacy' : 'PN-beta')).sort();
|
|
1078
|
+
expect(updatedSids).toEqual(['PN-beta', 'PN-legacy']);
|
|
1079
|
+
expect(updateCalls[0]?.body ?? '').toContain('multi-number.ngrok.io');
|
|
1080
|
+
});
|
|
1081
|
+
|
|
961
1082
|
test('webhook sync failure on ingress update does not fail the ingress update', async () => {
|
|
962
1083
|
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
963
1084
|
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
@@ -1316,4 +1437,363 @@ describe('Twilio config handler', () => {
|
|
|
1316
1437
|
expect(responseStr).not.toContain('AC_secret_account_sid_12345');
|
|
1317
1438
|
expect(responseStr).not.toContain('secret_auth_token_67890');
|
|
1318
1439
|
});
|
|
1440
|
+
|
|
1441
|
+
// ── sms_compliance_status ───────────────────────────────────────────
|
|
1442
|
+
|
|
1443
|
+
test('sms_compliance_status returns structured compliance data for toll-free number', async () => {
|
|
1444
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1445
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1446
|
+
rawConfigStore = { sms: { phoneNumber: '+18001234567' } };
|
|
1447
|
+
|
|
1448
|
+
globalThis.fetch = (async (url: string | URL | Request) => {
|
|
1449
|
+
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
1450
|
+
// Phone number SID lookup
|
|
1451
|
+
if (urlStr.includes('IncomingPhoneNumbers.json') && urlStr.includes('PhoneNumber=')) {
|
|
1452
|
+
return new Response(JSON.stringify({
|
|
1453
|
+
incoming_phone_numbers: [{ sid: 'PN123abc', phone_number: '+18001234567' }],
|
|
1454
|
+
}), { status: 200 });
|
|
1455
|
+
}
|
|
1456
|
+
// Toll-free verification lookup
|
|
1457
|
+
if (urlStr.includes('Tollfree/Verifications')) {
|
|
1458
|
+
return new Response(JSON.stringify({
|
|
1459
|
+
verifications: [{
|
|
1460
|
+
sid: 'TF_VER_001',
|
|
1461
|
+
status: 'TWILIO_APPROVED',
|
|
1462
|
+
rejection_reason: null,
|
|
1463
|
+
edit_allowed: false,
|
|
1464
|
+
}],
|
|
1465
|
+
}), { status: 200 });
|
|
1466
|
+
}
|
|
1467
|
+
return originalFetch(url);
|
|
1468
|
+
}) as typeof fetch;
|
|
1469
|
+
|
|
1470
|
+
const msg: TwilioConfigRequest = {
|
|
1471
|
+
type: 'twilio_config',
|
|
1472
|
+
action: 'sms_compliance_status',
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
const { ctx, sent } = createTestContext();
|
|
1476
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1477
|
+
|
|
1478
|
+
expect(sent).toHaveLength(1);
|
|
1479
|
+
const res = sent[0] as { type: string; success: boolean; compliance?: Record<string, unknown> };
|
|
1480
|
+
expect(res.success).toBe(true);
|
|
1481
|
+
expect(res.compliance).toBeDefined();
|
|
1482
|
+
expect(res.compliance!.numberType).toBe('toll_free');
|
|
1483
|
+
expect(res.compliance!.verificationSid).toBe('TF_VER_001');
|
|
1484
|
+
expect(res.compliance!.verificationStatus).toBe('TWILIO_APPROVED');
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test('sms_compliance_status returns local_10dlc type for non-toll-free number without remote check', async () => {
|
|
1488
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1489
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1490
|
+
rawConfigStore = { sms: { phoneNumber: '+15551234567' } };
|
|
1491
|
+
|
|
1492
|
+
const msg: TwilioConfigRequest = {
|
|
1493
|
+
type: 'twilio_config',
|
|
1494
|
+
action: 'sms_compliance_status',
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
const { ctx, sent } = createTestContext();
|
|
1498
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1499
|
+
|
|
1500
|
+
expect(sent).toHaveLength(1);
|
|
1501
|
+
const res = sent[0] as { type: string; success: boolean; compliance?: Record<string, unknown> };
|
|
1502
|
+
expect(res.success).toBe(true);
|
|
1503
|
+
expect(res.compliance).toBeDefined();
|
|
1504
|
+
expect(res.compliance!.numberType).toBe('local_10dlc');
|
|
1505
|
+
// No verification fields for non-toll-free
|
|
1506
|
+
expect(res.compliance!.verificationSid).toBeUndefined();
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
test('sms_compliance_status returns error when no credentials', async () => {
|
|
1510
|
+
const msg: TwilioConfigRequest = {
|
|
1511
|
+
type: 'twilio_config',
|
|
1512
|
+
action: 'sms_compliance_status',
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
const { ctx, sent } = createTestContext();
|
|
1516
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1517
|
+
|
|
1518
|
+
expect(sent).toHaveLength(1);
|
|
1519
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1520
|
+
expect(res.success).toBe(false);
|
|
1521
|
+
expect(res.error).toContain('Twilio credentials not configured');
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
// ── sms_submit_tollfree_verification ────────────────────────────────
|
|
1525
|
+
|
|
1526
|
+
test('sms_submit_tollfree_verification validates required fields', async () => {
|
|
1527
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1528
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1529
|
+
|
|
1530
|
+
const msg: TwilioConfigRequest = {
|
|
1531
|
+
type: 'twilio_config',
|
|
1532
|
+
action: 'sms_submit_tollfree_verification',
|
|
1533
|
+
verificationParams: {
|
|
1534
|
+
// Missing all required fields
|
|
1535
|
+
businessName: 'Test Biz',
|
|
1536
|
+
},
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
const { ctx, sent } = createTestContext();
|
|
1540
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1541
|
+
|
|
1542
|
+
expect(sent).toHaveLength(1);
|
|
1543
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1544
|
+
expect(res.success).toBe(false);
|
|
1545
|
+
expect(res.error).toContain('Missing required verification fields');
|
|
1546
|
+
expect(res.error).toContain('tollfreePhoneNumberSid');
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
test('sms_submit_tollfree_verification defaults businessType to SOLE_PROPRIETOR', async () => {
|
|
1550
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1551
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1552
|
+
|
|
1553
|
+
let capturedBody = '';
|
|
1554
|
+
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
1555
|
+
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
1556
|
+
if (urlStr.includes('Tollfree/Verifications') && init?.method === 'POST') {
|
|
1557
|
+
capturedBody = init?.body?.toString() ?? '';
|
|
1558
|
+
return new Response(JSON.stringify({
|
|
1559
|
+
sid: 'TF_VER_NEW',
|
|
1560
|
+
status: 'PENDING_REVIEW',
|
|
1561
|
+
}), { status: 201 });
|
|
1562
|
+
}
|
|
1563
|
+
return originalFetch(url);
|
|
1564
|
+
}) as typeof fetch;
|
|
1565
|
+
|
|
1566
|
+
const msg: TwilioConfigRequest = {
|
|
1567
|
+
type: 'twilio_config',
|
|
1568
|
+
action: 'sms_submit_tollfree_verification',
|
|
1569
|
+
verificationParams: {
|
|
1570
|
+
tollfreePhoneNumberSid: 'PN123',
|
|
1571
|
+
businessName: 'Test Biz',
|
|
1572
|
+
businessWebsite: 'https://test.com',
|
|
1573
|
+
notificationEmail: 'test@test.com',
|
|
1574
|
+
useCaseCategories: ['CUSTOMER_CARE'],
|
|
1575
|
+
useCaseSummary: 'Customer support messages',
|
|
1576
|
+
productionMessageSample: 'Your order has shipped!',
|
|
1577
|
+
optInImageUrls: ['https://test.com/optin.png'],
|
|
1578
|
+
optInType: 'WEB_FORM',
|
|
1579
|
+
messageVolume: '100',
|
|
1580
|
+
// businessType NOT provided — should default to SOLE_PROPRIETOR
|
|
1581
|
+
},
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
const { ctx, sent } = createTestContext();
|
|
1585
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1586
|
+
|
|
1587
|
+
expect(sent).toHaveLength(1);
|
|
1588
|
+
const res = sent[0] as { type: string; success: boolean; compliance?: Record<string, unknown> };
|
|
1589
|
+
expect(res.success).toBe(true);
|
|
1590
|
+
expect(res.compliance).toBeDefined();
|
|
1591
|
+
expect(res.compliance!.verificationSid).toBe('TF_VER_NEW');
|
|
1592
|
+
expect(res.compliance!.verificationStatus).toBe('PENDING_REVIEW');
|
|
1593
|
+
|
|
1594
|
+
// Verify the default businessType was sent to Twilio
|
|
1595
|
+
expect(capturedBody).toContain('BusinessType=SOLE_PROPRIETOR');
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
test('sms_submit_tollfree_verification rejects invalid useCaseCategories', async () => {
|
|
1599
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1600
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1601
|
+
|
|
1602
|
+
const msg: TwilioConfigRequest = {
|
|
1603
|
+
type: 'twilio_config',
|
|
1604
|
+
action: 'sms_submit_tollfree_verification',
|
|
1605
|
+
verificationParams: {
|
|
1606
|
+
tollfreePhoneNumberSid: 'PN123',
|
|
1607
|
+
businessName: 'Test Biz',
|
|
1608
|
+
businessWebsite: 'https://test.com',
|
|
1609
|
+
notificationEmail: 'test@test.com',
|
|
1610
|
+
useCaseCategories: ['INVALID_CATEGORY'],
|
|
1611
|
+
useCaseSummary: 'Test',
|
|
1612
|
+
productionMessageSample: 'Test message',
|
|
1613
|
+
optInImageUrls: ['https://test.com/optin.png'],
|
|
1614
|
+
optInType: 'WEB_FORM',
|
|
1615
|
+
messageVolume: '100',
|
|
1616
|
+
},
|
|
1617
|
+
};
|
|
1618
|
+
|
|
1619
|
+
const { ctx, sent } = createTestContext();
|
|
1620
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1621
|
+
|
|
1622
|
+
expect(sent).toHaveLength(1);
|
|
1623
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1624
|
+
expect(res.success).toBe(false);
|
|
1625
|
+
expect(res.error).toContain('Invalid useCaseCategories');
|
|
1626
|
+
expect(res.error).toContain('INVALID_CATEGORY');
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
test('sms_submit_tollfree_verification returns error without verificationParams', async () => {
|
|
1630
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1631
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1632
|
+
|
|
1633
|
+
const msg: TwilioConfigRequest = {
|
|
1634
|
+
type: 'twilio_config',
|
|
1635
|
+
action: 'sms_submit_tollfree_verification',
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1638
|
+
const { ctx, sent } = createTestContext();
|
|
1639
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1640
|
+
|
|
1641
|
+
expect(sent).toHaveLength(1);
|
|
1642
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1643
|
+
expect(res.success).toBe(false);
|
|
1644
|
+
expect(res.error).toContain('verificationParams is required');
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
// ── release_number ─────────────────────────────────────────────────
|
|
1648
|
+
|
|
1649
|
+
test('release_number clears config and secure keys', async () => {
|
|
1650
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1651
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1652
|
+
secureKeyStore['credential:twilio:phone_number'] = '+15551234567';
|
|
1653
|
+
rawConfigStore = {
|
|
1654
|
+
sms: {
|
|
1655
|
+
phoneNumber: '+15551234567',
|
|
1656
|
+
assistantPhoneNumbers: { 'ast-alpha': '+15551234567' },
|
|
1657
|
+
},
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
1661
|
+
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
1662
|
+
// Phone number SID lookup
|
|
1663
|
+
if (urlStr.includes('IncomingPhoneNumbers.json') && urlStr.includes('PhoneNumber=')) {
|
|
1664
|
+
return new Response(JSON.stringify({
|
|
1665
|
+
incoming_phone_numbers: [{ sid: 'PN123abc', phone_number: '+15551234567' }],
|
|
1666
|
+
}), { status: 200 });
|
|
1667
|
+
}
|
|
1668
|
+
// Phone number deletion
|
|
1669
|
+
if (urlStr.includes('IncomingPhoneNumbers/PN123abc.json') && init?.method === 'DELETE') {
|
|
1670
|
+
return new Response('', { status: 204 });
|
|
1671
|
+
}
|
|
1672
|
+
return originalFetch(url);
|
|
1673
|
+
}) as typeof fetch;
|
|
1674
|
+
|
|
1675
|
+
const msg: TwilioConfigRequest = {
|
|
1676
|
+
type: 'twilio_config',
|
|
1677
|
+
action: 'release_number',
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
const { ctx, sent } = createTestContext();
|
|
1681
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1682
|
+
|
|
1683
|
+
expect(sent).toHaveLength(1);
|
|
1684
|
+
const res = sent[0] as { type: string; success: boolean; warning?: string };
|
|
1685
|
+
expect(res.success).toBe(true);
|
|
1686
|
+
expect(res.warning).toContain('Phone number released');
|
|
1687
|
+
|
|
1688
|
+
// Verify config was cleared
|
|
1689
|
+
const sms = rawConfigStore.sms as Record<string, unknown>;
|
|
1690
|
+
expect(sms.phoneNumber).toBeUndefined();
|
|
1691
|
+
expect(sms.assistantPhoneNumbers).toBeUndefined();
|
|
1692
|
+
|
|
1693
|
+
// Verify secure key was cleared
|
|
1694
|
+
expect(secureKeyStore['credential:twilio:phone_number']).toBeUndefined();
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
test('release_number returns error when no credentials', async () => {
|
|
1698
|
+
rawConfigStore = { sms: { phoneNumber: '+15551234567' } };
|
|
1699
|
+
|
|
1700
|
+
const msg: TwilioConfigRequest = {
|
|
1701
|
+
type: 'twilio_config',
|
|
1702
|
+
action: 'release_number',
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
const { ctx, sent } = createTestContext();
|
|
1706
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1707
|
+
|
|
1708
|
+
expect(sent).toHaveLength(1);
|
|
1709
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1710
|
+
expect(res.success).toBe(false);
|
|
1711
|
+
expect(res.error).toContain('Twilio credentials not configured');
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
test('release_number returns error when no phone number assigned', async () => {
|
|
1715
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1716
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1717
|
+
rawConfigStore = {};
|
|
1718
|
+
|
|
1719
|
+
const msg: TwilioConfigRequest = {
|
|
1720
|
+
type: 'twilio_config',
|
|
1721
|
+
action: 'release_number',
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
const { ctx, sent } = createTestContext();
|
|
1725
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1726
|
+
|
|
1727
|
+
expect(sent).toHaveLength(1);
|
|
1728
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1729
|
+
expect(res.success).toBe(false);
|
|
1730
|
+
expect(res.error).toContain('No phone number to release');
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
// ── sms_delete_tollfree_verification ────────────────────────────────
|
|
1734
|
+
|
|
1735
|
+
test('sms_delete_tollfree_verification requires verificationSid', async () => {
|
|
1736
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1737
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1738
|
+
|
|
1739
|
+
const msg: TwilioConfigRequest = {
|
|
1740
|
+
type: 'twilio_config',
|
|
1741
|
+
action: 'sms_delete_tollfree_verification',
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
const { ctx, sent } = createTestContext();
|
|
1745
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1746
|
+
|
|
1747
|
+
expect(sent).toHaveLength(1);
|
|
1748
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1749
|
+
expect(res.success).toBe(false);
|
|
1750
|
+
expect(res.error).toContain('verificationSid is required');
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
test('sms_delete_tollfree_verification includes queue warning', async () => {
|
|
1754
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1755
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1756
|
+
|
|
1757
|
+
globalThis.fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
1758
|
+
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
1759
|
+
if (urlStr.includes('Tollfree/Verifications/TF_VER_001') && init?.method === 'DELETE') {
|
|
1760
|
+
return new Response('', { status: 204 });
|
|
1761
|
+
}
|
|
1762
|
+
return originalFetch(url);
|
|
1763
|
+
}) as typeof fetch;
|
|
1764
|
+
|
|
1765
|
+
const msg: TwilioConfigRequest = {
|
|
1766
|
+
type: 'twilio_config',
|
|
1767
|
+
action: 'sms_delete_tollfree_verification',
|
|
1768
|
+
verificationSid: 'TF_VER_001',
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
const { ctx, sent } = createTestContext();
|
|
1772
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1773
|
+
|
|
1774
|
+
expect(sent).toHaveLength(1);
|
|
1775
|
+
const res = sent[0] as { type: string; success: boolean; warning?: string };
|
|
1776
|
+
expect(res.success).toBe(true);
|
|
1777
|
+
expect(res.warning).toContain('review queue');
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
// ── sms_update_tollfree_verification ────────────────────────────────
|
|
1781
|
+
|
|
1782
|
+
test('sms_update_tollfree_verification requires verificationSid', async () => {
|
|
1783
|
+
secureKeyStore['credential:twilio:account_sid'] = 'AC1234567890abcdef1234567890abcdef';
|
|
1784
|
+
secureKeyStore['credential:twilio:auth_token'] = 'test_auth_token';
|
|
1785
|
+
|
|
1786
|
+
const msg: TwilioConfigRequest = {
|
|
1787
|
+
type: 'twilio_config',
|
|
1788
|
+
action: 'sms_update_tollfree_verification',
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1791
|
+
const { ctx, sent } = createTestContext();
|
|
1792
|
+
await handleTwilioConfig(msg, {} as net.Socket, ctx);
|
|
1793
|
+
|
|
1794
|
+
expect(sent).toHaveLength(1);
|
|
1795
|
+
const res = sent[0] as { type: string; success: boolean; error?: string };
|
|
1796
|
+
expect(res.success).toBe(false);
|
|
1797
|
+
expect(res.error).toContain('verificationSid is required');
|
|
1798
|
+
});
|
|
1319
1799
|
});
|
|
@@ -382,6 +382,12 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
382
382
|
type: 'twilio_config',
|
|
383
383
|
action: 'get',
|
|
384
384
|
},
|
|
385
|
+
channel_readiness: {
|
|
386
|
+
type: 'channel_readiness',
|
|
387
|
+
action: 'get',
|
|
388
|
+
channel: 'sms',
|
|
389
|
+
includeRemote: true,
|
|
390
|
+
},
|
|
385
391
|
guardian_verification: {
|
|
386
392
|
type: 'guardian_verification',
|
|
387
393
|
action: 'create_challenge',
|
|
@@ -564,6 +570,17 @@ const clientMessages: Record<ClientMessageType, ClientMessage> = {
|
|
|
564
570
|
tool_names_list: {
|
|
565
571
|
type: 'tool_names_list',
|
|
566
572
|
},
|
|
573
|
+
dictation_request: {
|
|
574
|
+
type: 'dictation_request',
|
|
575
|
+
transcription: 'Hello world',
|
|
576
|
+
context: {
|
|
577
|
+
bundleIdentifier: 'com.example.app',
|
|
578
|
+
appName: 'Example App',
|
|
579
|
+
windowTitle: 'Main Window',
|
|
580
|
+
selectedText: 'some selected text',
|
|
581
|
+
cursorInTextField: true,
|
|
582
|
+
},
|
|
583
|
+
},
|
|
567
584
|
};
|
|
568
585
|
|
|
569
586
|
// ---------------------------------------------------------------------------
|
|
@@ -824,6 +841,11 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
824
841
|
sessionId: 'sess-routed-001',
|
|
825
842
|
interactionType: 'computer_use',
|
|
826
843
|
},
|
|
844
|
+
ride_shotgun_progress: {
|
|
845
|
+
type: 'ride_shotgun_progress',
|
|
846
|
+
watchId: 'watch-shotgun-001',
|
|
847
|
+
message: 'Observing user activity...',
|
|
848
|
+
},
|
|
827
849
|
ride_shotgun_result: {
|
|
828
850
|
type: 'ride_shotgun_result',
|
|
829
851
|
sessionId: 'sess-shotgun-001',
|
|
@@ -1227,6 +1249,41 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
1227
1249
|
success: true,
|
|
1228
1250
|
hasCredentials: true,
|
|
1229
1251
|
phoneNumber: '+15551234567',
|
|
1252
|
+
compliance: {
|
|
1253
|
+
numberType: 'toll_free',
|
|
1254
|
+
verificationSid: 'TF_VER_001',
|
|
1255
|
+
verificationStatus: 'TWILIO_APPROVED',
|
|
1256
|
+
},
|
|
1257
|
+
testResult: {
|
|
1258
|
+
messageSid: 'SM-test-001',
|
|
1259
|
+
to: '+15559876543',
|
|
1260
|
+
initialStatus: 'queued',
|
|
1261
|
+
finalStatus: 'delivered',
|
|
1262
|
+
},
|
|
1263
|
+
diagnostics: {
|
|
1264
|
+
readiness: { ready: true, issues: [] },
|
|
1265
|
+
compliance: { status: 'TWILIO_APPROVED', detail: 'Toll-free verification: TWILIO_APPROVED' },
|
|
1266
|
+
overallStatus: 'healthy',
|
|
1267
|
+
actionItems: [],
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
channel_readiness_response: {
|
|
1271
|
+
type: 'channel_readiness_response',
|
|
1272
|
+
success: true,
|
|
1273
|
+
snapshots: [
|
|
1274
|
+
{
|
|
1275
|
+
channel: 'sms',
|
|
1276
|
+
ready: false,
|
|
1277
|
+
checkedAt: 1700000000000,
|
|
1278
|
+
stale: false,
|
|
1279
|
+
reasons: [{ code: 'twilio_credentials', text: 'Twilio credentials are not configured' }],
|
|
1280
|
+
localChecks: [
|
|
1281
|
+
{ name: 'twilio_credentials', passed: false, message: 'Twilio credentials are not configured' },
|
|
1282
|
+
{ name: 'phone_number', passed: true, message: 'Phone number is assigned' },
|
|
1283
|
+
{ name: 'ingress', passed: true, message: 'Public ingress URL is configured' },
|
|
1284
|
+
],
|
|
1285
|
+
},
|
|
1286
|
+
],
|
|
1230
1287
|
},
|
|
1231
1288
|
guardian_verification_response: {
|
|
1232
1289
|
type: 'guardian_verification_response',
|
|
@@ -1608,6 +1665,12 @@ const serverMessages: Record<ServerMessageType, ServerMessage> = {
|
|
|
1608
1665
|
type: 'tool_names_list_response',
|
|
1609
1666
|
names: ['bash', 'file_read', 'file_write'],
|
|
1610
1667
|
},
|
|
1668
|
+
dictation_response: {
|
|
1669
|
+
type: 'dictation_response',
|
|
1670
|
+
text: 'Hello world',
|
|
1671
|
+
mode: 'dictation',
|
|
1672
|
+
actionPlan: undefined,
|
|
1673
|
+
},
|
|
1611
1674
|
};
|
|
1612
1675
|
|
|
1613
1676
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import type { MessagingProvider } from '../messaging/provider.js';
|
|
3
|
+
import type { SendOptions } from '../messaging/provider-types.js';
|
|
4
|
+
|
|
5
|
+
const sendMessageMock = mock(async (..._args: unknown[]) => ({
|
|
6
|
+
id: 'msg-1',
|
|
7
|
+
timestamp: 123,
|
|
8
|
+
conversationId: 'conv-1',
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const provider: MessagingProvider = {
|
|
12
|
+
id: 'sms',
|
|
13
|
+
displayName: 'SMS',
|
|
14
|
+
credentialService: 'twilio',
|
|
15
|
+
capabilities: new Set(['send']),
|
|
16
|
+
testConnection: async () => ({ connected: true, user: 'x', platform: 'sms' }),
|
|
17
|
+
listConversations: async () => [],
|
|
18
|
+
getHistory: async () => [],
|
|
19
|
+
search: async () => ({ total: 0, messages: [], hasMore: false }),
|
|
20
|
+
sendMessage: (token: string, conversationId: string, text: string, options?: SendOptions) =>
|
|
21
|
+
sendMessageMock(token, conversationId, text, options),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
mock.module('../config/bundled-skills/messaging/tools/shared.js', () => ({
|
|
25
|
+
resolveProvider: () => provider,
|
|
26
|
+
withProviderToken: async (_provider: MessagingProvider, fn: (token: string) => Promise<unknown>) => fn('provider-token'),
|
|
27
|
+
ok: (content: string) => ({ content, isError: false }),
|
|
28
|
+
err: (content: string) => ({ content, isError: true }),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
import { run } from '../config/bundled-skills/messaging/tools/messaging-send.js';
|
|
32
|
+
|
|
33
|
+
describe('messaging-send tool', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
sendMessageMock.mockClear();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('passes assistantId from tool context to provider send options', async () => {
|
|
39
|
+
const result = await run(
|
|
40
|
+
{
|
|
41
|
+
platform: 'sms',
|
|
42
|
+
conversation_id: '+15550004444',
|
|
43
|
+
text: 'test message',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
workingDir: '/tmp',
|
|
47
|
+
sessionId: 'sess-1',
|
|
48
|
+
conversationId: 'conv-1',
|
|
49
|
+
assistantId: 'ast-alpha',
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(result.isError).toBe(false);
|
|
54
|
+
expect(sendMessageMock).toHaveBeenCalledWith(
|
|
55
|
+
'provider-token',
|
|
56
|
+
'+15550004444',
|
|
57
|
+
'test message',
|
|
58
|
+
{
|
|
59
|
+
subject: undefined,
|
|
60
|
+
inReplyTo: undefined,
|
|
61
|
+
assistantId: 'ast-alpha',
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -68,6 +68,8 @@ function makeSessionEmittingViaClient(...messages: ServerMessage[]): Session {
|
|
|
68
68
|
persistUserMessage: () => undefined as unknown as string,
|
|
69
69
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
70
70
|
setChannelCapabilities: () => {},
|
|
71
|
+
setAssistantId: () => {},
|
|
72
|
+
setGuardianContext: () => {},
|
|
71
73
|
updateClient: (handler: (msg: ServerMessage) => void) => {
|
|
72
74
|
clientHandler = handler;
|
|
73
75
|
},
|
|
@@ -90,6 +92,8 @@ function makeSessionEmittingViaAgentLoop(...messages: ServerMessage[]): Session
|
|
|
90
92
|
persistUserMessage: () => undefined as unknown as string,
|
|
91
93
|
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
92
94
|
setChannelCapabilities: () => {},
|
|
95
|
+
setAssistantId: () => {},
|
|
96
|
+
setGuardianContext: () => {},
|
|
93
97
|
updateClient: () => {},
|
|
94
98
|
runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
|
|
95
99
|
for (const msg of messages) {
|