@vellumai/assistant 0.5.6 → 0.5.8
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Mocks — must be declared before importing the module under test
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
mock.module("../util/logger.js", () => ({
|
|
8
|
+
getLogger: () =>
|
|
9
|
+
new Proxy({} as Record<string, unknown>, {
|
|
10
|
+
get: () => () => {},
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const deliveredMessages: Array<{
|
|
15
|
+
url: string;
|
|
16
|
+
body: Record<string, unknown>;
|
|
17
|
+
}> = [];
|
|
18
|
+
|
|
19
|
+
let deliveryShouldFail = false;
|
|
20
|
+
|
|
21
|
+
mock.module("../runtime/gateway-client.js", () => ({
|
|
22
|
+
deliverChannelReply: async (url: string, body: Record<string, unknown>) => {
|
|
23
|
+
if (deliveryShouldFail) {
|
|
24
|
+
throw new Error("simulated delivery failure");
|
|
25
|
+
}
|
|
26
|
+
deliveredMessages.push({ url, body });
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
mock.module("../runtime/approval-message-composer.js", () => ({
|
|
31
|
+
composeApprovalMessageGenerative: async () => "Already resolved.",
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Import the module under test after mocks are set up
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
import type pino from "pino";
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
clearStaleNotificationCache,
|
|
42
|
+
deliverStaleApprovalReply,
|
|
43
|
+
} from "../runtime/routes/guardian-approval-reply-helpers.js";
|
|
44
|
+
|
|
45
|
+
const noopLogger = new Proxy({} as pino.Logger, {
|
|
46
|
+
get: () => () => {},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Tests
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
describe("deliverStaleApprovalReply deduplication", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
deliveredMessages.length = 0;
|
|
56
|
+
deliveryShouldFail = false;
|
|
57
|
+
clearStaleNotificationCache();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
clearStaleNotificationCache();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("sends the first 'approval_already_resolved' notification", async () => {
|
|
65
|
+
await deliverStaleApprovalReply({
|
|
66
|
+
scenario: "approval_already_resolved",
|
|
67
|
+
sourceChannel: "slack",
|
|
68
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
69
|
+
chatId: "chat-1",
|
|
70
|
+
assistantId: "asst-1",
|
|
71
|
+
logger: noopLogger,
|
|
72
|
+
errorLogMessage: "test",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(deliveredMessages).toHaveLength(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("suppresses duplicate 'approval_already_resolved' for the same chat", async () => {
|
|
79
|
+
const params = {
|
|
80
|
+
scenario: "approval_already_resolved" as const,
|
|
81
|
+
sourceChannel: "slack" as const,
|
|
82
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
83
|
+
chatId: "chat-1",
|
|
84
|
+
assistantId: "asst-1",
|
|
85
|
+
logger: noopLogger,
|
|
86
|
+
errorLogMessage: "test",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await deliverStaleApprovalReply(params);
|
|
90
|
+
await deliverStaleApprovalReply(params);
|
|
91
|
+
await deliverStaleApprovalReply(params);
|
|
92
|
+
|
|
93
|
+
expect(deliveredMessages).toHaveLength(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("allows 'approval_already_resolved' for different chats", async () => {
|
|
97
|
+
const base = {
|
|
98
|
+
scenario: "approval_already_resolved" as const,
|
|
99
|
+
sourceChannel: "slack" as const,
|
|
100
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
101
|
+
assistantId: "asst-1",
|
|
102
|
+
logger: noopLogger,
|
|
103
|
+
errorLogMessage: "test",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await deliverStaleApprovalReply({ ...base, chatId: "chat-1" });
|
|
107
|
+
await deliverStaleApprovalReply({ ...base, chatId: "chat-2" });
|
|
108
|
+
|
|
109
|
+
expect(deliveredMessages).toHaveLength(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("does not deduplicate non-'approval_already_resolved' scenarios", async () => {
|
|
113
|
+
const params = {
|
|
114
|
+
scenario: "reminder_prompt" as const,
|
|
115
|
+
sourceChannel: "slack" as const,
|
|
116
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
117
|
+
chatId: "chat-1",
|
|
118
|
+
assistantId: "asst-1",
|
|
119
|
+
logger: noopLogger,
|
|
120
|
+
errorLogMessage: "test",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
await deliverStaleApprovalReply(params);
|
|
124
|
+
await deliverStaleApprovalReply(params);
|
|
125
|
+
|
|
126
|
+
expect(deliveredMessages).toHaveLength(2);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("allows re-send after cache is cleared (simulates TTL expiry)", async () => {
|
|
130
|
+
const params = {
|
|
131
|
+
scenario: "approval_already_resolved" as const,
|
|
132
|
+
sourceChannel: "slack" as const,
|
|
133
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
134
|
+
chatId: "chat-1",
|
|
135
|
+
assistantId: "asst-1",
|
|
136
|
+
logger: noopLogger,
|
|
137
|
+
errorLogMessage: "test",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
await deliverStaleApprovalReply(params);
|
|
141
|
+
expect(deliveredMessages).toHaveLength(1);
|
|
142
|
+
|
|
143
|
+
// Simulate TTL expiry
|
|
144
|
+
clearStaleNotificationCache();
|
|
145
|
+
|
|
146
|
+
await deliverStaleApprovalReply(params);
|
|
147
|
+
expect(deliveredMessages).toHaveLength(2);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("does not cache dedup key when delivery fails, allowing retries", async () => {
|
|
151
|
+
const params = {
|
|
152
|
+
scenario: "approval_already_resolved" as const,
|
|
153
|
+
sourceChannel: "slack" as const,
|
|
154
|
+
replyCallbackUrl: "https://example.com/reply",
|
|
155
|
+
chatId: "chat-1",
|
|
156
|
+
assistantId: "asst-1",
|
|
157
|
+
logger: noopLogger,
|
|
158
|
+
errorLogMessage: "test",
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// First attempt fails — should not cache the dedup key
|
|
162
|
+
deliveryShouldFail = true;
|
|
163
|
+
await deliverStaleApprovalReply(params);
|
|
164
|
+
expect(deliveredMessages).toHaveLength(0);
|
|
165
|
+
|
|
166
|
+
// Second attempt succeeds — should not be suppressed by dedup
|
|
167
|
+
deliveryShouldFail = false;
|
|
168
|
+
await deliverStaleApprovalReply(params);
|
|
169
|
+
expect(deliveredMessages).toHaveLength(1);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ContactWithChannels } from "../contacts/types.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mock state for resolveCallHints tests
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
let mockAssistantName: string | null = "Velissa";
|
|
10
|
+
let mockGuardianName: string = "Sidd";
|
|
11
|
+
let mockTargetContact: ContactWithChannels | null = null;
|
|
12
|
+
let mockRecentContacts: ContactWithChannels[] = [];
|
|
13
|
+
let mockFindContactByAddressThrows = false;
|
|
14
|
+
let mockListContactsThrows = false;
|
|
15
|
+
|
|
16
|
+
const logWarnFn = mock(() => {});
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Mock modules — must be before importing module under test
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
mock.module("../daemon/identity-helpers.js", () => ({
|
|
23
|
+
getAssistantName: () => mockAssistantName,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mock.module("../prompts/user-reference.js", () => ({
|
|
27
|
+
DEFAULT_USER_REFERENCE: "my human",
|
|
28
|
+
resolveGuardianName: () => mockGuardianName,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
mock.module("../contacts/contact-store.js", () => ({
|
|
32
|
+
findContactByAddress: (_type: string, _address: string) => {
|
|
33
|
+
if (mockFindContactByAddressThrows) {
|
|
34
|
+
throw new Error("DB error: findContactByAddress");
|
|
35
|
+
}
|
|
36
|
+
return mockTargetContact;
|
|
37
|
+
},
|
|
38
|
+
findGuardianForChannel: () => null,
|
|
39
|
+
listGuardianChannels: () => null,
|
|
40
|
+
listContacts: (_limit?: number) => {
|
|
41
|
+
if (mockListContactsThrows) {
|
|
42
|
+
throw new Error("DB error: listContacts");
|
|
43
|
+
}
|
|
44
|
+
return mockRecentContacts;
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Bun's mock.module for "../util/logger.js" doesn't intercept the transitive
|
|
49
|
+
// import in stt-hints.ts due to a Bun limitation. Mocking pino at the package
|
|
50
|
+
// level works because getLogger uses a Proxy that lazily creates a pino child
|
|
51
|
+
// logger — intercepting pino itself captures all log calls.
|
|
52
|
+
const mockChildLogger = {
|
|
53
|
+
debug: () => {},
|
|
54
|
+
info: () => {},
|
|
55
|
+
warn: logWarnFn,
|
|
56
|
+
error: () => {},
|
|
57
|
+
child: () => mockChildLogger,
|
|
58
|
+
};
|
|
59
|
+
const mockPinoLogger = Object.assign(() => mockChildLogger, {
|
|
60
|
+
destination: () => ({}),
|
|
61
|
+
multistream: () => ({}),
|
|
62
|
+
});
|
|
63
|
+
mock.module("pino", () => ({ default: mockPinoLogger }));
|
|
64
|
+
mock.module("pino-pretty", () => ({ default: () => ({}) }));
|
|
65
|
+
|
|
66
|
+
// Import after mocking
|
|
67
|
+
import { buildSttHints, resolveCallHints, type SttHintsInput } from "../calls/stt-hints.js";
|
|
68
|
+
|
|
69
|
+
function emptyInput(): SttHintsInput {
|
|
70
|
+
return {
|
|
71
|
+
staticHints: [],
|
|
72
|
+
assistantName: null,
|
|
73
|
+
guardianName: null,
|
|
74
|
+
taskDescription: null,
|
|
75
|
+
targetContactName: null,
|
|
76
|
+
callerContactName: null,
|
|
77
|
+
inviteFriendName: null,
|
|
78
|
+
inviteGuardianName: null,
|
|
79
|
+
recentContactNames: [],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe("buildSttHints", () => {
|
|
84
|
+
test("empty inputs produce empty string", () => {
|
|
85
|
+
expect(buildSttHints(emptyInput())).toBe("");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("static hints included verbatim", () => {
|
|
89
|
+
const input = emptyInput();
|
|
90
|
+
input.staticHints = ["Vellum", "Acme"];
|
|
91
|
+
expect(buildSttHints(input)).toBe("Vellum,Acme");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("assistant name included", () => {
|
|
95
|
+
const input = emptyInput();
|
|
96
|
+
input.assistantName = "Velissa";
|
|
97
|
+
expect(buildSttHints(input)).toBe("Velissa");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("guardian name included", () => {
|
|
101
|
+
const input = emptyInput();
|
|
102
|
+
input.guardianName = "Sidd";
|
|
103
|
+
expect(buildSttHints(input)).toBe("Sidd");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('default guardian name "my human" excluded', () => {
|
|
107
|
+
const input = emptyInput();
|
|
108
|
+
input.guardianName = "my human";
|
|
109
|
+
expect(buildSttHints(input)).toBe("");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("guardian name with whitespace around default sentinel excluded", () => {
|
|
113
|
+
const input = emptyInput();
|
|
114
|
+
input.guardianName = " my human ";
|
|
115
|
+
expect(buildSttHints(input)).toBe("");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("invite friend name included", () => {
|
|
119
|
+
const input = emptyInput();
|
|
120
|
+
input.inviteFriendName = "Alice";
|
|
121
|
+
expect(buildSttHints(input)).toBe("Alice");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("invite guardian name included", () => {
|
|
125
|
+
const input = emptyInput();
|
|
126
|
+
input.inviteGuardianName = "Bob";
|
|
127
|
+
expect(buildSttHints(input)).toBe("Bob");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("target contact name included", () => {
|
|
131
|
+
const input = emptyInput();
|
|
132
|
+
input.targetContactName = "Charlie";
|
|
133
|
+
expect(buildSttHints(input)).toBe("Charlie");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("caller contact name included", () => {
|
|
137
|
+
const input = emptyInput();
|
|
138
|
+
input.callerContactName = "Diana";
|
|
139
|
+
expect(buildSttHints(input)).toBe("Diana");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("recent contact names included", () => {
|
|
143
|
+
const input = emptyInput();
|
|
144
|
+
input.recentContactNames = ["Dave", "Eve"];
|
|
145
|
+
expect(buildSttHints(input)).toBe("Dave,Eve");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("proper nouns extracted from task description", () => {
|
|
149
|
+
const input = emptyInput();
|
|
150
|
+
input.taskDescription = "Call John Smith at Acme Corp";
|
|
151
|
+
const result = buildSttHints(input);
|
|
152
|
+
expect(result).toContain("John");
|
|
153
|
+
expect(result).toContain("Smith");
|
|
154
|
+
expect(result).toContain("Acme");
|
|
155
|
+
expect(result).toContain("Corp");
|
|
156
|
+
// "Call" is the first word of the sentence — should not be extracted
|
|
157
|
+
expect(result).not.toContain("Call");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("proper nouns extracted across sentence boundaries", () => {
|
|
161
|
+
const input = emptyInput();
|
|
162
|
+
input.taskDescription = "Meet with Alice. Then call Bob! Ask Charlie? Done.";
|
|
163
|
+
const result = buildSttHints(input);
|
|
164
|
+
expect(result).toContain("Alice");
|
|
165
|
+
expect(result).toContain("Bob");
|
|
166
|
+
expect(result).toContain("Charlie");
|
|
167
|
+
// First words of sentences should be excluded
|
|
168
|
+
expect(result).not.toContain("Meet");
|
|
169
|
+
expect(result).not.toContain("Then");
|
|
170
|
+
expect(result).not.toContain("Ask");
|
|
171
|
+
expect(result).not.toContain("Done");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("duplicates removed (case-insensitive)", () => {
|
|
175
|
+
const input = emptyInput();
|
|
176
|
+
input.staticHints = ["Vellum", "vellum", "VELLUM"];
|
|
177
|
+
input.recentContactNames = ["Vellum"];
|
|
178
|
+
const result = buildSttHints(input);
|
|
179
|
+
// Should appear only once — the first occurrence is kept
|
|
180
|
+
expect(result).toBe("Vellum");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("empty and whitespace-only entries filtered", () => {
|
|
184
|
+
const input = emptyInput();
|
|
185
|
+
input.staticHints = ["", " ", "Valid", " ", "Also Valid"];
|
|
186
|
+
expect(buildSttHints(input)).toBe("Valid,Also Valid");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("entries are trimmed", () => {
|
|
190
|
+
const input = emptyInput();
|
|
191
|
+
input.staticHints = [" Padded ", " Spaces "];
|
|
192
|
+
expect(buildSttHints(input)).toBe("Padded,Spaces");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("output truncated at MAX_HINTS_LENGTH without partial words", () => {
|
|
196
|
+
const input = emptyInput();
|
|
197
|
+
// Create hints that will exceed 500 chars when joined
|
|
198
|
+
const longHints: string[] = [];
|
|
199
|
+
for (let i = 0; i < 100; i++) {
|
|
200
|
+
longHints.push(`Hint${i}LongWord`);
|
|
201
|
+
}
|
|
202
|
+
input.staticHints = longHints;
|
|
203
|
+
const result = buildSttHints(input);
|
|
204
|
+
expect(result.length).toBeLessThanOrEqual(500);
|
|
205
|
+
// Should not end with a comma (that would indicate a truncation right after a separator)
|
|
206
|
+
expect(result).not.toMatch(/,$/);
|
|
207
|
+
// Every comma-separated part should be a complete hint from our input
|
|
208
|
+
const parts = result.split(",");
|
|
209
|
+
for (const part of parts) {
|
|
210
|
+
expect(input.staticHints).toContain(part);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("all sources combined in correct order", () => {
|
|
215
|
+
const input: SttHintsInput = {
|
|
216
|
+
staticHints: ["StaticOne"],
|
|
217
|
+
assistantName: "Velissa",
|
|
218
|
+
guardianName: "Sidd",
|
|
219
|
+
taskDescription: "Call John at Acme",
|
|
220
|
+
targetContactName: "Target",
|
|
221
|
+
callerContactName: "Caller",
|
|
222
|
+
inviteFriendName: "Friend",
|
|
223
|
+
inviteGuardianName: "Guardian",
|
|
224
|
+
recentContactNames: ["Recent"],
|
|
225
|
+
};
|
|
226
|
+
const result = buildSttHints(input);
|
|
227
|
+
const parts = result.split(",");
|
|
228
|
+
// Verify all expected hints are present
|
|
229
|
+
expect(parts).toContain("StaticOne");
|
|
230
|
+
expect(parts).toContain("Velissa");
|
|
231
|
+
expect(parts).toContain("Sidd");
|
|
232
|
+
expect(parts).toContain("John");
|
|
233
|
+
expect(parts).toContain("Acme");
|
|
234
|
+
expect(parts).toContain("Target");
|
|
235
|
+
expect(parts).toContain("Caller");
|
|
236
|
+
expect(parts).toContain("Friend");
|
|
237
|
+
expect(parts).toContain("Guardian");
|
|
238
|
+
expect(parts).toContain("Recent");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("surnames after abbreviation periods are preserved", () => {
|
|
242
|
+
const input = emptyInput();
|
|
243
|
+
input.taskDescription = "Call Dr. Smith at Acme";
|
|
244
|
+
const result = buildSttHints(input);
|
|
245
|
+
expect(result).toContain("Smith");
|
|
246
|
+
expect(result).toContain("Acme");
|
|
247
|
+
// "Dr" should also appear as a capitalized word
|
|
248
|
+
expect(result).toContain("Dr");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("multiple abbreviation titles preserve following names", () => {
|
|
252
|
+
const input = emptyInput();
|
|
253
|
+
input.taskDescription = "Meet Mr. Johnson and Mrs. Williams at the office";
|
|
254
|
+
const result = buildSttHints(input);
|
|
255
|
+
expect(result).toContain("Johnson");
|
|
256
|
+
expect(result).toContain("Williams");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("non-ASCII letters preserved in names", () => {
|
|
260
|
+
const input = emptyInput();
|
|
261
|
+
input.taskDescription = "Call José García and Łukasz Nowak";
|
|
262
|
+
const result = buildSttHints(input);
|
|
263
|
+
expect(result).toContain("José");
|
|
264
|
+
expect(result).toContain("García");
|
|
265
|
+
expect(result).toContain("Łukasz");
|
|
266
|
+
expect(result).toContain("Nowak");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("accented uppercase letters detected as proper nouns", () => {
|
|
270
|
+
const input = emptyInput();
|
|
271
|
+
input.taskDescription = "Talk to Zoë about the project";
|
|
272
|
+
const result = buildSttHints(input);
|
|
273
|
+
expect(result).toContain("Zoë");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("null and empty string names are excluded", () => {
|
|
277
|
+
const input = emptyInput();
|
|
278
|
+
input.assistantName = "";
|
|
279
|
+
input.guardianName = "";
|
|
280
|
+
input.targetContactName = null;
|
|
281
|
+
input.inviteFriendName = null;
|
|
282
|
+
input.inviteGuardianName = null;
|
|
283
|
+
expect(buildSttHints(input)).toBe("");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// resolveCallHints — wiring and error resilience
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
function makeContact(displayName: string): ContactWithChannels {
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
return {
|
|
294
|
+
id: `contact-${displayName.toLowerCase()}`,
|
|
295
|
+
displayName,
|
|
296
|
+
notes: null,
|
|
297
|
+
lastInteraction: null,
|
|
298
|
+
interactionCount: 0,
|
|
299
|
+
createdAt: now,
|
|
300
|
+
updatedAt: now,
|
|
301
|
+
role: "contact",
|
|
302
|
+
contactType: "human",
|
|
303
|
+
principalId: null,
|
|
304
|
+
userFile: null,
|
|
305
|
+
channels: [],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
describe("resolveCallHints", () => {
|
|
310
|
+
beforeEach(() => {
|
|
311
|
+
mockAssistantName = "Velissa";
|
|
312
|
+
mockGuardianName = "Sidd";
|
|
313
|
+
mockTargetContact = null;
|
|
314
|
+
mockRecentContacts = [];
|
|
315
|
+
mockFindContactByAddressThrows = false;
|
|
316
|
+
mockListContactsThrows = false;
|
|
317
|
+
logWarnFn.mockClear();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("happy path wires all sources correctly", () => {
|
|
321
|
+
mockTargetContact = makeContact("Alice");
|
|
322
|
+
mockRecentContacts = [makeContact("Bob"), makeContact("Charlie")];
|
|
323
|
+
|
|
324
|
+
const session = {
|
|
325
|
+
task: "Call Alice at Acme",
|
|
326
|
+
toNumber: "+15551234567",
|
|
327
|
+
fromNumber: "+15559876543",
|
|
328
|
+
direction: "outbound" as const,
|
|
329
|
+
inviteFriendName: "Dave",
|
|
330
|
+
inviteGuardianName: "Eve",
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const result = resolveCallHints(session, ["StaticHint"]);
|
|
334
|
+
const parts = result.split(",");
|
|
335
|
+
|
|
336
|
+
expect(parts).toContain("StaticHint");
|
|
337
|
+
expect(parts).toContain("Velissa");
|
|
338
|
+
expect(parts).toContain("Sidd");
|
|
339
|
+
expect(parts).toContain("Alice");
|
|
340
|
+
expect(parts).toContain("Dave");
|
|
341
|
+
expect(parts).toContain("Eve");
|
|
342
|
+
expect(parts).toContain("Bob");
|
|
343
|
+
expect(parts).toContain("Charlie");
|
|
344
|
+
// Proper nouns from task description
|
|
345
|
+
expect(parts).toContain("Acme");
|
|
346
|
+
expect(logWarnFn).not.toHaveBeenCalled();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("findContactByAddress failure is caught and logged without throwing", () => {
|
|
350
|
+
mockFindContactByAddressThrows = true;
|
|
351
|
+
mockRecentContacts = [makeContact("Bob")];
|
|
352
|
+
|
|
353
|
+
const session = {
|
|
354
|
+
task: null,
|
|
355
|
+
toNumber: "+15551234567",
|
|
356
|
+
fromNumber: "+15559876543",
|
|
357
|
+
direction: "outbound" as const,
|
|
358
|
+
inviteFriendName: null,
|
|
359
|
+
inviteGuardianName: null,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// Should not throw
|
|
363
|
+
const result = resolveCallHints(session, []);
|
|
364
|
+
const parts = result.split(",");
|
|
365
|
+
|
|
366
|
+
// Target contact should be absent (lookup failed)
|
|
367
|
+
// But other sources should still work
|
|
368
|
+
expect(parts).toContain("Velissa");
|
|
369
|
+
expect(parts).toContain("Sidd");
|
|
370
|
+
expect(parts).toContain("Bob");
|
|
371
|
+
expect(logWarnFn).toHaveBeenCalled();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("listContacts failure is caught and logged without throwing", () => {
|
|
375
|
+
mockListContactsThrows = true;
|
|
376
|
+
mockTargetContact = makeContact("Alice");
|
|
377
|
+
|
|
378
|
+
const session = {
|
|
379
|
+
task: null,
|
|
380
|
+
toNumber: "+15551234567",
|
|
381
|
+
fromNumber: "+15559876543",
|
|
382
|
+
direction: "outbound" as const,
|
|
383
|
+
inviteFriendName: null,
|
|
384
|
+
inviteGuardianName: null,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Should not throw
|
|
388
|
+
const result = resolveCallHints(session, []);
|
|
389
|
+
const parts = result.split(",");
|
|
390
|
+
|
|
391
|
+
// Recent contacts should be absent (listing failed)
|
|
392
|
+
// But other sources should still work
|
|
393
|
+
expect(parts).toContain("Velissa");
|
|
394
|
+
expect(parts).toContain("Sidd");
|
|
395
|
+
expect(parts).toContain("Alice");
|
|
396
|
+
expect(logWarnFn).toHaveBeenCalled();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("inbound call resolves caller contact from fromNumber", () => {
|
|
400
|
+
mockTargetContact = makeContact("Alice");
|
|
401
|
+
mockRecentContacts = [makeContact("Bob")];
|
|
402
|
+
|
|
403
|
+
const session = {
|
|
404
|
+
task: null,
|
|
405
|
+
toNumber: "+15559876543",
|
|
406
|
+
fromNumber: "+15551234567",
|
|
407
|
+
direction: "inbound" as const,
|
|
408
|
+
inviteFriendName: null,
|
|
409
|
+
inviteGuardianName: null,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const result = resolveCallHints(session, []);
|
|
413
|
+
const parts = result.split(",");
|
|
414
|
+
|
|
415
|
+
// For inbound, the contact found via fromNumber should appear as caller, not target
|
|
416
|
+
expect(parts).toContain("Alice");
|
|
417
|
+
expect(parts).toContain("Velissa");
|
|
418
|
+
expect(parts).toContain("Sidd");
|
|
419
|
+
expect(parts).toContain("Bob");
|
|
420
|
+
expect(logWarnFn).not.toHaveBeenCalled();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("null session produces hints from assistant name, guardian name, and recent contacts", () => {
|
|
424
|
+
mockRecentContacts = [makeContact("RecentOne"), makeContact("RecentTwo")];
|
|
425
|
+
|
|
426
|
+
const result = resolveCallHints(null, ["Static"]);
|
|
427
|
+
const parts = result.split(",");
|
|
428
|
+
|
|
429
|
+
expect(parts).toContain("Static");
|
|
430
|
+
expect(parts).toContain("Velissa");
|
|
431
|
+
expect(parts).toContain("Sidd");
|
|
432
|
+
expect(parts).toContain("RecentOne");
|
|
433
|
+
expect(parts).toContain("RecentTwo");
|
|
434
|
+
// No target contact lookup should have been attempted (no session)
|
|
435
|
+
expect(logWarnFn).not.toHaveBeenCalled();
|
|
436
|
+
});
|
|
437
|
+
});
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Unit tests for the GET /v1/suggestion endpoint (handleGetSuggestion).
|
|
3
3
|
*
|
|
4
4
|
* Validates happy path, all null-return paths, caching, staleness check,
|
|
5
|
-
* quote stripping,
|
|
6
|
-
* and modelIntent verification.
|
|
5
|
+
* quote stripping, empty response rejection, and modelIntent verification.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
import { describe, expect, mock, test } from "bun:test";
|
|
@@ -293,36 +292,6 @@ describe("GET /v1/suggestion", () => {
|
|
|
293
292
|
expect(body.suggestion).toBe("Sure, let's go!");
|
|
294
293
|
});
|
|
295
294
|
|
|
296
|
-
test("truncates long suggestions at word boundary", async () => {
|
|
297
|
-
// A 60-char string that will exceed the 50-char limit
|
|
298
|
-
const longText =
|
|
299
|
-
"This is a really long suggestion that goes well beyond fifty chars";
|
|
300
|
-
const provider = makeMockProvider(longText);
|
|
301
|
-
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
302
|
-
mockGetConversationByKey.mockImplementation(() => ({
|
|
303
|
-
conversationId: "conv-test",
|
|
304
|
-
}));
|
|
305
|
-
mockGetMessages.mockImplementation(() => [
|
|
306
|
-
{
|
|
307
|
-
id: "msg-asst-1",
|
|
308
|
-
conversationId: "conv-test",
|
|
309
|
-
role: "assistant",
|
|
310
|
-
content: JSON.stringify([{ type: "text", text: "Hello there" }]),
|
|
311
|
-
createdAt: Date.now(),
|
|
312
|
-
metadata: null,
|
|
313
|
-
},
|
|
314
|
-
]);
|
|
315
|
-
|
|
316
|
-
const url = makeUrl({ conversationKey: "test-key" });
|
|
317
|
-
const deps = makeDeps();
|
|
318
|
-
const res = await handleGetSuggestion(url, deps);
|
|
319
|
-
const body = (await res.json()) as { suggestion: string };
|
|
320
|
-
|
|
321
|
-
expect(body.suggestion.length).toBeLessThanOrEqual(50);
|
|
322
|
-
// Should end at a word boundary (no partial words)
|
|
323
|
-
expect(body.suggestion).not.toMatch(/\s$/);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
295
|
test("rejects empty LLM response", async () => {
|
|
327
296
|
const provider = makeMockProvider("");
|
|
328
297
|
mockGetConfiguredProvider.mockImplementation(async () => provider);
|