@vellumai/assistant 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -1,14 +1,93 @@
|
|
|
1
|
-
import { eq, desc, asc, and, count, sql, inArray } from 'drizzle-orm';
|
|
1
|
+
import { eq, desc, asc, and, count, sql, inArray, or, isNull } from 'drizzle-orm';
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
|
-
import {
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getDb, rawGet, rawExec } from './db.js';
|
|
4
5
|
import { conversations, messages, toolInvocations, messageRuns, channelInboundEvents, memoryItemSources, memoryItems, memoryEmbeddings, memoryItemEntities, memorySegments, messageAttachments, llmRequestLogs } from './schema.js';
|
|
5
6
|
import { getConfig } from '../config/loader.js';
|
|
6
7
|
import { indexMessageNow } from './indexer.js';
|
|
8
|
+
import { parseChannelId } from '../channels/types.js';
|
|
9
|
+
import type { ChannelId } from '../channels/types.js';
|
|
10
|
+
import { isChannelId, CHANNEL_IDS } from '../channels/types.js';
|
|
7
11
|
import { getLogger } from '../util/logger.js';
|
|
8
12
|
import { deleteOrphanAttachments } from './attachments-store.js';
|
|
13
|
+
import { createRowMapper } from '../util/row-mapper.js';
|
|
9
14
|
|
|
10
15
|
const log = getLogger('conversation-store');
|
|
11
16
|
|
|
17
|
+
// ── Message metadata Zod schema ──────────────────────────────────────
|
|
18
|
+
// Validates the JSON stored in messages.metadata. Known fields are typed;
|
|
19
|
+
// extra keys are allowed via passthrough so callers can attach ad-hoc data.
|
|
20
|
+
|
|
21
|
+
const channelIdSchema = z.enum(CHANNEL_IDS);
|
|
22
|
+
|
|
23
|
+
const subagentNotificationSchema = z.object({
|
|
24
|
+
subagentId: z.string(),
|
|
25
|
+
label: z.string(),
|
|
26
|
+
status: z.enum(['completed', 'failed', 'aborted']),
|
|
27
|
+
error: z.string().optional(),
|
|
28
|
+
conversationId: z.string().optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const messageMetadataSchema = z.object({
|
|
32
|
+
userMessageChannel: channelIdSchema.optional(),
|
|
33
|
+
assistantMessageChannel: channelIdSchema.optional(),
|
|
34
|
+
subagentNotification: subagentNotificationSchema.optional(),
|
|
35
|
+
}).passthrough();
|
|
36
|
+
|
|
37
|
+
export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
|
|
38
|
+
|
|
39
|
+
export interface ConversationRow {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string | null;
|
|
42
|
+
createdAt: number;
|
|
43
|
+
updatedAt: number;
|
|
44
|
+
totalInputTokens: number;
|
|
45
|
+
totalOutputTokens: number;
|
|
46
|
+
totalEstimatedCost: number;
|
|
47
|
+
contextSummary: string | null;
|
|
48
|
+
contextCompactedMessageCount: number;
|
|
49
|
+
contextCompactedAt: number | null;
|
|
50
|
+
threadType: string;
|
|
51
|
+
source: string;
|
|
52
|
+
memoryScopeId: string;
|
|
53
|
+
originChannel: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const parseConversation = createRowMapper<typeof conversations.$inferSelect, ConversationRow>({
|
|
57
|
+
id: 'id',
|
|
58
|
+
title: 'title',
|
|
59
|
+
createdAt: 'createdAt',
|
|
60
|
+
updatedAt: 'updatedAt',
|
|
61
|
+
totalInputTokens: 'totalInputTokens',
|
|
62
|
+
totalOutputTokens: 'totalOutputTokens',
|
|
63
|
+
totalEstimatedCost: 'totalEstimatedCost',
|
|
64
|
+
contextSummary: 'contextSummary',
|
|
65
|
+
contextCompactedMessageCount: 'contextCompactedMessageCount',
|
|
66
|
+
contextCompactedAt: 'contextCompactedAt',
|
|
67
|
+
threadType: 'threadType',
|
|
68
|
+
source: 'source',
|
|
69
|
+
memoryScopeId: 'memoryScopeId',
|
|
70
|
+
originChannel: 'originChannel',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export interface MessageRow {
|
|
74
|
+
id: string;
|
|
75
|
+
conversationId: string;
|
|
76
|
+
role: string;
|
|
77
|
+
content: string;
|
|
78
|
+
createdAt: number;
|
|
79
|
+
metadata: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const parseMessage = createRowMapper<typeof messages.$inferSelect, MessageRow>({
|
|
83
|
+
id: 'id',
|
|
84
|
+
conversationId: 'conversationId',
|
|
85
|
+
role: 'role',
|
|
86
|
+
content: 'content',
|
|
87
|
+
createdAt: 'createdAt',
|
|
88
|
+
metadata: 'metadata',
|
|
89
|
+
});
|
|
90
|
+
|
|
12
91
|
/**
|
|
13
92
|
* Monotonic timestamp source for message ordering. Two messages saved within
|
|
14
93
|
* the same millisecond (e.g., tool_results user message + assistant message in
|
|
@@ -23,11 +102,12 @@ function monotonicNow(): number {
|
|
|
23
102
|
return lastTimestamp;
|
|
24
103
|
}
|
|
25
104
|
|
|
26
|
-
export function createConversation(titleOrOpts?: string | { title?: string; threadType?: 'standard' | 'private' | 'background' }) {
|
|
105
|
+
export function createConversation(titleOrOpts?: string | { title?: string; threadType?: 'standard' | 'private' | 'background'; source?: string }) {
|
|
27
106
|
const db = getDb();
|
|
28
107
|
const now = Date.now();
|
|
29
108
|
const opts = typeof titleOrOpts === 'string' ? { title: titleOrOpts } : (titleOrOpts ?? {});
|
|
30
109
|
const threadType = opts.threadType ?? 'standard';
|
|
110
|
+
const source = opts.source ?? 'user';
|
|
31
111
|
const id = uuid();
|
|
32
112
|
const memoryScopeId = threadType === 'private' ? `private:${id}` : 'default';
|
|
33
113
|
const conversation = {
|
|
@@ -42,20 +122,21 @@ export function createConversation(titleOrOpts?: string | { title?: string; thre
|
|
|
42
122
|
contextCompactedMessageCount: 0,
|
|
43
123
|
contextCompactedAt: null as number | null,
|
|
44
124
|
threadType,
|
|
125
|
+
source,
|
|
45
126
|
memoryScopeId,
|
|
46
127
|
};
|
|
47
128
|
db.insert(conversations).values(conversation).run();
|
|
48
129
|
return conversation;
|
|
49
130
|
}
|
|
50
131
|
|
|
51
|
-
export function getConversation(id: string) {
|
|
132
|
+
export function getConversation(id: string): ConversationRow | null {
|
|
52
133
|
const db = getDb();
|
|
53
|
-
const
|
|
134
|
+
const row = db
|
|
54
135
|
.select()
|
|
55
136
|
.from(conversations)
|
|
56
137
|
.where(eq(conversations.id, id))
|
|
57
138
|
.get();
|
|
58
|
-
return
|
|
139
|
+
return row ? parseConversation(row) : null;
|
|
59
140
|
}
|
|
60
141
|
|
|
61
142
|
export function getConversationThreadType(conversationId: string): 'standard' | 'private' {
|
|
@@ -84,7 +165,7 @@ export function deleteConversation(id: string): void {
|
|
|
84
165
|
});
|
|
85
166
|
}
|
|
86
167
|
|
|
87
|
-
export function listConversations(limit?: number, includeBackground = false, offset = 0) {
|
|
168
|
+
export function listConversations(limit?: number, includeBackground = false, offset = 0): ConversationRow[] {
|
|
88
169
|
const db = getDb();
|
|
89
170
|
const where = includeBackground ? undefined : sql`${conversations.threadType} != 'background'`;
|
|
90
171
|
const query = db
|
|
@@ -94,7 +175,7 @@ export function listConversations(limit?: number, includeBackground = false, off
|
|
|
94
175
|
.orderBy(desc(conversations.updatedAt))
|
|
95
176
|
.limit(limit ?? 100)
|
|
96
177
|
.offset(offset);
|
|
97
|
-
return query.all();
|
|
178
|
+
return query.all().map(parseConversation);
|
|
98
179
|
}
|
|
99
180
|
|
|
100
181
|
export function countConversations(includeBackground = false): number {
|
|
@@ -108,37 +189,74 @@ export function countConversations(includeBackground = false): number {
|
|
|
108
189
|
return total;
|
|
109
190
|
}
|
|
110
191
|
|
|
111
|
-
export function getLatestConversation() {
|
|
192
|
+
export function getLatestConversation(): ConversationRow | null {
|
|
112
193
|
const db = getDb();
|
|
113
|
-
const
|
|
194
|
+
const row = db
|
|
114
195
|
.select()
|
|
115
196
|
.from(conversations)
|
|
116
197
|
.where(sql`${conversations.threadType} != 'background'`)
|
|
117
198
|
.orderBy(desc(conversations.updatedAt))
|
|
118
199
|
.limit(1)
|
|
119
200
|
.get();
|
|
120
|
-
return
|
|
201
|
+
return row ? parseConversation(row) : null;
|
|
121
202
|
}
|
|
122
203
|
|
|
123
204
|
export function addMessage(conversationId: string, role: string, content: string, metadata?: Record<string, unknown>) {
|
|
124
205
|
const db = getDb();
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
206
|
+
const messageId = uuid();
|
|
207
|
+
|
|
208
|
+
if (metadata) {
|
|
209
|
+
const result = messageMetadataSchema.safeParse(metadata);
|
|
210
|
+
if (!result.success) {
|
|
211
|
+
log.warn({ conversationId, messageId, issues: result.error.issues }, 'Invalid message metadata, storing as-is');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const metadataStr = metadata ? JSON.stringify(metadata) : undefined;
|
|
216
|
+
const originChannelCandidate =
|
|
217
|
+
metadata && isChannelId(metadata.userMessageChannel)
|
|
218
|
+
? metadata.userMessageChannel
|
|
219
|
+
: null;
|
|
134
220
|
// Wrap insert + updatedAt bump in a transaction so they're atomic.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
221
|
+
// Retry on SQLITE_BUSY in case busy_timeout is exhausted under heavy contention.
|
|
222
|
+
// Timestamp is recomputed each attempt so a late retry doesn't persist a stale updatedAt.
|
|
223
|
+
const MAX_RETRIES = 3;
|
|
224
|
+
let now!: number;
|
|
225
|
+
for (let attempt = 0; ; attempt++) {
|
|
226
|
+
now = monotonicNow();
|
|
227
|
+
try {
|
|
228
|
+
const values = {
|
|
229
|
+
id: messageId,
|
|
230
|
+
conversationId,
|
|
231
|
+
role,
|
|
232
|
+
content,
|
|
233
|
+
createdAt: now,
|
|
234
|
+
...(metadataStr ? { metadata: metadataStr } : {}),
|
|
235
|
+
};
|
|
236
|
+
db.transaction((tx) => {
|
|
237
|
+
tx.insert(messages).values(values).run();
|
|
238
|
+
if (originChannelCandidate) {
|
|
239
|
+
tx.update(conversations)
|
|
240
|
+
.set({ originChannel: originChannelCandidate })
|
|
241
|
+
.where(and(eq(conversations.id, conversationId), isNull(conversations.originChannel)))
|
|
242
|
+
.run();
|
|
243
|
+
}
|
|
244
|
+
tx.update(conversations)
|
|
245
|
+
.set({ updatedAt: now })
|
|
246
|
+
.where(eq(conversations.id, conversationId))
|
|
247
|
+
.run();
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (attempt < MAX_RETRIES && (err as { code?: string }).code === 'SQLITE_BUSY') {
|
|
252
|
+
log.warn({ attempt, conversationId }, 'addMessage: SQLITE_BUSY, retrying');
|
|
253
|
+
Bun.sleepSync(50 * (attempt + 1));
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const message = { id: messageId, conversationId, role, content, createdAt: now, ...(metadataStr ? { metadata: metadataStr } : {}) };
|
|
142
260
|
|
|
143
261
|
try {
|
|
144
262
|
const config = getConfig();
|
|
@@ -158,14 +276,15 @@ export function addMessage(conversationId: string, role: string, content: string
|
|
|
158
276
|
return message;
|
|
159
277
|
}
|
|
160
278
|
|
|
161
|
-
export function getMessages(conversationId: string) {
|
|
279
|
+
export function getMessages(conversationId: string): MessageRow[] {
|
|
162
280
|
const db = getDb();
|
|
163
281
|
return db
|
|
164
282
|
.select()
|
|
165
283
|
.from(messages)
|
|
166
284
|
.where(eq(messages.conversationId, conversationId))
|
|
167
285
|
.orderBy(asc(messages.createdAt))
|
|
168
|
-
.all()
|
|
286
|
+
.all()
|
|
287
|
+
.map(parseMessage);
|
|
169
288
|
}
|
|
170
289
|
|
|
171
290
|
export function updateConversationTitle(id: string, title: string): void {
|
|
@@ -218,30 +337,27 @@ export function updateConversationContextWindow(
|
|
|
218
337
|
* Returns { conversations, messages } counts.
|
|
219
338
|
*/
|
|
220
339
|
export function clearAll(): { conversations: number; messages: number } {
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const msgCount = (raw.query('SELECT COUNT(*) AS c FROM messages').get() as { c: number }).c;
|
|
225
|
-
const convCount = (raw.query('SELECT COUNT(*) AS c FROM conversations').get() as { c: number }).c;
|
|
340
|
+
const msgCount = rawGet<{ c: number }>('SELECT COUNT(*) AS c FROM messages')?.c ?? 0;
|
|
341
|
+
const convCount = rawGet<{ c: number }>('SELECT COUNT(*) AS c FROM conversations')?.c ?? 0;
|
|
226
342
|
|
|
227
343
|
// Delete in dependency order. Cascades handle memory_segments,
|
|
228
344
|
// memory_item_sources, and tool_invocations, but we explicitly
|
|
229
345
|
// clear non-cascading memory tables too.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
346
|
+
rawExec('DELETE FROM memory_segment_fts');
|
|
347
|
+
rawExec('DELETE FROM memory_item_sources');
|
|
348
|
+
rawExec('DELETE FROM memory_segments');
|
|
349
|
+
rawExec('DELETE FROM memory_items');
|
|
350
|
+
rawExec('DELETE FROM memory_summaries');
|
|
351
|
+
rawExec('DELETE FROM memory_embeddings');
|
|
352
|
+
rawExec('DELETE FROM memory_jobs');
|
|
353
|
+
rawExec('DELETE FROM memory_checkpoints');
|
|
354
|
+
rawExec('DELETE FROM llm_request_logs');
|
|
355
|
+
rawExec('DELETE FROM llm_usage_events');
|
|
356
|
+
rawExec('DELETE FROM message_attachments');
|
|
357
|
+
rawExec('DELETE FROM attachments');
|
|
358
|
+
rawExec('DELETE FROM tool_invocations');
|
|
359
|
+
rawExec('DELETE FROM messages');
|
|
360
|
+
rawExec('DELETE FROM conversations');
|
|
245
361
|
|
|
246
362
|
return { conversations: convCount, messages: msgCount };
|
|
247
363
|
}
|
|
@@ -309,7 +425,7 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
309
425
|
.where(inArray(messageAttachments.messageId, messageIds))
|
|
310
426
|
.all()
|
|
311
427
|
.map((r) => r.attachmentId)
|
|
312
|
-
.filter((id): id is string => id
|
|
428
|
+
.filter((id): id is string => id != null)
|
|
313
429
|
: [];
|
|
314
430
|
|
|
315
431
|
db.transaction((tx) => {
|
|
@@ -401,7 +517,7 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
401
517
|
.where(eq(messageAttachments.messageId, messageId))
|
|
402
518
|
.all()
|
|
403
519
|
.map((r) => r.attachmentId)
|
|
404
|
-
.filter((id): id is string => id !==
|
|
520
|
+
.filter((id): id is string => id !== undefined);
|
|
405
521
|
|
|
406
522
|
db.transaction((tx) => {
|
|
407
523
|
// Collect memory segment IDs linked to this message before cascade.
|
|
@@ -480,3 +596,171 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
480
596
|
|
|
481
597
|
return result;
|
|
482
598
|
}
|
|
599
|
+
|
|
600
|
+
export interface ConversationSearchResult {
|
|
601
|
+
conversationId: string;
|
|
602
|
+
conversationTitle: string | null;
|
|
603
|
+
conversationUpdatedAt: number;
|
|
604
|
+
matchingMessages: Array<{
|
|
605
|
+
messageId: string;
|
|
606
|
+
role: string;
|
|
607
|
+
/** Plain-text excerpt around the match, truncated to ~200 chars. */
|
|
608
|
+
excerpt: string;
|
|
609
|
+
createdAt: number;
|
|
610
|
+
}>;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Full-text search across message content using SQL LIKE.
|
|
615
|
+
* Searches message content for the query string and returns matching
|
|
616
|
+
* conversations with their relevant messages, ordered by most recently updated.
|
|
617
|
+
*
|
|
618
|
+
* Messages that contain JSON-encoded content blocks are searched by their
|
|
619
|
+
* raw content string — the query will match inside code blocks, tool call text,
|
|
620
|
+
* and plain text alike. This is intentional: it avoids a full content parse
|
|
621
|
+
* at query time and stays fast even on large databases.
|
|
622
|
+
*/
|
|
623
|
+
export function searchConversations(
|
|
624
|
+
query: string,
|
|
625
|
+
opts?: { limit?: number; maxMessagesPerConversation?: number },
|
|
626
|
+
): ConversationSearchResult[] {
|
|
627
|
+
if (!query.trim()) return [];
|
|
628
|
+
|
|
629
|
+
const db = getDb();
|
|
630
|
+
const limit = opts?.limit ?? 20;
|
|
631
|
+
const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
|
|
632
|
+
// Escape backslashes first so they don't interfere with subsequent % and _ escaping.
|
|
633
|
+
// With ESCAPE '\\', SQLite treats \\ as a literal backslash, \% as a literal percent,
|
|
634
|
+
// and \_ as a literal underscore — so all three characters must be escaped in that order.
|
|
635
|
+
const pattern = `%${query.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_')}%`;
|
|
636
|
+
|
|
637
|
+
// Find conversations whose title or at least one message content matches the query.
|
|
638
|
+
// leftJoin ensures title-only matches are included even for empty conversations
|
|
639
|
+
// (innerJoin would silently drop them).
|
|
640
|
+
// ESCAPE '\\' is required for SQLite to treat the backslashes in the pattern as
|
|
641
|
+
// escape characters rather than literals; SQLite has no default escape character.
|
|
642
|
+
const matchingConversations = db
|
|
643
|
+
.select({
|
|
644
|
+
id: conversations.id,
|
|
645
|
+
title: conversations.title,
|
|
646
|
+
updatedAt: conversations.updatedAt,
|
|
647
|
+
})
|
|
648
|
+
.from(conversations)
|
|
649
|
+
.leftJoin(messages, eq(messages.conversationId, conversations.id))
|
|
650
|
+
.where(
|
|
651
|
+
and(
|
|
652
|
+
sql`${conversations.threadType} != 'background'`,
|
|
653
|
+
or(
|
|
654
|
+
sql`${messages.content} LIKE ${pattern} ESCAPE '\\'`,
|
|
655
|
+
sql`${conversations.title} LIKE ${pattern} ESCAPE '\\'`,
|
|
656
|
+
),
|
|
657
|
+
),
|
|
658
|
+
)
|
|
659
|
+
.groupBy(conversations.id)
|
|
660
|
+
.orderBy(desc(conversations.updatedAt))
|
|
661
|
+
.limit(limit)
|
|
662
|
+
.all();
|
|
663
|
+
|
|
664
|
+
if (matchingConversations.length === 0) return [];
|
|
665
|
+
|
|
666
|
+
const results: ConversationSearchResult[] = [];
|
|
667
|
+
|
|
668
|
+
for (const conv of matchingConversations) {
|
|
669
|
+
// Fetch the top matching messages for this conversation.
|
|
670
|
+
const matchingMsgs = db
|
|
671
|
+
.select({
|
|
672
|
+
id: messages.id,
|
|
673
|
+
role: messages.role,
|
|
674
|
+
content: messages.content,
|
|
675
|
+
createdAt: messages.createdAt,
|
|
676
|
+
})
|
|
677
|
+
.from(messages)
|
|
678
|
+
.where(
|
|
679
|
+
and(
|
|
680
|
+
eq(messages.conversationId, conv.id),
|
|
681
|
+
// ESCAPE '\\' required so backslashes in the pattern act as escape characters.
|
|
682
|
+
sql`${messages.content} LIKE ${pattern} ESCAPE '\\'`,
|
|
683
|
+
),
|
|
684
|
+
)
|
|
685
|
+
.orderBy(asc(messages.createdAt))
|
|
686
|
+
.limit(maxMsgsPerConv)
|
|
687
|
+
.all();
|
|
688
|
+
|
|
689
|
+
results.push({
|
|
690
|
+
conversationId: conv.id,
|
|
691
|
+
conversationTitle: conv.title,
|
|
692
|
+
conversationUpdatedAt: conv.updatedAt,
|
|
693
|
+
matchingMessages: matchingMsgs.map((m) => ({
|
|
694
|
+
messageId: m.id,
|
|
695
|
+
role: m.role,
|
|
696
|
+
excerpt: buildExcerpt(m.content, query),
|
|
697
|
+
createdAt: m.createdAt,
|
|
698
|
+
})),
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return results;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Build a short excerpt from raw message content centered around the first
|
|
707
|
+
* occurrence of `query`. The content may be JSON (content blocks) or plain
|
|
708
|
+
* text; we extract a readable snippet in either case.
|
|
709
|
+
*/
|
|
710
|
+
function buildExcerpt(rawContent: string, query: string): string {
|
|
711
|
+
// Try to extract plain text from JSON content blocks first.
|
|
712
|
+
let text = rawContent;
|
|
713
|
+
try {
|
|
714
|
+
const parsed = JSON.parse(rawContent);
|
|
715
|
+
if (Array.isArray(parsed)) {
|
|
716
|
+
const parts: string[] = [];
|
|
717
|
+
for (const block of parsed) {
|
|
718
|
+
if (typeof block === 'object' && block != null) {
|
|
719
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
720
|
+
parts.push(block.text);
|
|
721
|
+
} else if (block.type === 'tool_result') {
|
|
722
|
+
const inner = Array.isArray(block.content) ? block.content : [];
|
|
723
|
+
for (const ib of inner) {
|
|
724
|
+
if (ib?.type === 'text' && typeof ib.text === 'string') parts.push(ib.text);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (parts.length > 0) text = parts.join(' ');
|
|
730
|
+
} else if (typeof parsed === 'string') {
|
|
731
|
+
text = parsed;
|
|
732
|
+
}
|
|
733
|
+
} catch {
|
|
734
|
+
// Not JSON — use as-is
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const WINDOW = 100;
|
|
738
|
+
const lowerText = text.toLowerCase();
|
|
739
|
+
const lowerQuery = query.toLowerCase();
|
|
740
|
+
const idx = lowerText.indexOf(lowerQuery);
|
|
741
|
+
if (idx === -1) {
|
|
742
|
+
// Query matched the raw JSON but not the extracted text — fall back to raw start
|
|
743
|
+
return text.slice(0, WINDOW * 2).replace(/\s+/g, ' ').trim();
|
|
744
|
+
}
|
|
745
|
+
const start = Math.max(0, idx - WINDOW);
|
|
746
|
+
const end = Math.min(text.length, idx + query.length + WINDOW);
|
|
747
|
+
const excerpt = (start > 0 ? '…' : '') + text.slice(start, end).replace(/\s+/g, ' ').trim() + (end < text.length ? '…' : '');
|
|
748
|
+
return excerpt;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function setConversationOriginChannelIfUnset(conversationId: string, channel: ChannelId): void {
|
|
752
|
+
const db = getDb();
|
|
753
|
+
db.update(conversations)
|
|
754
|
+
.set({ originChannel: channel })
|
|
755
|
+
.where(and(eq(conversations.id, conversationId), isNull(conversations.originChannel)))
|
|
756
|
+
.run();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export function getConversationOriginChannel(conversationId: string): ChannelId | null {
|
|
760
|
+
const db = getDb();
|
|
761
|
+
const row = db.select({ originChannel: conversations.originChannel })
|
|
762
|
+
.from(conversations)
|
|
763
|
+
.where(eq(conversations.id, conversationId))
|
|
764
|
+
.get();
|
|
765
|
+
return parseChannelId(row?.originChannel) ?? null;
|
|
766
|
+
}
|
|
@@ -3,26 +3,49 @@ import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
|
3
3
|
import * as schema from './schema.js';
|
|
4
4
|
import { getDbPath, ensureDataDir, migrateToDataLayout, migrateToWorkspaceLayout } from '../util/platform.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
export type DrizzleDb = ReturnType<typeof drizzle<typeof schema>>;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
let db: DrizzleDb | null = null;
|
|
9
|
+
|
|
10
|
+
export function getDb(): DrizzleDb {
|
|
9
11
|
if (!db) {
|
|
10
12
|
migrateToDataLayout();
|
|
11
13
|
migrateToWorkspaceLayout();
|
|
12
14
|
ensureDataDir();
|
|
13
15
|
const sqlite = new Database(getDbPath());
|
|
14
16
|
sqlite.exec('PRAGMA journal_mode=WAL');
|
|
17
|
+
sqlite.exec('PRAGMA synchronous=FULL');
|
|
18
|
+
sqlite.exec('PRAGMA busy_timeout=5000');
|
|
15
19
|
sqlite.exec('PRAGMA foreign_keys = ON');
|
|
20
|
+
sqlite.exec('PRAGMA cache_size=-256000');
|
|
21
|
+
sqlite.exec('PRAGMA temp_store=MEMORY');
|
|
16
22
|
db = drizzle(sqlite, { schema });
|
|
17
23
|
}
|
|
18
24
|
return db;
|
|
19
25
|
}
|
|
20
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Get the underlying bun:sqlite Database from the global Drizzle instance.
|
|
29
|
+
*
|
|
30
|
+
* Use this instead of the raw cast `(db as unknown as { $client: Database }).$client`.
|
|
31
|
+
* See raw-query.ts for typed query helpers and guidelines on when raw SQL is appropriate.
|
|
32
|
+
*/
|
|
33
|
+
export function getSqlite(): Database {
|
|
34
|
+
return getSqliteFrom(getDb());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract the underlying bun:sqlite Database from any Drizzle instance.
|
|
39
|
+
* Useful in migrations and tests that receive the Drizzle instance as a parameter.
|
|
40
|
+
*/
|
|
41
|
+
export function getSqliteFrom(drizzleDb: DrizzleDb): Database {
|
|
42
|
+
return (drizzleDb as unknown as { $client: Database }).$client;
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
/** Reset the db singleton. Used by tests to ensure isolation between test files. */
|
|
22
46
|
export function resetDb(): void {
|
|
23
47
|
if (db) {
|
|
24
|
-
|
|
25
|
-
raw.close();
|
|
48
|
+
getSqliteFrom(db).close();
|
|
26
49
|
db = null;
|
|
27
50
|
}
|
|
28
51
|
}
|