@vellumai/assistant 0.3.27 → 0.4.0
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 +81 -4
- package/Dockerfile +2 -2
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +9 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +21 -19
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +1092 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +212 -36
- package/src/__tests__/notification-decision-fallback.test.ts +63 -3
- package/src/__tests__/notification-decision-strategy.test.ts +78 -0
- package/src/__tests__/notification-guardian-path.test.ts +15 -15
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +126 -59
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +358 -24
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +149 -21
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +22 -16
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +33 -6
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +68 -326
- package/src/daemon/session-runtime-assembly.ts +119 -25
- package/src/daemon/session-tool-setup.ts +3 -2
- package/src/daemon/session.ts +4 -3
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +586 -0
- package/src/memory/channel-guardian-store.ts +2 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +56 -0
- package/src/notifications/copy-composer.ts +31 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +173 -0
- package/src/runtime/actor-trust-resolver.ts +221 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -71
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +717 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +20 -2
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +205 -529
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +53 -10
- package/src/tools/types.ts +13 -2
- package/src/util/bundled-asset.ts +31 -0
- package/src/util/canonicalize-identity.ts +52 -0
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
|
@@ -42,6 +42,8 @@ mock.module('../memory/channel-guardian-store.js', () => ({
|
|
|
42
42
|
|
|
43
43
|
mock.module('../config/loader.js', () => ({
|
|
44
44
|
getConfig: () => ({
|
|
45
|
+
ui: {},
|
|
46
|
+
|
|
45
47
|
calls: {
|
|
46
48
|
userConsultTimeoutSeconds: 120,
|
|
47
49
|
},
|
|
@@ -103,6 +105,8 @@ function ensureConversation(id: string): void {
|
|
|
103
105
|
|
|
104
106
|
function resetTables(): void {
|
|
105
107
|
const db = getDb();
|
|
108
|
+
db.run('DELETE FROM canonical_guardian_deliveries');
|
|
109
|
+
db.run('DELETE FROM canonical_guardian_requests');
|
|
106
110
|
db.run('DELETE FROM guardian_action_deliveries');
|
|
107
111
|
db.run('DELETE FROM guardian_action_requests');
|
|
108
112
|
db.run('DELETE FROM call_pending_questions');
|
|
@@ -160,7 +164,7 @@ describe('guardian-dispatch', () => {
|
|
|
160
164
|
|
|
161
165
|
const db = getDb();
|
|
162
166
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
163
|
-
const request = raw.query('SELECT * FROM
|
|
167
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
164
168
|
| { id: string; status: string; question_text: string }
|
|
165
169
|
| undefined;
|
|
166
170
|
expect(request).toBeDefined();
|
|
@@ -168,7 +172,7 @@ describe('guardian-dispatch', () => {
|
|
|
168
172
|
expect(request!.question_text).toBe('What is the gate code?');
|
|
169
173
|
|
|
170
174
|
const vellumDelivery = raw.query(
|
|
171
|
-
'SELECT * FROM
|
|
175
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
|
|
172
176
|
).get(request!.id, 'vellum') as { status: string; destination_conversation_id: string | null } | undefined;
|
|
173
177
|
expect(vellumDelivery).toBeDefined();
|
|
174
178
|
expect(vellumDelivery!.status).toBe('sent');
|
|
@@ -224,18 +228,17 @@ describe('guardian-dispatch', () => {
|
|
|
224
228
|
|
|
225
229
|
const db = getDb();
|
|
226
230
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
227
|
-
const request = raw.query('SELECT * FROM
|
|
231
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
228
232
|
| { id: string }
|
|
229
233
|
| undefined;
|
|
230
234
|
const telegramDelivery = raw.query(
|
|
231
|
-
'SELECT * FROM
|
|
235
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
|
|
232
236
|
).get(request!.id, 'telegram') as
|
|
233
|
-
| { status: string; destination_chat_id: string | null
|
|
237
|
+
| { status: string; destination_chat_id: string | null }
|
|
234
238
|
| undefined;
|
|
235
239
|
expect(telegramDelivery).toBeDefined();
|
|
236
240
|
expect(telegramDelivery!.status).toBe('sent');
|
|
237
241
|
expect(telegramDelivery!.destination_chat_id).toBe('tg-chat-999');
|
|
238
|
-
expect(telegramDelivery!.destination_external_user_id).toBe('tg-user-888');
|
|
239
242
|
});
|
|
240
243
|
|
|
241
244
|
test('marks non-sent pipeline delivery results as failed', async () => {
|
|
@@ -275,15 +278,14 @@ describe('guardian-dispatch', () => {
|
|
|
275
278
|
|
|
276
279
|
const db = getDb();
|
|
277
280
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
278
|
-
const request = raw.query('SELECT * FROM
|
|
281
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
279
282
|
| { id: string }
|
|
280
283
|
| undefined;
|
|
281
284
|
const vellumDelivery = raw.query(
|
|
282
|
-
'SELECT * FROM
|
|
283
|
-
).get(request!.id, 'vellum') as { status: string
|
|
285
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
|
|
286
|
+
).get(request!.id, 'vellum') as { status: string } | undefined;
|
|
284
287
|
expect(vellumDelivery).toBeDefined();
|
|
285
288
|
expect(vellumDelivery!.status).toBe('failed');
|
|
286
|
-
expect(vellumDelivery!.last_error).toContain('IPC unavailable');
|
|
287
289
|
});
|
|
288
290
|
|
|
289
291
|
test('uses onThreadCreated callback conversation when delivery result omits conversationId', async () => {
|
|
@@ -326,17 +328,17 @@ describe('guardian-dispatch', () => {
|
|
|
326
328
|
|
|
327
329
|
const db = getDb();
|
|
328
330
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
329
|
-
const request = raw.query('SELECT * FROM
|
|
331
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
330
332
|
| { id: string }
|
|
331
333
|
| undefined;
|
|
332
334
|
const vellumDelivery = raw.query(
|
|
333
|
-
'SELECT * FROM
|
|
335
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
|
|
334
336
|
).get(request!.id, 'vellum') as { destination_conversation_id: string | null } | undefined;
|
|
335
337
|
expect(vellumDelivery).toBeDefined();
|
|
336
338
|
expect(vellumDelivery!.destination_conversation_id).toBe('conv-from-thread-created');
|
|
337
339
|
});
|
|
338
340
|
|
|
339
|
-
test('persists toolName and inputDigest on guardian
|
|
341
|
+
test('persists toolName and inputDigest on canonical guardian request for tool-approval dispatches', async () => {
|
|
340
342
|
const convId = 'conv-dispatch-5';
|
|
341
343
|
ensureConversation(convId);
|
|
342
344
|
|
|
@@ -359,7 +361,7 @@ describe('guardian-dispatch', () => {
|
|
|
359
361
|
|
|
360
362
|
const db = getDb();
|
|
361
363
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
362
|
-
const request = raw.query('SELECT * FROM
|
|
364
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
363
365
|
| { id: string; tool_name: string | null; input_digest: string | null }
|
|
364
366
|
| undefined;
|
|
365
367
|
expect(request).toBeDefined();
|
|
@@ -388,7 +390,7 @@ describe('guardian-dispatch', () => {
|
|
|
388
390
|
|
|
389
391
|
const db = getDb();
|
|
390
392
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
391
|
-
const request = raw.query('SELECT * FROM
|
|
393
|
+
const request = raw.query('SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?').get(session.id) as
|
|
392
394
|
| { id: string; tool_name: string | null; input_digest: string | null }
|
|
393
395
|
| undefined;
|
|
394
396
|
expect(request).toBeDefined();
|
|
@@ -485,11 +487,11 @@ describe('guardian-dispatch', () => {
|
|
|
485
487
|
pendingQuestion: pq2,
|
|
486
488
|
});
|
|
487
489
|
|
|
488
|
-
// Both dispatches should have created separate
|
|
490
|
+
// Both dispatches should have created separate canonical requests
|
|
489
491
|
const db = getDb();
|
|
490
492
|
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
491
493
|
const requests = raw.query(
|
|
492
|
-
'SELECT * FROM
|
|
494
|
+
'SELECT * FROM canonical_guardian_requests WHERE call_session_id = ? ORDER BY created_at ASC',
|
|
493
495
|
).all(session.id) as Array<{ id: string; question_text: string }>;
|
|
494
496
|
expect(requests).toHaveLength(2);
|
|
495
497
|
expect(requests[0].question_text).toBe('What is the gate code?');
|
|
@@ -498,7 +500,7 @@ describe('guardian-dispatch', () => {
|
|
|
498
500
|
// Each request should have its own delivery row, both pointing to the shared conversation
|
|
499
501
|
for (const req of requests) {
|
|
500
502
|
const delivery = raw.query(
|
|
501
|
-
'SELECT * FROM
|
|
503
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE request_id = ? AND destination_channel = ?',
|
|
502
504
|
).get(req.id, 'vellum') as { status: string; destination_conversation_id: string | null } | undefined;
|
|
503
505
|
expect(delivery).toBeDefined();
|
|
504
506
|
expect(delivery!.status).toBe('sent');
|
|
@@ -507,7 +509,7 @@ describe('guardian-dispatch', () => {
|
|
|
507
509
|
|
|
508
510
|
// Total delivery rows should be 2 (one per request), not 1
|
|
509
511
|
const allDeliveries = raw.query(
|
|
510
|
-
'SELECT * FROM
|
|
512
|
+
'SELECT * FROM canonical_guardian_deliveries WHERE destination_conversation_id = ?',
|
|
511
513
|
).all(sharedConversationId) as Array<{ request_id: string }>;
|
|
512
514
|
expect(allDeliveries).toHaveLength(2);
|
|
513
515
|
|
|
@@ -137,7 +137,7 @@ function registerPendingInteraction(
|
|
|
137
137
|
|
|
138
138
|
function makeGuardianContext(): GuardianContext {
|
|
139
139
|
return {
|
|
140
|
-
|
|
140
|
+
trustClass: 'guardian',
|
|
141
141
|
denialReason: undefined,
|
|
142
142
|
};
|
|
143
143
|
}
|
|
@@ -530,3 +530,70 @@ describe('guardian grant minting on tool-approval decisions', () => {
|
|
|
530
530
|
composeSpy.mockRestore();
|
|
531
531
|
});
|
|
532
532
|
});
|
|
533
|
+
|
|
534
|
+
describe('approval interception trust-class regression coverage', () => {
|
|
535
|
+
let deliverSpy: ReturnType<typeof spyOn>;
|
|
536
|
+
let composeSpy: ReturnType<typeof spyOn>;
|
|
537
|
+
|
|
538
|
+
beforeEach(() => {
|
|
539
|
+
resetTables();
|
|
540
|
+
deliverSpy = spyOn(gatewayClient, 'deliverChannelReply').mockResolvedValue(undefined);
|
|
541
|
+
composeSpy = spyOn(approvalMessageComposer, 'composeApprovalMessageGenerative')
|
|
542
|
+
.mockResolvedValue('test message');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('identity-known unknown sender does not auto-deny pending approval', async () => {
|
|
546
|
+
const requestId = 'req-unknown-no-auto-deny-1';
|
|
547
|
+
const sessionMock = registerPendingInteraction(requestId, CONVERSATION_ID, TOOL_NAME, TOOL_INPUT);
|
|
548
|
+
createTestGuardianApproval(requestId);
|
|
549
|
+
|
|
550
|
+
const result = await handleApprovalInterception({
|
|
551
|
+
conversationId: CONVERSATION_ID,
|
|
552
|
+
content: 'approve',
|
|
553
|
+
externalChatId: REQUESTER_CHAT,
|
|
554
|
+
sourceChannel: 'telegram',
|
|
555
|
+
senderExternalUserId: 'intruder-user-1',
|
|
556
|
+
replyCallbackUrl: 'https://gateway.test/deliver',
|
|
557
|
+
guardianCtx: {
|
|
558
|
+
trustClass: 'unknown',
|
|
559
|
+
},
|
|
560
|
+
assistantId: ASSISTANT_ID,
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
expect(result.handled).toBe(true);
|
|
564
|
+
expect(result.type).toBe('assistant_turn');
|
|
565
|
+
expect(sessionMock).not.toHaveBeenCalled();
|
|
566
|
+
|
|
567
|
+
deliverSpy.mockRestore();
|
|
568
|
+
composeSpy.mockRestore();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test('legacy unverified sender still auto-denies pending approval', async () => {
|
|
572
|
+
const requestId = 'req-unknown-auto-deny-1';
|
|
573
|
+
const sessionMock = registerPendingInteraction(requestId, CONVERSATION_ID, TOOL_NAME, TOOL_INPUT);
|
|
574
|
+
createTestGuardianApproval(requestId);
|
|
575
|
+
|
|
576
|
+
const result = await handleApprovalInterception({
|
|
577
|
+
conversationId: CONVERSATION_ID,
|
|
578
|
+
content: 'approve',
|
|
579
|
+
externalChatId: REQUESTER_CHAT,
|
|
580
|
+
sourceChannel: 'telegram',
|
|
581
|
+
senderExternalUserId: undefined,
|
|
582
|
+
replyCallbackUrl: 'https://gateway.test/deliver',
|
|
583
|
+
guardianCtx: {
|
|
584
|
+
trustClass: 'unknown',
|
|
585
|
+
denialReason: 'no_identity',
|
|
586
|
+
},
|
|
587
|
+
assistantId: ASSISTANT_ID,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
expect(result.handled).toBe(true);
|
|
591
|
+
expect(result.type).toBe('decision_applied');
|
|
592
|
+
expect(sessionMock).toHaveBeenCalled();
|
|
593
|
+
expect(sessionMock.mock.calls[0]?.[0]).toBe(requestId);
|
|
594
|
+
expect(sessionMock.mock.calls[0]?.[1]).toBe('deny');
|
|
595
|
+
|
|
596
|
+
deliverSpy.mockRestore();
|
|
597
|
+
composeSpy.mockRestore();
|
|
598
|
+
});
|
|
599
|
+
});
|
|
@@ -364,15 +364,16 @@ describe('HTTP route: handleStartOutbound', () => {
|
|
|
364
364
|
const req = jsonRequest({ destination: '+15551234567' });
|
|
365
365
|
const resp = await handleStartOutbound(req);
|
|
366
366
|
expect(resp.status).toBe(400);
|
|
367
|
-
const body = await resp.json() as
|
|
368
|
-
expect(body.error).toBe('
|
|
367
|
+
const body = await resp.json() as { error: { message: string; code: string } };
|
|
368
|
+
expect(body.error.code).toBe('BAD_REQUEST');
|
|
369
|
+
expect(body.error.message).toContain('channel');
|
|
369
370
|
});
|
|
370
371
|
|
|
371
372
|
test('returns 400 for missing destination (SMS)', async () => {
|
|
372
373
|
const req = jsonRequest({ channel: 'sms' });
|
|
373
374
|
const resp = await handleStartOutbound(req);
|
|
374
375
|
expect(resp.status).toBe(400);
|
|
375
|
-
const body = await resp.json() as
|
|
376
|
+
const body = await resp.json() as { error?: string };
|
|
376
377
|
expect(body.error).toBe('missing_destination');
|
|
377
378
|
});
|
|
378
379
|
|
|
@@ -391,15 +392,16 @@ describe('HTTP route: handleResendOutbound', () => {
|
|
|
391
392
|
const req = jsonRequest({});
|
|
392
393
|
const resp = await handleResendOutbound(req);
|
|
393
394
|
expect(resp.status).toBe(400);
|
|
394
|
-
const body = await resp.json() as
|
|
395
|
-
expect(body.error).toBe('
|
|
395
|
+
const body = await resp.json() as { error: { message: string; code: string } };
|
|
396
|
+
expect(body.error.code).toBe('BAD_REQUEST');
|
|
397
|
+
expect(body.error.message).toContain('channel');
|
|
396
398
|
});
|
|
397
399
|
|
|
398
400
|
test('returns 400 for no_active_session', async () => {
|
|
399
401
|
const req = jsonRequest({ channel: 'sms', assistantId: 'resend-no-session' });
|
|
400
402
|
const resp = await handleResendOutbound(req);
|
|
401
403
|
expect(resp.status).toBe(400);
|
|
402
|
-
const body = await resp.json() as
|
|
404
|
+
const body = await resp.json() as { error?: string };
|
|
403
405
|
expect(body.error).toBe('no_active_session');
|
|
404
406
|
});
|
|
405
407
|
|
|
@@ -432,15 +434,16 @@ describe('HTTP route: handleCancelOutbound', () => {
|
|
|
432
434
|
const req = jsonRequest({});
|
|
433
435
|
const resp = await handleCancelOutbound(req);
|
|
434
436
|
expect(resp.status).toBe(400);
|
|
435
|
-
const body = await resp.json() as
|
|
436
|
-
expect(body.error).toBe('
|
|
437
|
+
const body = await resp.json() as { error: { message: string; code: string } };
|
|
438
|
+
expect(body.error.code).toBe('BAD_REQUEST');
|
|
439
|
+
expect(body.error.message).toContain('channel');
|
|
437
440
|
});
|
|
438
441
|
|
|
439
442
|
test('returns 400 for no_active_session', async () => {
|
|
440
443
|
const req = jsonRequest({ channel: 'sms', assistantId: 'cancel-no-session' });
|
|
441
444
|
const resp = await handleCancelOutbound(req);
|
|
442
445
|
expect(resp.status).toBe(400);
|
|
443
|
-
const body = await resp.json() as
|
|
446
|
+
const body = await resp.json() as { error?: string };
|
|
444
447
|
expect(body.error).toBe('no_active_session');
|
|
445
448
|
});
|
|
446
449
|
|