@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,62 +1,91 @@
|
|
|
1
1
|
import { and, desc, eq, inArray, notInArray } from 'drizzle-orm';
|
|
2
2
|
import { getLogger } from '../../util/logger.js';
|
|
3
|
-
import { getDb } from '../db.js';
|
|
3
|
+
import { getDb, rawAll } from '../db.js';
|
|
4
4
|
import { memorySegments } from '../schema.js';
|
|
5
5
|
import type { Candidate, CandidateType } from './types.js';
|
|
6
6
|
import { computeRecencyScore } from './ranking.js';
|
|
7
7
|
|
|
8
8
|
const log = getLogger('memory-retriever');
|
|
9
9
|
|
|
10
|
+
// Threshold beyond which a raw SQL query is considered slow and logged as a warning.
|
|
11
|
+
const SLOW_QUERY_MS = 2000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Execute a synchronous raw SQL query with timing. Logs a warning if the
|
|
15
|
+
* query exceeds SLOW_QUERY_MS. Since bun:sqlite queries are synchronous,
|
|
16
|
+
* we can't abort them mid-execution, but we can detect and report slowness.
|
|
17
|
+
*/
|
|
18
|
+
function timedRawQuery<T>(label: string, fn: () => T[]): T[] {
|
|
19
|
+
const start = performance.now();
|
|
20
|
+
const result = fn();
|
|
21
|
+
const elapsed = performance.now() - start;
|
|
22
|
+
if (elapsed >= SLOW_QUERY_MS) {
|
|
23
|
+
log.warn({ label, elapsedMs: Math.round(elapsed) }, 'Raw SQL query exceeded slow threshold');
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface FtsRow {
|
|
29
|
+
segment_id: string;
|
|
30
|
+
message_id: string;
|
|
31
|
+
text: string;
|
|
32
|
+
created_at: number;
|
|
33
|
+
rank: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
export function lexicalSearch(query: string, limit: number, excludedMessageIds: string[] = [], scopeIds?: string[]): Candidate[] {
|
|
11
37
|
const trimmed = query.trim();
|
|
12
38
|
if (trimmed.length === 0 || limit <= 0) return [];
|
|
13
39
|
const matchQuery = buildFtsMatchQuery(trimmed);
|
|
14
40
|
if (!matchQuery) return [];
|
|
15
41
|
const excluded = new Set(excludedMessageIds);
|
|
16
|
-
const db = getDb();
|
|
17
|
-
const raw = (db as unknown as { $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } } }).$client;
|
|
18
|
-
let rows: Array<{
|
|
19
|
-
segment_id: string;
|
|
20
|
-
message_id: string;
|
|
21
|
-
text: string;
|
|
22
|
-
created_at: number;
|
|
23
|
-
rank: number;
|
|
24
|
-
}> = [];
|
|
25
|
-
const queryLimit = excluded.size > 0
|
|
26
|
-
? Math.max(limit + 24, limit * 2)
|
|
27
|
-
: limit;
|
|
28
42
|
const scopeClause = scopeIds
|
|
29
43
|
? ` AND s.scope_id IN (${scopeIds.map(() => '?').join(',')})`
|
|
30
44
|
: '';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
|
|
46
|
+
// Adaptive overfetch: when exclusions are present, double the SQL LIMIT until we
|
|
47
|
+
// collect `limit` visible rows or the DB has no more matching results to offer.
|
|
48
|
+
// This handles dense exclusion sets without wasteful fixed-ratio over-fetching.
|
|
49
|
+
const MAX_FETCH_MULTIPLIER = 8;
|
|
50
|
+
let queryLimit = excluded.size > 0 ? Math.max(limit * 2, limit + 24) : limit;
|
|
51
|
+
let visibleRows: FtsRow[] = [];
|
|
52
|
+
|
|
53
|
+
while (true) {
|
|
54
|
+
let rows: FtsRow[] = [];
|
|
55
|
+
try {
|
|
56
|
+
rows = timedRawQuery('lexicalSearch', () =>
|
|
57
|
+
rawAll<FtsRow>(`
|
|
58
|
+
SELECT
|
|
59
|
+
f.segment_id AS segment_id,
|
|
60
|
+
s.message_id AS message_id,
|
|
61
|
+
s.text AS text,
|
|
62
|
+
s.created_at AS created_at,
|
|
63
|
+
bm25(memory_segment_fts) AS rank
|
|
64
|
+
FROM memory_segment_fts f
|
|
65
|
+
JOIN memory_segments s ON s.id = f.segment_id
|
|
66
|
+
WHERE memory_segment_fts MATCH ?${scopeClause}
|
|
67
|
+
ORDER BY rank
|
|
68
|
+
LIMIT ?
|
|
69
|
+
`, matchQuery, ...(scopeIds ?? []), queryLimit),
|
|
70
|
+
);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
log.warn({ err, query: truncate(trimmed, 80) }, 'Memory lexical search query parse failed');
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
visibleRows = excluded.size > 0
|
|
77
|
+
? rows.filter((row) => !excluded.has(row.message_id))
|
|
78
|
+
: rows;
|
|
79
|
+
|
|
80
|
+
// Stop when we have enough rows, the DB returned fewer than requested
|
|
81
|
+
// (no more results exist), or we've reached the safety cap.
|
|
82
|
+
if (visibleRows.length >= limit || rows.length < queryLimit || queryLimit >= limit * MAX_FETCH_MULTIPLIER) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
queryLimit = Math.min(queryLimit * 2, limit * MAX_FETCH_MULTIPLIER);
|
|
55
86
|
}
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
? rows.filter((row) => !excluded.has(row.message_id)).slice(0, limit)
|
|
59
|
-
: rows;
|
|
88
|
+
visibleRows = visibleRows.slice(0, limit);
|
|
60
89
|
|
|
61
90
|
const finiteRanks = visibleRows
|
|
62
91
|
.map((row) => row.rank)
|
|
@@ -124,17 +153,19 @@ export function recencySearch(conversationId: string, limit: number, excludedMes
|
|
|
124
153
|
* Supplements FTS-based lexical search with LIKE-based matching on items.
|
|
125
154
|
*/
|
|
126
155
|
export function directItemSearch(query: string, limit: number, scopeIds?: string[]): Candidate[] {
|
|
127
|
-
const db = getDb();
|
|
128
156
|
const tokens = [...new Set(query
|
|
129
157
|
.toLowerCase()
|
|
130
158
|
.split(/[^a-z0-9_.-]+/g)
|
|
131
159
|
.filter((t) => t.length >= 2))];
|
|
132
160
|
if (tokens.length === 0) return [];
|
|
133
161
|
|
|
134
|
-
const raw = (db as unknown as { $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } } }).$client;
|
|
135
162
|
const likeClauses = tokens.map(
|
|
136
|
-
(
|
|
163
|
+
() => `(LOWER(subject) LIKE ? OR LOWER(statement) LIKE ?)`,
|
|
137
164
|
);
|
|
165
|
+
const likeParams = tokens.flatMap((t) => {
|
|
166
|
+
const pattern = `%${escapeLikeWildcards(t)}%`;
|
|
167
|
+
return [pattern, pattern];
|
|
168
|
+
});
|
|
138
169
|
const scopeClause = scopeIds
|
|
139
170
|
? ` AND scope_id IN (${scopeIds.map(() => '?').join(',')})`
|
|
140
171
|
: '';
|
|
@@ -145,9 +176,8 @@ export function directItemSearch(query: string, limit: number, scopeIds?: string
|
|
|
145
176
|
ORDER BY last_seen_at DESC
|
|
146
177
|
LIMIT ?
|
|
147
178
|
`;
|
|
148
|
-
const params: unknown[] = [...(scopeIds ?? []), limit];
|
|
149
179
|
|
|
150
|
-
|
|
180
|
+
interface ItemRow {
|
|
151
181
|
id: string;
|
|
152
182
|
kind: string;
|
|
153
183
|
subject: string;
|
|
@@ -156,9 +186,13 @@ export function directItemSearch(query: string, limit: number, scopeIds?: string
|
|
|
156
186
|
importance: number | null;
|
|
157
187
|
first_seen_at: number;
|
|
158
188
|
last_seen_at: number;
|
|
159
|
-
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let rows: ItemRow[] = [];
|
|
160
192
|
try {
|
|
161
|
-
rows =
|
|
193
|
+
rows = timedRawQuery('directItemSearch', () =>
|
|
194
|
+
rawAll<ItemRow>(sqlQuery, ...likeParams, ...(scopeIds ?? []), limit),
|
|
195
|
+
);
|
|
162
196
|
} catch {
|
|
163
197
|
return [];
|
|
164
198
|
}
|
|
@@ -212,11 +246,6 @@ export function buildFtsMatchQuery(text: string): string | null {
|
|
|
212
246
|
.join(' OR ');
|
|
213
247
|
}
|
|
214
248
|
|
|
215
|
-
export function escapeSqlLike(s: string): string {
|
|
216
|
-
return s.replace(/'/g, "''").replace(/%/g, '').replace(/_/g, '');
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Escape only LIKE wildcards (% and _) for use with parameterized queries where the driver handles quoting. */
|
|
220
249
|
export function escapeLikeWildcards(s: string): string {
|
|
221
250
|
return s.replace(/%/g, '').replace(/_/g, '');
|
|
222
251
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { inArray, sql } from 'drizzle-orm';
|
|
2
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
3
2
|
import type { AssistantConfig, MemoryRerankingConfig } from '../../config/types.js';
|
|
4
|
-
import { getConfig } from '../../config/loader.js';
|
|
5
3
|
import { estimateTextTokens } from '../../context/token-estimator.js';
|
|
6
4
|
import { getLogger } from '../../util/logger.js';
|
|
5
|
+
import { getConfiguredProvider, extractText, userMessage } from '../../providers/provider-send-message.js';
|
|
7
6
|
import { getDb } from '../db.js';
|
|
8
7
|
import { memoryItems } from '../schema.js';
|
|
9
8
|
import type { Candidate, CandidateSource, ItemMetadata } from './types.js';
|
|
@@ -53,6 +52,7 @@ export function mergeCandidates(
|
|
|
53
52
|
entity: Candidate[] = [],
|
|
54
53
|
freshnessConfig?: { enabled: boolean; maxAgeDays: Record<string, number>; staleDecay: number; reinforcementShieldDays: number },
|
|
55
54
|
relationScoreMultiplier?: number,
|
|
55
|
+
candidateDepthMap?: Map<string, number>,
|
|
56
56
|
): Candidate[] {
|
|
57
57
|
// Build effective weight map that reflects the actual scoring weight for
|
|
58
58
|
// each source. For entity_relation the static SOURCE_WEIGHTS entry is 1.0
|
|
@@ -126,7 +126,11 @@ export function mergeCandidates(
|
|
|
126
126
|
const lastUsedAt = meta?.lastUsedAt ?? null;
|
|
127
127
|
const freshnessWeight = computeFreshnessWeight(row, accessCount, lastUsedAt, freshnessConfig);
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
let sourceWeight = effectiveWeights[row.source] ?? 1.0;
|
|
130
|
+
if (row.source === 'entity_relation' && candidateDepthMap && relationScoreMultiplier != null) {
|
|
131
|
+
const depth = candidateDepthMap.get(row.key) ?? 1;
|
|
132
|
+
sourceWeight = Math.pow(relationScoreMultiplier, depth);
|
|
133
|
+
}
|
|
130
134
|
row.finalScore = rrfScore * (0.5 + 0.5 * effectiveImportance) * trustWeight * freshnessWeight * sourceWeight;
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -291,10 +295,9 @@ export async function rerankWithLLM(
|
|
|
291
295
|
candidates: Candidate[],
|
|
292
296
|
rerankingConfig: MemoryRerankingConfig,
|
|
293
297
|
): Promise<Candidate[]> {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
log.debug('No Anthropic API key available for LLM re-ranking, skipping');
|
|
298
|
+
const provider = getConfiguredProvider();
|
|
299
|
+
if (!provider) {
|
|
300
|
+
log.debug('Configured provider unavailable for LLM re-ranking, skipping');
|
|
298
301
|
return candidates;
|
|
299
302
|
}
|
|
300
303
|
|
|
@@ -304,26 +307,27 @@ export async function rerankWithLLM(
|
|
|
304
307
|
text: truncate(c.text, 200),
|
|
305
308
|
}));
|
|
306
309
|
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
310
|
+
const response = await provider.sendMessage(
|
|
311
|
+
[userMessage(`Query: ${truncate(query, 200)}\n\nCandidates:\n${candidateList.map((c) => `[${c.index}] ${c.text}`).join('\n')}`)],
|
|
312
|
+
undefined,
|
|
313
|
+
'You are a relevance scoring assistant. Given a query and a list of memory candidates, rate each candidate\'s relevance to the query on a scale of 0-10. Return ONLY a JSON array of objects with "index" (the candidate index) and "score" (0-10 integer). No explanation.',
|
|
314
|
+
{
|
|
315
|
+
config: {
|
|
316
|
+
model: rerankingConfig.model,
|
|
317
|
+
max_tokens: 1024,
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
);
|
|
317
321
|
|
|
318
322
|
// Extract text from the response
|
|
319
|
-
const
|
|
320
|
-
if (!
|
|
323
|
+
const responseText = extractText(response);
|
|
324
|
+
if (!responseText) {
|
|
321
325
|
log.warn('LLM re-ranking returned no text block, skipping');
|
|
322
326
|
return candidates;
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
// Parse the JSON array from the response
|
|
326
|
-
const jsonMatch =
|
|
330
|
+
const jsonMatch = responseText.match(/\[[\s\S]*\]/);
|
|
327
331
|
if (!jsonMatch) {
|
|
328
332
|
log.warn('LLM re-ranking response did not contain JSON array, skipping');
|
|
329
333
|
return candidates;
|
|
@@ -354,10 +358,10 @@ export async function rerankWithLLM(
|
|
|
354
358
|
|
|
355
359
|
reranked.sort((a, b) => {
|
|
356
360
|
// Scored items come before unscored items
|
|
357
|
-
if (a.llmScore
|
|
358
|
-
if (a.llmScore
|
|
361
|
+
if (a.llmScore != null && b.llmScore == null) return -1;
|
|
362
|
+
if (a.llmScore == null && b.llmScore != null) return 1;
|
|
359
363
|
// Both scored: sort by score descending
|
|
360
|
-
if (a.llmScore
|
|
364
|
+
if (a.llmScore != null && b.llmScore != null) {
|
|
361
365
|
const scoreDelta = b.llmScore - a.llmScore;
|
|
362
366
|
if (scoreDelta !== 0) return scoreDelta;
|
|
363
367
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inArray } from 'drizzle-orm';
|
|
2
2
|
import { getDb } from '../db.js';
|
|
3
3
|
import { getQdrantClient } from '../qdrant-client.js';
|
|
4
|
+
import type { QdrantSearchResult } from '../qdrant-client.js';
|
|
4
5
|
import {
|
|
5
6
|
memoryItems,
|
|
6
7
|
memoryItemSources,
|
|
@@ -9,6 +10,84 @@ import {
|
|
|
9
10
|
} from '../schema.js';
|
|
10
11
|
import type { Candidate } from './types.js';
|
|
11
12
|
import { computeRecencyScore } from './ranking.js';
|
|
13
|
+
import { getLogger } from '../../util/logger.js';
|
|
14
|
+
|
|
15
|
+
const log = getLogger('qdrant-circuit-breaker');
|
|
16
|
+
|
|
17
|
+
// ── Qdrant circuit breaker ───────────────────────────────────────────
|
|
18
|
+
// After FAILURE_THRESHOLD consecutive Qdrant failures, stop sending
|
|
19
|
+
// requests (open state). After COOLDOWN_MS, allow a single probe
|
|
20
|
+
// request (half-open). If the probe succeeds, close the circuit; if it
|
|
21
|
+
// fails, re-open and restart the cooldown.
|
|
22
|
+
|
|
23
|
+
const FAILURE_THRESHOLD = 5;
|
|
24
|
+
const COOLDOWN_MS = 60_000;
|
|
25
|
+
|
|
26
|
+
type BreakerState = 'closed' | 'open' | 'half-open';
|
|
27
|
+
|
|
28
|
+
let breakerState: BreakerState = 'closed';
|
|
29
|
+
let consecutiveFailures = 0;
|
|
30
|
+
let openedAt = 0;
|
|
31
|
+
// Ensures only one request passes through during half-open state
|
|
32
|
+
let halfOpenProbeInFlight = false;
|
|
33
|
+
|
|
34
|
+
function qdrantBreakerAllows(): boolean {
|
|
35
|
+
if (breakerState === 'closed') return true;
|
|
36
|
+
if (breakerState === 'open') {
|
|
37
|
+
if (Date.now() - openedAt >= COOLDOWN_MS) {
|
|
38
|
+
breakerState = 'half-open';
|
|
39
|
+
halfOpenProbeInFlight = true;
|
|
40
|
+
log.info('Qdrant circuit breaker entering half-open state — allowing probe request');
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// half-open: only allow through if no probe is already in flight
|
|
46
|
+
if (halfOpenProbeInFlight) return false;
|
|
47
|
+
halfOpenProbeInFlight = true;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function qdrantBreakerRecordSuccess(): void {
|
|
52
|
+
if (breakerState !== 'closed') {
|
|
53
|
+
log.info({ previousFailures: consecutiveFailures }, 'Qdrant circuit breaker closed — search succeeded');
|
|
54
|
+
}
|
|
55
|
+
consecutiveFailures = 0;
|
|
56
|
+
breakerState = 'closed';
|
|
57
|
+
openedAt = 0;
|
|
58
|
+
halfOpenProbeInFlight = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function qdrantBreakerRecordFailure(): void {
|
|
62
|
+
consecutiveFailures++;
|
|
63
|
+
halfOpenProbeInFlight = false;
|
|
64
|
+
if (consecutiveFailures >= FAILURE_THRESHOLD) {
|
|
65
|
+
breakerState = 'open';
|
|
66
|
+
openedAt = Date.now();
|
|
67
|
+
log.warn(
|
|
68
|
+
{ consecutiveFailures, cooldownMs: COOLDOWN_MS },
|
|
69
|
+
'Qdrant circuit breaker opened — semantic search disabled until probe succeeds',
|
|
70
|
+
);
|
|
71
|
+
} else if (breakerState === 'half-open') {
|
|
72
|
+
// Probe failed — re-open
|
|
73
|
+
breakerState = 'open';
|
|
74
|
+
openedAt = Date.now();
|
|
75
|
+
log.warn('Qdrant circuit breaker re-opened — half-open probe failed');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @internal Test-only: reset circuit breaker state */
|
|
80
|
+
export function _resetQdrantBreaker(): void {
|
|
81
|
+
breakerState = 'closed';
|
|
82
|
+
consecutiveFailures = 0;
|
|
83
|
+
openedAt = 0;
|
|
84
|
+
halfOpenProbeInFlight = false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @internal Test-only: get breaker state */
|
|
88
|
+
export function _getQdrantBreakerState(): { state: BreakerState; consecutiveFailures: number } {
|
|
89
|
+
return { state: breakerState, consecutiveFailures };
|
|
90
|
+
}
|
|
12
91
|
|
|
13
92
|
export async function semanticSearch(
|
|
14
93
|
queryVector: number[],
|
|
@@ -20,18 +99,79 @@ export async function semanticSearch(
|
|
|
20
99
|
): Promise<Candidate[]> {
|
|
21
100
|
if (limit <= 0) return [];
|
|
22
101
|
|
|
102
|
+
// Circuit breaker: throw so the caller's .catch() marks the result as degraded
|
|
103
|
+
if (!qdrantBreakerAllows()) {
|
|
104
|
+
log.debug('Qdrant circuit breaker open — skipping semantic search');
|
|
105
|
+
throw new Error('Qdrant circuit breaker open');
|
|
106
|
+
}
|
|
107
|
+
|
|
23
108
|
const qdrant = getQdrantClient();
|
|
24
109
|
|
|
25
110
|
// Overfetch to account for items filtered out post-query (invalidated, excluded, etc.)
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
111
|
+
// Use 3x when exclusions are active to ensure enough results survive filtering
|
|
112
|
+
const overfetchMultiplier = excludedMessageIds.length > 0 ? 3 : 2;
|
|
113
|
+
const fetchLimit = limit * overfetchMultiplier;
|
|
114
|
+
let results: QdrantSearchResult[];
|
|
115
|
+
try {
|
|
116
|
+
results = await qdrant.searchWithFilter(
|
|
117
|
+
queryVector,
|
|
118
|
+
fetchLimit,
|
|
119
|
+
['item', 'summary', 'segment'],
|
|
120
|
+
excludedMessageIds,
|
|
121
|
+
);
|
|
122
|
+
qdrantBreakerRecordSuccess();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
qdrantBreakerRecordFailure();
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
33
127
|
|
|
34
128
|
const db = getDb();
|
|
129
|
+
|
|
130
|
+
// Batch-fetch all backing records upfront to avoid N+1 queries per result
|
|
131
|
+
const itemTargetIds: string[] = [];
|
|
132
|
+
const summaryTargetIds: string[] = [];
|
|
133
|
+
const segmentTargetIds: string[] = [];
|
|
134
|
+
for (const r of results) {
|
|
135
|
+
if (r.payload.target_type === 'item') itemTargetIds.push(r.payload.target_id);
|
|
136
|
+
else if (r.payload.target_type === 'summary') summaryTargetIds.push(r.payload.target_id);
|
|
137
|
+
else segmentTargetIds.push(r.payload.target_id);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const itemsMap = new Map<string, typeof memoryItems.$inferSelect>();
|
|
141
|
+
if (itemTargetIds.length > 0) {
|
|
142
|
+
const allItems = db.select().from(memoryItems).where(inArray(memoryItems.id, itemTargetIds)).all();
|
|
143
|
+
for (const item of allItems) itemsMap.set(item.id, item);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sourcesMap = new Map<string, string[]>();
|
|
147
|
+
if (itemTargetIds.length > 0) {
|
|
148
|
+
const allSources = db.select({
|
|
149
|
+
memoryItemId: memoryItemSources.memoryItemId,
|
|
150
|
+
messageId: memoryItemSources.messageId,
|
|
151
|
+
}).from(memoryItemSources)
|
|
152
|
+
.where(inArray(memoryItemSources.memoryItemId, itemTargetIds))
|
|
153
|
+
.all();
|
|
154
|
+
for (const s of allSources) {
|
|
155
|
+
const existing = sourcesMap.get(s.memoryItemId);
|
|
156
|
+
if (existing) existing.push(s.messageId);
|
|
157
|
+
else sourcesMap.set(s.memoryItemId, [s.messageId]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const summariesMap = new Map<string, typeof memorySummaries.$inferSelect>();
|
|
162
|
+
if (scopeIds && summaryTargetIds.length > 0) {
|
|
163
|
+
const allSummaries = db.select().from(memorySummaries).where(inArray(memorySummaries.id, summaryTargetIds)).all();
|
|
164
|
+
for (const s of allSummaries) summariesMap.set(s.id, s);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const segmentsMap = new Map<string, typeof memorySegments.$inferSelect>();
|
|
168
|
+
if (scopeIds && segmentTargetIds.length > 0) {
|
|
169
|
+
const allSegments = db.select().from(memorySegments).where(inArray(memorySegments.id, segmentTargetIds)).all();
|
|
170
|
+
for (const seg of allSegments) segmentsMap.set(seg.id, seg);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const excludedSet = excludedMessageIds.length > 0 ? new Set(excludedMessageIds) : null;
|
|
174
|
+
|
|
35
175
|
const candidates: Candidate[] = [];
|
|
36
176
|
for (const result of results) {
|
|
37
177
|
const { payload, score } = result;
|
|
@@ -39,16 +179,14 @@ export async function semanticSearch(
|
|
|
39
179
|
const createdAt = payload.created_at ?? Date.now();
|
|
40
180
|
|
|
41
181
|
if (payload.target_type === 'item') {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!item || item.status !== 'active' || item.invalidAt !== null) continue;
|
|
182
|
+
const item = itemsMap.get(payload.target_id);
|
|
183
|
+
if (!item || item.status !== 'active' || item.invalidAt != null) continue;
|
|
45
184
|
if (scopeIds && !scopeIds.includes(item.scopeId)) continue;
|
|
46
|
-
const sources =
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (nonExcluded.length === 0) continue;
|
|
185
|
+
const sources = sourcesMap.get(payload.target_id);
|
|
186
|
+
if (!sources || sources.length === 0) continue;
|
|
187
|
+
if (excludedSet) {
|
|
188
|
+
const hasNonExcluded = sources.some((msgId) => !excludedSet.has(msgId));
|
|
189
|
+
if (!hasNonExcluded) continue;
|
|
52
190
|
}
|
|
53
191
|
candidates.push({
|
|
54
192
|
key: `item:${payload.target_id}`,
|
|
@@ -67,7 +205,7 @@ export async function semanticSearch(
|
|
|
67
205
|
});
|
|
68
206
|
} else if (payload.target_type === 'summary') {
|
|
69
207
|
if (scopeIds) {
|
|
70
|
-
const summary =
|
|
208
|
+
const summary = summariesMap.get(payload.target_id);
|
|
71
209
|
if (!summary || !scopeIds.includes(summary.scopeId)) continue;
|
|
72
210
|
}
|
|
73
211
|
candidates.push({
|
|
@@ -87,7 +225,7 @@ export async function semanticSearch(
|
|
|
87
225
|
});
|
|
88
226
|
} else {
|
|
89
227
|
if (scopeIds) {
|
|
90
|
-
const segment =
|
|
228
|
+
const segment = segmentsMap.get(payload.target_id);
|
|
91
229
|
if (!segment || !scopeIds.includes(segment.scopeId)) continue;
|
|
92
230
|
}
|
|
93
231
|
candidates.push({
|
|
@@ -105,6 +105,7 @@ export interface EntitySearchResult {
|
|
|
105
105
|
relationTraversedEdgeCount: number;
|
|
106
106
|
relationNeighborEntityCount: number;
|
|
107
107
|
relationExpandedItemCount: number;
|
|
108
|
+
candidateDepths?: Map<string, number>; // candidate key → BFS hop depth (1-based)
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
export interface MatchedEntityRow {
|
|
@@ -137,3 +138,26 @@ export interface MemorySearchResult {
|
|
|
137
138
|
recency: number;
|
|
138
139
|
};
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
import type { EntityRelationType, EntityType } from '../entity-extractor.js';
|
|
143
|
+
|
|
144
|
+
export interface TraversalOptions {
|
|
145
|
+
maxEdges: number;
|
|
146
|
+
maxNeighborEntities: number;
|
|
147
|
+
maxDepth?: number; // default 3
|
|
148
|
+
relationTypes?: EntityRelationType[];
|
|
149
|
+
entityTypes?: EntityType[];
|
|
150
|
+
/** When true, only follow source→target edges (frontier must be on source side). */
|
|
151
|
+
directed?: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface TraversalResult {
|
|
155
|
+
neighborEntityIds: string[];
|
|
156
|
+
traversedEdgeCount: number;
|
|
157
|
+
neighborDepths: Map<string, number>; // entityId → depth (1-based)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface TraversalStep {
|
|
161
|
+
relationTypes?: EntityRelationType[];
|
|
162
|
+
entityTypes?: EntityType[];
|
|
163
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { eq, lte, or, and, isNull } from 'drizzle-orm';
|
|
8
8
|
import { randomUUID, randomBytes } from 'node:crypto';
|
|
9
|
-
import { getDb } from './db.js';
|
|
9
|
+
import { getDb, rawRun } from './db.js';
|
|
10
10
|
import { sharedAppLinks } from './schema.js';
|
|
11
11
|
import type { AppManifest } from '../bundler/manifest.js';
|
|
12
12
|
|
|
@@ -130,9 +130,8 @@ export function deleteSharedAppLinkByToken(shareToken: string): boolean {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export function incrementDownloadCount(shareToken: string): void {
|
|
133
|
-
|
|
134
|
-
const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
135
|
-
raw.prepare(
|
|
133
|
+
rawRun(
|
|
136
134
|
`UPDATE shared_app_links SET download_count = download_count + 1 WHERE share_token = ?`,
|
|
137
|
-
|
|
135
|
+
shareToken,
|
|
136
|
+
);
|
|
138
137
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unit interval [0, 1] — used for confidence and importance fields on memory items.
|
|
5
|
+
* Coerces out-of-range numbers to the nearest bound rather than rejecting,
|
|
6
|
+
* since LLM-generated values occasionally exceed the range.
|
|
7
|
+
*/
|
|
8
|
+
export const unitInterval = z.number().transform((v) => Math.min(1, Math.max(0, v)));
|
|
9
|
+
|
|
10
|
+
/** Zod schema for validating confidence/importance values on memory items. */
|
|
11
|
+
export const memoryItemScores = z.object({
|
|
12
|
+
confidence: unitInterval,
|
|
13
|
+
importance: unitInterval,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Clamp a numeric value to [0, 1]. */
|
|
17
|
+
export function clampUnitInterval(value: number): number {
|
|
18
|
+
return Math.min(1, Math.max(0, value));
|
|
19
|
+
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* Works across all platforms — Slack, Gmail, Discord, etc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { readFileSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
+
import { ensureDir, pathExists } from '../util/fs.js';
|
|
10
11
|
import { randomUUID } from 'node:crypto';
|
|
11
12
|
import { getRootDir } from '../util/platform.js';
|
|
12
13
|
|
|
@@ -24,9 +25,7 @@ export interface Draft {
|
|
|
24
25
|
|
|
25
26
|
function getDraftsDir(platform: string): string {
|
|
26
27
|
const dir = join(getRootDir(), 'workspace', 'data', 'drafts', platform);
|
|
27
|
-
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
29
|
-
}
|
|
28
|
+
ensureDir(dir);
|
|
30
29
|
return dir;
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -61,7 +60,7 @@ export function createDraft(opts: {
|
|
|
61
60
|
|
|
62
61
|
export function getDraft(platform: string, id: string): Draft | null {
|
|
63
62
|
const path = getDraftPath(platform, id);
|
|
64
|
-
if (!
|
|
63
|
+
if (!pathExists(path)) return null;
|
|
65
64
|
return JSON.parse(readFileSync(path, 'utf-8')) as Draft;
|
|
66
65
|
}
|
|
67
66
|
|
|
@@ -82,7 +81,7 @@ export function listDrafts(platform: string): Draft[] {
|
|
|
82
81
|
|
|
83
82
|
export function deleteDraft(platform: string, id: string): boolean {
|
|
84
83
|
const path = getDraftPath(platform, id);
|
|
85
|
-
if (!
|
|
84
|
+
if (!pathExists(path)) return false;
|
|
86
85
|
unlinkSync(path);
|
|
87
86
|
return true;
|
|
88
87
|
}
|