@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
|
@@ -61,6 +61,7 @@ mock.module('../tools/registry.js', () => ({
|
|
|
61
61
|
// Imports under test
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
|
|
64
|
+
import { DEFAULT_CONFIG } from '../config/defaults.js';
|
|
64
65
|
import { redactSensitiveFields } from '../security/redaction.js';
|
|
65
66
|
import { setSecureKey } from '../security/secure-keys.js';
|
|
66
67
|
import { CredentialBroker } from '../tools/credentials/broker.js';
|
|
@@ -125,7 +126,18 @@ describe('Invariant 1: secrets never enter LLM context', () => {
|
|
|
125
126
|
test('user message containing secret is blocked from entering history', () => {
|
|
126
127
|
// Mock config to enable block mode
|
|
127
128
|
mock.module('../config/loader.js', () => ({
|
|
129
|
+
applyNestedDefaults: (config: unknown) => config,
|
|
128
130
|
getConfig: () => ({
|
|
131
|
+
ui: {},
|
|
132
|
+
secretDetection: {
|
|
133
|
+
enabled: true,
|
|
134
|
+
action: 'block',
|
|
135
|
+
blockIngress: true,
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
invalidateConfigCache: () => {},
|
|
139
|
+
loadConfig: () => ({
|
|
140
|
+
ui: {},
|
|
129
141
|
secretDetection: {
|
|
130
142
|
enabled: true,
|
|
131
143
|
action: 'block',
|
|
@@ -204,6 +216,10 @@ describe('Invariant 2: no generic plaintext secret read API', () => {
|
|
|
204
216
|
'messaging/providers/telegram-bot/adapter.ts', // Telegram bot token lookup for connectivity check
|
|
205
217
|
'messaging/providers/sms/adapter.ts', // Twilio credential lookup for SMS connectivity check
|
|
206
218
|
'runtime/channel-readiness-service.ts', // channel readiness probes for SMS/Telegram connectivity
|
|
219
|
+
'messaging/providers/whatsapp/adapter.ts', // WhatsApp credential lookup for connectivity check
|
|
220
|
+
'schedule/integration-status.ts', // integration status checks for scheduled reports
|
|
221
|
+
'daemon/handlers/oauth-connect.ts', // OAuth connect handler for integration setup
|
|
222
|
+
'daemon/handlers/config-slack-channel.ts', // Slack channel config credential management
|
|
207
223
|
]);
|
|
208
224
|
|
|
209
225
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -436,20 +452,14 @@ describe('One-time send override', () => {
|
|
|
436
452
|
});
|
|
437
453
|
|
|
438
454
|
test('allowOneTimeSend defaults to false in config', () => {
|
|
439
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
440
|
-
const { DEFAULT_CONFIG } = require('../config/defaults.js');
|
|
441
455
|
expect(DEFAULT_CONFIG.secretDetection.allowOneTimeSend).toBe(false);
|
|
442
456
|
});
|
|
443
457
|
|
|
444
458
|
test('default secretDetection.action is redact', () => {
|
|
445
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
446
|
-
const { DEFAULT_CONFIG } = require('../config/defaults.js');
|
|
447
459
|
expect(DEFAULT_CONFIG.secretDetection.action).toBe('redact');
|
|
448
460
|
});
|
|
449
461
|
|
|
450
462
|
test('default secretDetection.blockIngress is true', () => {
|
|
451
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
452
|
-
const { DEFAULT_CONFIG } = require('../config/defaults.js');
|
|
453
463
|
expect(DEFAULT_CONFIG.secretDetection.blockIngress).toBe(true);
|
|
454
464
|
});
|
|
455
465
|
});
|
|
@@ -470,7 +470,7 @@ describe('credential_store tool — oauth2_connect error paths', () => {
|
|
|
470
470
|
client_id: 'test-client-id',
|
|
471
471
|
}, { ..._ctx, isInteractive: false });
|
|
472
472
|
expect(result.isError).toBe(true);
|
|
473
|
-
expect(result.content).toContain('interactive
|
|
473
|
+
expect(result.content).toContain('non-interactive session');
|
|
474
474
|
});
|
|
475
475
|
|
|
476
476
|
test('resolves gmail alias to integration:gmail', async () => {
|
|
@@ -672,7 +672,7 @@ describe('credential_store tool — tool definition', () => {
|
|
|
672
672
|
expect(schema.required).toContain('action');
|
|
673
673
|
const props = schema.properties as Record<string, Record<string, unknown>>;
|
|
674
674
|
expect(props.action.enum).toEqual(
|
|
675
|
-
['store', 'list', 'delete', 'prompt', 'oauth2_connect'],
|
|
675
|
+
['store', 'list', 'delete', 'prompt', 'oauth2_connect', 'describe'],
|
|
676
676
|
);
|
|
677
677
|
});
|
|
678
678
|
|
|
@@ -406,18 +406,19 @@ describe('credential_store tool', () => {
|
|
|
406
406
|
expect(entries[0].service).toBe('svc-b');
|
|
407
407
|
});
|
|
408
408
|
|
|
409
|
-
test('
|
|
409
|
+
test('recovers from corrupt secure storage by resetting and returning empty list', async () => {
|
|
410
410
|
// Store a credential so metadata exists
|
|
411
411
|
await credentialStoreTool.execute({
|
|
412
412
|
action: 'store', service: 'svc-x', field: 'key', value: 'val-x',
|
|
413
413
|
}, _ctx);
|
|
414
414
|
|
|
415
|
-
// Corrupt the encrypted store file
|
|
415
|
+
// Corrupt the encrypted store file — the store auto-recovers by
|
|
416
|
+
// backing up the corrupt file and creating a fresh store
|
|
416
417
|
writeFileSync(STORE_PATH, 'not-valid-json!!!', 'utf-8');
|
|
417
418
|
|
|
418
419
|
const result = await credentialStoreTool.execute({ action: 'list' }, _ctx);
|
|
419
|
-
|
|
420
|
-
expect(result.
|
|
420
|
+
// Store auto-recovers: list succeeds but the corrupted credentials are lost
|
|
421
|
+
expect(result.isError).toBe(false);
|
|
421
422
|
});
|
|
422
423
|
});
|
|
423
424
|
|
|
@@ -94,6 +94,15 @@ const conversation = {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
mock.module('../memory/conversation-store.js', () => ({
|
|
97
|
+
setConversationOriginChannelIfUnset: () => {},
|
|
98
|
+
updateConversationContextWindow: () => {},
|
|
99
|
+
deleteMessageById: () => {},
|
|
100
|
+
updateConversationTitle: () => {},
|
|
101
|
+
updateConversationUsage: () => {},
|
|
102
|
+
addMessage: () => ({ id: 'mock-msg-id' }),
|
|
103
|
+
provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
|
|
104
|
+
getConversationOriginInterface: () => null,
|
|
105
|
+
getConversationOriginChannel: () => null,
|
|
97
106
|
getLatestConversation: () => conversation,
|
|
98
107
|
createConversation: () => conversation,
|
|
99
108
|
getConversation: (id: string) => (id === conversation.id ? conversation : null),
|
|
@@ -203,6 +203,8 @@ mock.module('../providers/ratelimit.js', () => ({
|
|
|
203
203
|
|
|
204
204
|
mock.module('../config/loader.js', () => ({
|
|
205
205
|
getConfig: () => ({
|
|
206
|
+
ui: {},
|
|
207
|
+
|
|
206
208
|
provider: 'mock-provider',
|
|
207
209
|
providerOrder: ['mock-provider'],
|
|
208
210
|
maxTokens: 4096,
|
|
@@ -243,7 +245,31 @@ mock.module('../memory/external-conversation-store.js', () => ({
|
|
|
243
245
|
getBindingsForConversations: () => new Map(),
|
|
244
246
|
}));
|
|
245
247
|
|
|
248
|
+
mock.module('../memory/conversation-attention-store.js', () => ({
|
|
249
|
+
getAttentionStateByConversationIds: () => new Map(),
|
|
250
|
+
recordAttentionSignal: () => {},
|
|
251
|
+
recordConversationSeenSignal: () => {},
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
mock.module('../memory/canonical-guardian-store.js', () => ({
|
|
255
|
+
generateCanonicalRequestCode: () => 'mock-code-0000',
|
|
256
|
+
createCanonicalGuardianRequest: () => ({ requestCode: 'mock-code-0000', status: 'pending' }),
|
|
257
|
+
submitCanonicalRequest: () => ({ requestCode: 'mock-code-0000', status: 'pending' }),
|
|
258
|
+
getCanonicalRequest: () => null,
|
|
259
|
+
resolveCanonicalRequest: () => false,
|
|
260
|
+
listPendingCanonicalRequests: () => [],
|
|
261
|
+
}));
|
|
262
|
+
|
|
246
263
|
mock.module('../memory/conversation-store.js', () => ({
|
|
264
|
+
setConversationOriginChannelIfUnset: () => {},
|
|
265
|
+
updateConversationContextWindow: () => {},
|
|
266
|
+
deleteMessageById: () => {},
|
|
267
|
+
updateConversationTitle: () => {},
|
|
268
|
+
updateConversationUsage: () => {},
|
|
269
|
+
addMessage: () => ({ id: 'mock-msg-id' }),
|
|
270
|
+
provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
|
|
271
|
+
getConversationOriginInterface: () => null,
|
|
272
|
+
getConversationOriginChannel: () => null,
|
|
247
273
|
getLatestConversation: () => conversation,
|
|
248
274
|
createConversation: (titleOrOpts?: string | { title?: string; threadType?: string }) => {
|
|
249
275
|
lastCreateConversationArgs = titleOrOpts;
|
|
@@ -266,6 +292,7 @@ mock.module('../memory/conversation-store.js', () => ({
|
|
|
266
292
|
getMessages: () => [],
|
|
267
293
|
listConversations: () => [conversation],
|
|
268
294
|
countConversations: () => 1,
|
|
295
|
+
getDisplayMetaForConversations: () => new Map(),
|
|
269
296
|
}));
|
|
270
297
|
|
|
271
298
|
mock.module('../daemon/session.js', () => ({
|
|
@@ -7,6 +7,7 @@ const runDeterministicChecksMock = mock();
|
|
|
7
7
|
const createEventMock = mock();
|
|
8
8
|
const updateEventDedupeKeyMock = mock();
|
|
9
9
|
const dispatchDecisionMock = mock();
|
|
10
|
+
const activeBindingChannels = new Set<string>(['telegram']);
|
|
10
11
|
|
|
11
12
|
mock.module('../util/logger.js', () => ({
|
|
12
13
|
getLogger: () =>
|
|
@@ -21,7 +22,7 @@ mock.module('../channels/config.js', () => ({
|
|
|
21
22
|
|
|
22
23
|
mock.module('../memory/channel-guardian-store.js', () => ({
|
|
23
24
|
getActiveBinding: (_assistantId: string, channel: string) =>
|
|
24
|
-
channel
|
|
25
|
+
activeBindingChannels.has(channel)
|
|
25
26
|
? {
|
|
26
27
|
guardianDeliveryChatId: 'guardian-chat-123',
|
|
27
28
|
guardianExternalUserId: 'guardian-user-123',
|
|
@@ -83,6 +84,8 @@ describe('emitNotificationSignal routing intent re-persistence', () => {
|
|
|
83
84
|
createEventMock.mockReset();
|
|
84
85
|
updateEventDedupeKeyMock.mockReset();
|
|
85
86
|
dispatchDecisionMock.mockReset();
|
|
87
|
+
activeBindingChannels.clear();
|
|
88
|
+
activeBindingChannels.add('telegram');
|
|
86
89
|
|
|
87
90
|
createEventMock.mockReturnValue({ id: 'evt-1' });
|
|
88
91
|
runDeterministicChecksMock.mockResolvedValue({ passed: true });
|
|
@@ -176,4 +179,43 @@ describe('emitNotificationSignal routing intent re-persistence', () => {
|
|
|
176
179
|
|
|
177
180
|
expect(updateDecisionMock).not.toHaveBeenCalled();
|
|
178
181
|
});
|
|
182
|
+
|
|
183
|
+
test('excludes unverified binding channels from connected channel candidates', async () => {
|
|
184
|
+
activeBindingChannels.clear();
|
|
185
|
+
|
|
186
|
+
const decision = {
|
|
187
|
+
shouldNotify: true,
|
|
188
|
+
selectedChannels: ['vellum'],
|
|
189
|
+
reasoningSummary: 'Local only',
|
|
190
|
+
renderedCopy: {
|
|
191
|
+
vellum: { title: 'Reminder', body: 'Check this' },
|
|
192
|
+
},
|
|
193
|
+
dedupeKey: 'dedupe-rem-3',
|
|
194
|
+
confidence: 0.8,
|
|
195
|
+
fallbackUsed: false,
|
|
196
|
+
persistedDecisionId: 'dec-3',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
evaluateSignalMock.mockResolvedValue(decision);
|
|
200
|
+
enforceRoutingIntentMock.mockImplementation((inputDecision: unknown) => inputDecision);
|
|
201
|
+
|
|
202
|
+
await emitNotificationSignal({
|
|
203
|
+
sourceEventName: 'reminder.fired',
|
|
204
|
+
sourceChannel: 'scheduler',
|
|
205
|
+
sourceSessionId: 'rem-3',
|
|
206
|
+
attentionHints: {
|
|
207
|
+
requiresAction: false,
|
|
208
|
+
urgency: 'medium',
|
|
209
|
+
isAsyncBackground: false,
|
|
210
|
+
visibleInSourceNow: false,
|
|
211
|
+
},
|
|
212
|
+
contextPayload: { reminderId: 'rem-3' },
|
|
213
|
+
routingIntent: 'single_channel',
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(evaluateSignalMock).toHaveBeenCalled();
|
|
217
|
+
const callArgs = evaluateSignalMock.mock.calls[0];
|
|
218
|
+
expect(callArgs).toBeDefined();
|
|
219
|
+
expect(callArgs?.[1]).toEqual(['vellum']);
|
|
220
|
+
});
|
|
179
221
|
});
|
|
@@ -253,24 +253,29 @@ describe('encrypted-store', () => {
|
|
|
253
253
|
expect(getKey('test')).toBe('value');
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
-
test('setKey
|
|
256
|
+
test('setKey recovers from a corrupt store file by backing up and creating fresh store', () => {
|
|
257
257
|
// Write a valid store first
|
|
258
258
|
setKey('existing', 'old-secret');
|
|
259
259
|
// Corrupt the store
|
|
260
260
|
writeFileSync(STORE_PATH, 'corrupted data');
|
|
261
|
-
// setKey should
|
|
261
|
+
// setKey should recover by backing up corrupt file and creating fresh store
|
|
262
262
|
const result = setKey('new-key', 'new-value');
|
|
263
|
-
expect(result).toBe(
|
|
263
|
+
expect(result).toBe(true);
|
|
264
|
+
// Old key is lost but new key works
|
|
265
|
+
expect(getKey('new-key')).toBe('new-value');
|
|
266
|
+
expect(getKey('existing')).toBeUndefined();
|
|
264
267
|
});
|
|
265
268
|
|
|
266
|
-
test('setKey
|
|
269
|
+
test('setKey recovers from a store with invalid version', () => {
|
|
267
270
|
writeFileSync(STORE_PATH, JSON.stringify({
|
|
268
271
|
version: 99,
|
|
269
272
|
salt: 'abc',
|
|
270
273
|
entries: {},
|
|
271
274
|
}));
|
|
275
|
+
// setKey should recover by backing up invalid store and creating fresh store
|
|
272
276
|
const result = setKey('test', 'value');
|
|
273
|
-
expect(result).toBe(
|
|
277
|
+
expect(result).toBe(true);
|
|
278
|
+
expect(getKey('test')).toBe('value');
|
|
274
279
|
});
|
|
275
280
|
|
|
276
281
|
test('writeStore enforces 0600 permissions on existing files', () => {
|
|
@@ -243,9 +243,9 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
243
243
|
body: makeFormBody({ CallSid: 'CA123', AccountSid: 'AC_test' }),
|
|
244
244
|
});
|
|
245
245
|
expect(res.status).toBe(410);
|
|
246
|
-
const body = await res.json() as { error: string;
|
|
247
|
-
expect(body.code).toBe('
|
|
248
|
-
expect(body.error).toContain('Direct webhook access disabled');
|
|
246
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
247
|
+
expect(body.error.code).toBe('GONE');
|
|
248
|
+
expect(body.error.message).toContain('Direct webhook access disabled');
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
test('POST /webhooks/twilio/status returns 410', async () => {
|
|
@@ -255,8 +255,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
255
255
|
body: makeFormBody({ CallSid: 'CA123', CallStatus: 'completed' }),
|
|
256
256
|
});
|
|
257
257
|
expect(res.status).toBe(410);
|
|
258
|
-
const body = await res.json() as { error: string;
|
|
259
|
-
expect(body.code).toBe('
|
|
258
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
259
|
+
expect(body.error.code).toBe('GONE');
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
test('POST /webhooks/twilio/connect-action returns 410', async () => {
|
|
@@ -266,8 +266,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
266
266
|
body: makeFormBody({ CallSid: 'CA123' }),
|
|
267
267
|
});
|
|
268
268
|
expect(res.status).toBe(410);
|
|
269
|
-
const body = await res.json() as { error: string;
|
|
270
|
-
expect(body.code).toBe('
|
|
269
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
270
|
+
expect(body.error.code).toBe('GONE');
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
test('POST /v1/calls/twilio/voice-webhook returns 410', async () => {
|
|
@@ -277,8 +277,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
277
277
|
body: makeFormBody({ CallSid: 'CA123' }),
|
|
278
278
|
});
|
|
279
279
|
expect(res.status).toBe(410);
|
|
280
|
-
const body = await res.json() as { error: string;
|
|
281
|
-
expect(body.code).toBe('
|
|
280
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
281
|
+
expect(body.error.code).toBe('GONE');
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
test('POST /v1/calls/twilio/status returns 410', async () => {
|
|
@@ -288,8 +288,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
288
288
|
body: makeFormBody({ CallSid: 'CA123', CallStatus: 'completed' }),
|
|
289
289
|
});
|
|
290
290
|
expect(res.status).toBe(410);
|
|
291
|
-
const body = await res.json() as { error: string;
|
|
292
|
-
expect(body.code).toBe('
|
|
291
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
292
|
+
expect(body.error.code).toBe('GONE');
|
|
293
293
|
});
|
|
294
294
|
});
|
|
295
295
|
|
|
@@ -304,9 +304,9 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
304
304
|
body: makeFormBody({ Body: 'hello', From: '+15551234567', To: '+15559876543', MessageSid: 'SM123' }),
|
|
305
305
|
});
|
|
306
306
|
expect(res.status).toBe(410);
|
|
307
|
-
const body = await res.json() as { error: string;
|
|
308
|
-
expect(body.code).toBe('
|
|
309
|
-
expect(body.error).toContain('Direct webhook access disabled');
|
|
307
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
308
|
+
expect(body.error.code).toBe('GONE');
|
|
309
|
+
expect(body.error.message).toContain('Direct webhook access disabled');
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
test('POST /v1/calls/twilio/sms returns 410 (legacy path also blocked)', async () => {
|
|
@@ -316,8 +316,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
316
316
|
body: makeFormBody({ Body: 'hello', From: '+15551234567', MessageSid: 'SM456' }),
|
|
317
317
|
});
|
|
318
318
|
expect(res.status).toBe(410);
|
|
319
|
-
const body = await res.json() as { error: string;
|
|
320
|
-
expect(body.code).toBe('
|
|
319
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
320
|
+
expect(body.error.code).toBe('GONE');
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
test('POST /webhooks/twilio/sms with valid auth still returns 410 (auth does not bypass gateway-only)', async () => {
|
|
@@ -331,8 +331,8 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
331
331
|
});
|
|
332
332
|
// The gateway-only guard runs before auth for Twilio webhook paths
|
|
333
333
|
expect(res.status).toBe(410);
|
|
334
|
-
const body = await res.json() as { error: string;
|
|
335
|
-
expect(body.code).toBe('
|
|
334
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
335
|
+
expect(body.error.code).toBe('GONE');
|
|
336
336
|
});
|
|
337
337
|
});
|
|
338
338
|
|
|
@@ -407,9 +407,9 @@ describe('gateway-only ingress enforcement', () => {
|
|
|
407
407
|
},
|
|
408
408
|
});
|
|
409
409
|
expect(res.status).toBe(403);
|
|
410
|
-
const body = await res.json() as { error: string;
|
|
411
|
-
expect(body.code).toBe('
|
|
412
|
-
expect(body.error).toContain('Direct relay access disabled');
|
|
410
|
+
const body = await res.json() as { error: { code: string; message: string } };
|
|
411
|
+
expect(body.error.code).toBe('FORBIDDEN');
|
|
412
|
+
expect(body.error.message).toContain('Direct relay access disabled');
|
|
413
413
|
});
|
|
414
414
|
|
|
415
415
|
test('allows request with no origin header (private network peer)', async () => {
|