@vellumai/assistant 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +27 -0
- package/.env.example +22 -0
- package/Dockerfile +99 -0
- package/Dockerfile.sandbox +5 -0
- package/README.md +248 -0
- package/bun.lock +1723 -0
- package/bunfig.toml +2 -0
- package/docs/skills.md +158 -0
- package/drizzle/0000_dizzy_maggott.sql +301 -0
- package/drizzle/meta/0000_snapshot.json +1999 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/eslint.config.mjs +17 -0
- package/hook-templates/debug-prompt-logger/hook.json +7 -0
- package/hook-templates/debug-prompt-logger/run.sh +68 -0
- package/knip.json +9 -0
- package/package.json +70 -0
- package/scripts/capture-x-graphql.ts +545 -0
- package/scripts/ipc/check-contract-inventory.ts +104 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +166 -0
- package/scripts/ipc/generate-swift.ts +492 -0
- package/scripts/test-filesystem-tools.sh +48 -0
- package/scripts/test.sh +127 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2485 -0
- package/src/__tests__/account-registry.test.ts +245 -0
- package/src/__tests__/active-skill-tools.test.ts +378 -0
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/agent-loop-thinking.test.ts +81 -0
- package/src/__tests__/agent-loop.test.ts +1135 -0
- package/src/__tests__/anthropic-provider.test.ts +778 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
- package/src/__tests__/app-bundler.test.ts +292 -0
- package/src/__tests__/app-executors.test.ts +613 -0
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/app-open-proxy.test.ts +62 -0
- package/src/__tests__/asset-materialize-tool.test.ts +452 -0
- package/src/__tests__/asset-search-tool.test.ts +477 -0
- package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
- package/src/__tests__/assistant-attachments.test.ts +437 -0
- package/src/__tests__/assistant-event-hub.test.ts +226 -0
- package/src/__tests__/assistant-event.test.ts +123 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/attachments-store.test.ts +476 -0
- package/src/__tests__/attachments.test.ts +134 -0
- package/src/__tests__/audit-log-rotation.test.ts +154 -0
- package/src/__tests__/browser-fill-credential.test.ts +309 -0
- package/src/__tests__/browser-manager.test.ts +203 -0
- package/src/__tests__/browser-runtime-check.test.ts +55 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +68 -0
- package/src/__tests__/browser-skill-endstate.test.ts +195 -0
- package/src/__tests__/bundle-scanner.test.ts +313 -0
- package/src/__tests__/call-bridge.test.ts +517 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +625 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +699 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +174 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/channel-approval-routes.test.ts +2356 -0
- package/src/__tests__/channel-approval.test.ts +299 -0
- package/src/__tests__/channel-approvals.test.ts +521 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/channel-guardian.test.ts +1005 -0
- package/src/__tests__/checker.test.ts +3519 -0
- package/src/__tests__/clarification-resolver.test.ts +159 -0
- package/src/__tests__/classifier.test.ts +67 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
- package/src/__tests__/cli-discover.test.ts +85 -0
- package/src/__tests__/cli.test.ts +26 -0
- package/src/__tests__/clipboard.test.ts +80 -0
- package/src/__tests__/commit-guarantee.test.ts +335 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
- package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
- package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +1462 -0
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +332 -0
- package/src/__tests__/connection-policy.test.ts +102 -0
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/context-memory-e2e.test.ts +434 -0
- package/src/__tests__/context-token-estimator.test.ts +135 -0
- package/src/__tests__/context-window-manager.test.ts +376 -0
- package/src/__tests__/contradiction-checker.test.ts +314 -0
- package/src/__tests__/conversation-store.test.ts +612 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
- package/src/__tests__/credential-broker-server-use.test.ts +554 -0
- package/src/__tests__/credential-broker.test.ts +167 -0
- package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
- package/src/__tests__/credential-metadata-store.test.ts +779 -0
- package/src/__tests__/credential-policy-validate.test.ts +121 -0
- package/src/__tests__/credential-resolve.test.ts +328 -0
- package/src/__tests__/credential-security-e2e.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +583 -0
- package/src/__tests__/credential-selection.test.ts +354 -0
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/credential-vault.test.ts +852 -0
- package/src/__tests__/daemon-assistant-events.test.ts +164 -0
- package/src/__tests__/daemon-server-session-init.test.ts +522 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
- package/src/__tests__/diff.test.ts +121 -0
- package/src/__tests__/domain-normalize.test.ts +112 -0
- package/src/__tests__/domain-policy.test.ts +124 -0
- package/src/__tests__/doordash-client.test.ts +186 -0
- package/src/__tests__/doordash-session.test.ts +152 -0
- package/src/__tests__/dynamic-page-surface.test.ts +91 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
- package/src/__tests__/edit-engine.test.ts +180 -0
- package/src/__tests__/elevenlabs-client.test.ts +271 -0
- package/src/__tests__/email-cli.test.ts +283 -0
- package/src/__tests__/encrypted-store.test.ts +332 -0
- package/src/__tests__/entity-extractor.test.ts +190 -0
- package/src/__tests__/ephemeral-permissions.test.ts +362 -0
- package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
- package/src/__tests__/event-bus.test.ts +222 -0
- package/src/__tests__/file-edit-tool.test.ts +122 -0
- package/src/__tests__/file-ops-service.test.ts +330 -0
- package/src/__tests__/file-read-tool.test.ts +75 -0
- package/src/__tests__/file-write-tool.test.ts +113 -0
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
- package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
- package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +71 -0
- package/src/__tests__/fuzzy-match-property.test.ts +216 -0
- package/src/__tests__/fuzzy-match.test.ts +138 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +631 -0
- package/src/__tests__/gemini-image-service.test.ts +261 -0
- package/src/__tests__/gemini-provider.test.ts +651 -0
- package/src/__tests__/get-weather.test.ts +318 -0
- package/src/__tests__/gmail-integration.test.ts +73 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +352 -0
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +191 -0
- package/src/__tests__/handlers-slack-config.test.ts +200 -0
- package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
- package/src/__tests__/handlers-telegram-config.test.ts +968 -0
- package/src/__tests__/handlers-twilio-config.test.ts +659 -0
- package/src/__tests__/handlers-twitter-config.test.ts +858 -0
- package/src/__tests__/headless-browser-interactions.test.ts +536 -0
- package/src/__tests__/headless-browser-navigate.test.ts +211 -0
- package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
- package/src/__tests__/history-repair-observability.test.ts +56 -0
- package/src/__tests__/history-repair.test.ts +510 -0
- package/src/__tests__/home-base-bootstrap.test.ts +82 -0
- package/src/__tests__/hooks-blocking.test.ts +128 -0
- package/src/__tests__/hooks-cli.test.ts +144 -0
- package/src/__tests__/hooks-config.test.ts +93 -0
- package/src/__tests__/hooks-discovery.test.ts +199 -0
- package/src/__tests__/hooks-integration.test.ts +189 -0
- package/src/__tests__/hooks-manager.test.ts +187 -0
- package/src/__tests__/hooks-runner.test.ts +182 -0
- package/src/__tests__/hooks-settings.test.ts +154 -0
- package/src/__tests__/hooks-templates.test.ts +137 -0
- package/src/__tests__/hooks-ts-runner.test.ts +125 -0
- package/src/__tests__/hooks-watch.test.ts +100 -0
- package/src/__tests__/host-file-edit-tool.test.ts +228 -0
- package/src/__tests__/host-file-read-tool.test.ts +123 -0
- package/src/__tests__/host-file-write-tool.test.ts +136 -0
- package/src/__tests__/host-shell-tool.test.ts +562 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ingress-url-consistency.test.ts +214 -0
- package/src/__tests__/intent-routing.test.ts +259 -0
- package/src/__tests__/ipc-blob-store.test.ts +315 -0
- package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
- package/src/__tests__/ipc-contract.test.ts +74 -0
- package/src/__tests__/ipc-protocol.test.ts +113 -0
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +1769 -0
- package/src/__tests__/ipc-validate.test.ts +407 -0
- package/src/__tests__/key-migration.test.ts +206 -0
- package/src/__tests__/keychain.test.ts +258 -0
- package/src/__tests__/llm-usage-store.test.ts +221 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
- package/src/__tests__/managed-store.test.ts +608 -0
- package/src/__tests__/media-generate-image.test.ts +238 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
- package/src/__tests__/media-visibility-policy.test.ts +141 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +235 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
- package/src/__tests__/memory-query-builder.test.ts +59 -0
- package/src/__tests__/memory-recall-quality.test.ts +846 -0
- package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
- package/src/__tests__/memory-regressions.test.ts +4435 -0
- package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/migration-cli-flows.test.ts +169 -0
- package/src/__tests__/migration-ordering.test.ts +249 -0
- package/src/__tests__/mock-signup-server.test.ts +528 -0
- package/src/__tests__/oauth-callback-registry.test.ts +92 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +285 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +176 -0
- package/src/__tests__/onboarding-template-contract.test.ts +58 -0
- package/src/__tests__/openai-provider.test.ts +753 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/parser.test.ts +472 -0
- package/src/__tests__/path-classifier.test.ts +73 -0
- package/src/__tests__/path-policy.test.ts +435 -0
- package/src/__tests__/platform-move-helper.test.ts +99 -0
- package/src/__tests__/platform-socket-path.test.ts +52 -0
- package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
- package/src/__tests__/platform.test.ts +131 -0
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +340 -0
- package/src/__tests__/prebuilt-home-base-seed.test.ts +75 -0
- package/src/__tests__/pricing.test.ts +256 -0
- package/src/__tests__/profile-compiler.test.ts +374 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +342 -0
- package/src/__tests__/provider-registry-ollama.test.ts +16 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/proxy-approval-callback.test.ts +601 -0
- package/src/__tests__/public-ingress-urls.test.ts +256 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/ratelimit.test.ts +297 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +175 -0
- package/src/__tests__/recurrence-engine.test.ts +78 -0
- package/src/__tests__/recurrence-types.test.ts +79 -0
- package/src/__tests__/registry.test.ts +494 -0
- package/src/__tests__/relay-server.test.ts +688 -0
- package/src/__tests__/reminder-store.test.ts +223 -0
- package/src/__tests__/reminder.test.ts +229 -0
- package/src/__tests__/request-file-tool.test.ts +158 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +227 -0
- package/src/__tests__/run-orchestrator.test.ts +425 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +189 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
- package/src/__tests__/runtime-events-sse.test.ts +162 -0
- package/src/__tests__/runtime-runs-http.test.ts +438 -0
- package/src/__tests__/runtime-runs.test.ts +260 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
- package/src/__tests__/sandbox-host-parity.test.ts +950 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
- package/src/__tests__/schedule-store.test.ts +484 -0
- package/src/__tests__/schedule-tools.test.ts +783 -0
- package/src/__tests__/scheduler-recurrence.test.ts +430 -0
- package/src/__tests__/script-proxy-certs.test.ts +90 -0
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
- package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
- package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
- package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
- package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
- package/src/__tests__/script-proxy-policy.test.ts +310 -0
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
- package/src/__tests__/script-proxy-router.test.ts +180 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
- package/src/__tests__/secret-allowlist.test.ts +230 -0
- package/src/__tests__/secret-ingress-handler.test.ts +110 -0
- package/src/__tests__/secret-onetime-send.test.ts +130 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
- package/src/__tests__/secret-response-routing.test.ts +93 -0
- package/src/__tests__/secret-scanner-executor.test.ts +348 -0
- package/src/__tests__/secret-scanner.test.ts +900 -0
- package/src/__tests__/secure-keys.test.ts +323 -0
- package/src/__tests__/server-history-render.test.ts +431 -0
- package/src/__tests__/session-abort-tool-results.test.ts +240 -0
- package/src/__tests__/session-conflict-gate.test.ts +1136 -0
- package/src/__tests__/session-error.test.ts +369 -0
- package/src/__tests__/session-evictor.test.ts +188 -0
- package/src/__tests__/session-init.benchmark.test.ts +465 -0
- package/src/__tests__/session-load-history-repair.test.ts +222 -0
- package/src/__tests__/session-pre-run-repair.test.ts +213 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-profile-injection.test.ts +444 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
- package/src/__tests__/session-queue.test.ts +1535 -0
- package/src/__tests__/session-runtime-assembly.test.ts +476 -0
- package/src/__tests__/session-runtime-workspace.test.ts +183 -0
- package/src/__tests__/session-skill-tools.test.ts +2431 -0
- package/src/__tests__/session-slash-known.test.ts +368 -0
- package/src/__tests__/session-slash-queue.test.ts +288 -0
- package/src/__tests__/session-slash-unknown.test.ts +271 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
- package/src/__tests__/session-undo.test.ts +75 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
- package/src/__tests__/session-workspace-injection.test.ts +327 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
- package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
- package/src/__tests__/shell-credential-ref.test.ts +187 -0
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
- package/src/__tests__/shell-parser-property.test.ts +433 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
- package/src/__tests__/signup-e2e.test.ts +353 -0
- package/src/__tests__/size-guard.test.ts +117 -0
- package/src/__tests__/skill-include-graph.test.ts +303 -0
- package/src/__tests__/skill-load-tool.test.ts +409 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +338 -0
- package/src/__tests__/skill-script-runner-host.test.ts +489 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/skill-tool-factory.test.ts +252 -0
- package/src/__tests__/skill-tool-manifest.test.ts +658 -0
- package/src/__tests__/skill-version-hash.test.ts +182 -0
- package/src/__tests__/skills.test.ts +680 -0
- package/src/__tests__/slash-commands-catalog.test.ts +86 -0
- package/src/__tests__/slash-commands-parser.test.ts +119 -0
- package/src/__tests__/slash-commands-resolver.test.ts +193 -0
- package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/starter-bundle.test.ts +136 -0
- package/src/__tests__/starter-task-flow.test.ts +143 -0
- package/src/__tests__/subagent-manager-notify.test.ts +404 -0
- package/src/__tests__/subagent-tools.test.ts +801 -0
- package/src/__tests__/subagent-types.test.ts +78 -0
- package/src/__tests__/swarm-orchestrator.test.ts +428 -0
- package/src/__tests__/swarm-plan-validator.test.ts +330 -0
- package/src/__tests__/swarm-recursion.test.ts +165 -0
- package/src/__tests__/swarm-router-planner.test.ts +208 -0
- package/src/__tests__/swarm-session-integration.test.ts +274 -0
- package/src/__tests__/swarm-tool.test.ts +145 -0
- package/src/__tests__/swarm-worker-backend.test.ts +129 -0
- package/src/__tests__/swarm-worker-runner.test.ts +272 -0
- package/src/__tests__/system-prompt.test.ts +439 -0
- package/src/__tests__/task-compiler.test.ts +284 -0
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +216 -0
- package/src/__tests__/task-scheduler.test.ts +217 -0
- package/src/__tests__/task-tools.test.ts +595 -0
- package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
- package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
- package/src/__tests__/terminal-sandbox.test.ts +202 -0
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
- package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
- package/src/__tests__/tool-audit-listener.test.ts +113 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +253 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
- package/src/__tests__/tool-executor-redaction.test.ts +289 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +1989 -0
- package/src/__tests__/tool-metrics-listener.test.ts +225 -0
- package/src/__tests__/tool-notification-listener.test.ts +49 -0
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/tool-policy.test.ts +54 -0
- package/src/__tests__/tool-profiling-listener.test.ts +268 -0
- package/src/__tests__/tool-result-truncation.test.ts +217 -0
- package/src/__tests__/tool-trace-listener.test.ts +226 -0
- package/src/__tests__/top-level-renderer.test.ts +121 -0
- package/src/__tests__/top-level-scanner.test.ts +141 -0
- package/src/__tests__/trace-emitter.test.ts +173 -0
- package/src/__tests__/trust-store.test.ts +1605 -0
- package/src/__tests__/turn-commit.test.ts +554 -0
- package/src/__tests__/twilio-provider.test.ts +329 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
- package/src/__tests__/twilio-routes.test.ts +577 -0
- package/src/__tests__/twitter-auth-handler.test.ts +667 -0
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/url-safety.test.ts +418 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/weather-skill-regression.test.ts +225 -0
- package/src/__tests__/web-fetch.test.ts +869 -0
- package/src/__tests__/web-search.test.ts +584 -0
- package/src/__tests__/workspace-git-service.test.ts +1153 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +486 -0
- package/src/__tests__/workspace-lifecycle.test.ts +292 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/agent/attachments.ts +35 -0
- package/src/agent/loop.ts +500 -0
- package/src/agent/message-types.ts +17 -0
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/autonomy/autonomy-resolver.ts +60 -0
- package/src/autonomy/autonomy-store.ts +122 -0
- package/src/autonomy/disposition-mapper.ts +31 -0
- package/src/autonomy/index.ts +11 -0
- package/src/autonomy/types.ts +39 -0
- package/src/bundler/app-bundler.ts +295 -0
- package/src/bundler/bundle-scanner.ts +535 -0
- package/src/bundler/bundle-signer.ts +124 -0
- package/src/bundler/manifest.ts +21 -0
- package/src/bundler/signature-verifier.ts +184 -0
- package/src/calls/call-bridge.ts +168 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +430 -0
- package/src/calls/call-orchestrator.ts +498 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +87 -0
- package/src/calls/call-store.ts +422 -0
- package/src/calls/elevenlabs-client.ts +97 -0
- package/src/calls/elevenlabs-config.ts +31 -0
- package/src/calls/relay-server.ts +390 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +45 -0
- package/src/calls/twilio-provider.ts +263 -0
- package/src/calls/twilio-rest.ts +156 -0
- package/src/calls/twilio-routes.ts +311 -0
- package/src/calls/types.ts +39 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/calls/voice-quality.ts +114 -0
- package/src/cli/autonomy.ts +188 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/contacts.ts +149 -0
- package/src/cli/core-commands.ts +784 -0
- package/src/cli/doordash.ts +1055 -0
- package/src/cli/email-guardrails.ts +200 -0
- package/src/cli/email.ts +405 -0
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/main-screen.tsx +53 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +754 -0
- package/src/cli.ts +918 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/.gitkeep +0 -0
- package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
- package/src/config/bundled-skills/agentmail/icon.svg +21 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1404 -0
- package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
- package/src/config/bundled-skills/app-builder/icon.svg +9 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
- package/src/config/bundled-skills/browser/SKILL.md +28 -0
- package/src/config/bundled-skills/browser/TOOLS.json +234 -0
- package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
- package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +57 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +60 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +66 -0
- package/src/config/bundled-skills/document/SKILL.md +26 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/icon.svg +24 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
- package/src/config/bundled-skills/google-calendar/types.ts +97 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +115 -0
- package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
- package/src/config/bundled-skills/messaging/SKILL.md +153 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +125 -0
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +31 -0
- package/src/config/bundled-skills/messaging/tools/shared.ts +76 -0
- package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
- package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +533 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +98 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +54 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +76 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +113 -0
- package/src/config/bundled-skills/public-ingress/SKILL.md +200 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/self-upgrade/SKILL.md +68 -0
- package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
- package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +281 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
- package/src/config/bundled-skills/twitter/SKILL.md +220 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/bundled-skills/weather/SKILL.md +37 -0
- package/src/config/bundled-skills/weather/TOOLS.json +32 -0
- package/src/config/bundled-skills/weather/icon.svg +24 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
- package/src/config/computer-use-prompt.ts +97 -0
- package/src/config/defaults.ts +263 -0
- package/src/config/loader.ts +339 -0
- package/src/config/schema.ts +1436 -0
- package/src/config/skill-state.ts +95 -0
- package/src/config/skills.ts +972 -0
- package/src/config/system-prompt.ts +675 -0
- package/src/config/templates/BOOTSTRAP.md +70 -0
- package/src/config/templates/IDENTITY.md +25 -0
- package/src/config/templates/LOOKS.md +25 -0
- package/src/config/templates/SOUL.md +37 -0
- package/src/config/templates/USER.md +19 -0
- package/src/config/types.ts +42 -0
- package/src/config/vellum-skills/chatgpt-import/SKILL.md +24 -0
- package/src/config/vellum-skills/chatgpt-import/TOOLS.json +23 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +284 -0
- package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
- package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +199 -0
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +153 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +143 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +213 -0
- package/src/contacts/contact-store.ts +410 -0
- package/src/contacts/index.ts +11 -0
- package/src/contacts/types.ts +28 -0
- package/src/context/token-estimator.ts +108 -0
- package/src/context/tool-result-truncation.ts +128 -0
- package/src/context/window-manager.ts +531 -0
- package/src/daemon/assistant-attachments.ts +691 -0
- package/src/daemon/classifier.ts +110 -0
- package/src/daemon/computer-use-session.ts +903 -0
- package/src/daemon/connection-policy.ts +41 -0
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +530 -0
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +187 -0
- package/src/daemon/handlers/config.ts +1517 -0
- package/src/daemon/handlers/diagnostics.ts +338 -0
- package/src/daemon/handlers/documents.ts +173 -0
- package/src/daemon/handlers/home-base.ts +78 -0
- package/src/daemon/handlers/identity.ts +127 -0
- package/src/daemon/handlers/index.ts +129 -0
- package/src/daemon/handlers/misc.ts +331 -0
- package/src/daemon/handlers/open-bundle-handler.ts +80 -0
- package/src/daemon/handlers/publish.ts +187 -0
- package/src/daemon/handlers/sessions.ts +555 -0
- package/src/daemon/handlers/shared.ts +570 -0
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +486 -0
- package/src/daemon/handlers/subagents.ts +210 -0
- package/src/daemon/handlers/twitter-auth.ts +198 -0
- package/src/daemon/handlers/work-items.ts +632 -0
- package/src/daemon/handlers/workspace-files.ts +75 -0
- package/src/daemon/handlers.ts +17 -0
- package/src/daemon/history-repair.ts +214 -0
- package/src/daemon/ipc-blob-store.ts +231 -0
- package/src/daemon/ipc-contract-inventory.json +495 -0
- package/src/daemon/ipc-contract-inventory.ts +126 -0
- package/src/daemon/ipc-contract.ts +2551 -0
- package/src/daemon/ipc-protocol.ts +75 -0
- package/src/daemon/ipc-validate.ts +188 -0
- package/src/daemon/lifecycle.ts +582 -0
- package/src/daemon/main.ts +21 -0
- package/src/daemon/media-visibility-policy.ts +57 -0
- package/src/daemon/ride-shotgun-handler.ts +309 -0
- package/src/daemon/server.ts +1215 -0
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +196 -0
- package/src/daemon/session-conflict-gate.ts +184 -0
- package/src/daemon/session-dynamic-profile.ts +63 -0
- package/src/daemon/session-error.ts +290 -0
- package/src/daemon/session-evictor.ts +196 -0
- package/src/daemon/session-history.ts +437 -0
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-memory.ts +212 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +193 -0
- package/src/daemon/session-process.ts +323 -0
- package/src/daemon/session-queue-manager.ts +82 -0
- package/src/daemon/session-runtime-assembly.ts +447 -0
- package/src/daemon/session-skill-tools.ts +356 -0
- package/src/daemon/session-slash.ts +305 -0
- package/src/daemon/session-surfaces.ts +702 -0
- package/src/daemon/session-tool-setup.ts +523 -0
- package/src/daemon/session-usage.ts +72 -0
- package/src/daemon/session-workspace.ts +19 -0
- package/src/daemon/session.ts +400 -0
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/trace-emitter.ts +82 -0
- package/src/daemon/video-thumbnail.ts +62 -0
- package/src/daemon/watch-handler.ts +274 -0
- package/src/doordash/client.ts +999 -0
- package/src/doordash/queries.ts +1311 -0
- package/src/doordash/query-extractor.ts +93 -0
- package/src/doordash/session.ts +82 -0
- package/src/email/provider.ts +117 -0
- package/src/email/providers/agentmail.ts +317 -0
- package/src/email/providers/index.ts +58 -0
- package/src/email/service.ts +303 -0
- package/src/email/types.ts +126 -0
- package/src/events/bus.ts +157 -0
- package/src/events/domain-events.ts +83 -0
- package/src/events/index.ts +18 -0
- package/src/events/tool-audit-listener.ts +80 -0
- package/src/events/tool-domain-event-publisher.ts +111 -0
- package/src/events/tool-metrics-listener.ts +159 -0
- package/src/events/tool-notification-listener.ts +17 -0
- package/src/events/tool-profiling-listener.ts +158 -0
- package/src/events/tool-trace-listener.ts +75 -0
- package/src/export/formatter.ts +98 -0
- package/src/followups/followup-store.ts +168 -0
- package/src/followups/index.ts +10 -0
- package/src/followups/types.ts +29 -0
- package/src/gallery/default-gallery.ts +795 -0
- package/src/gallery/gallery-manifest.ts +24 -0
- package/src/home-base/app-link-store.ts +82 -0
- package/src/home-base/bootstrap.ts +68 -0
- package/src/home-base/prebuilt/index.html +662 -0
- package/src/home-base/prebuilt/seed-metadata.json +21 -0
- package/src/home-base/prebuilt/seed.ts +112 -0
- package/src/home-base/prebuilt-home-base-updater.ts +30 -0
- package/src/hooks/cli.ts +163 -0
- package/src/hooks/config.ts +88 -0
- package/src/hooks/discovery.ts +110 -0
- package/src/hooks/manager.ts +124 -0
- package/src/hooks/runner.ts +123 -0
- package/src/hooks/templates.ts +52 -0
- package/src/hooks/types.ts +72 -0
- package/src/inbound/public-ingress-urls.ts +123 -0
- package/src/index.ts +81 -0
- package/src/instrument.ts +60 -0
- package/src/logfire.ts +99 -0
- package/src/media/gemini-image-service.ts +136 -0
- package/src/memory/account-store.ts +108 -0
- package/src/memory/admin.ts +211 -0
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +577 -0
- package/src/memory/attachments-store.ts +397 -0
- package/src/memory/channel-delivery-store.ts +353 -0
- package/src/memory/channel-guardian-store.ts +669 -0
- package/src/memory/checkpoints.ts +52 -0
- package/src/memory/clarification-resolver.ts +298 -0
- package/src/memory/conflict-intent.ts +157 -0
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +350 -0
- package/src/memory/contradiction-checker.ts +358 -0
- package/src/memory/conversation-key-store.ts +122 -0
- package/src/memory/conversation-store.ts +470 -0
- package/src/memory/db.ts +1991 -0
- package/src/memory/embedding-backend.ts +229 -0
- package/src/memory/embedding-gemini.ts +52 -0
- package/src/memory/embedding-local.ts +65 -0
- package/src/memory/embedding-ollama.ts +55 -0
- package/src/memory/embedding-openai.ts +25 -0
- package/src/memory/entity-extractor.ts +474 -0
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/fingerprint.ts +20 -0
- package/src/memory/indexer.ts +156 -0
- package/src/memory/items-extractor.ts +461 -0
- package/src/memory/job-handlers/backfill.ts +139 -0
- package/src/memory/job-handlers/cleanup.ts +58 -0
- package/src/memory/job-handlers/conflict.ts +141 -0
- package/src/memory/job-handlers/embedding.ts +61 -0
- package/src/memory/job-handlers/extraction.ts +123 -0
- package/src/memory/job-handlers/index-maintenance.ts +54 -0
- package/src/memory/job-handlers/summarization.ts +286 -0
- package/src/memory/job-utils.ts +170 -0
- package/src/memory/jobs-store.ts +401 -0
- package/src/memory/jobs-worker.ts +313 -0
- package/src/memory/llm-request-log-store.ts +45 -0
- package/src/memory/llm-usage-store.ts +60 -0
- package/src/memory/message-content.ts +54 -0
- package/src/memory/profile-compiler.ts +160 -0
- package/src/memory/published-pages-store.ts +137 -0
- package/src/memory/qdrant-client.ts +366 -0
- package/src/memory/qdrant-manager.ts +242 -0
- package/src/memory/query-builder.ts +45 -0
- package/src/memory/retrieval-budget.ts +30 -0
- package/src/memory/retriever.ts +653 -0
- package/src/memory/runs-store.ts +305 -0
- package/src/memory/schema.ts +677 -0
- package/src/memory/search/entity.ts +298 -0
- package/src/memory/search/formatting.ts +207 -0
- package/src/memory/search/lexical.ts +227 -0
- package/src/memory/search/ranking.ts +401 -0
- package/src/memory/search/semantic.ts +121 -0
- package/src/memory/search/types.ts +137 -0
- package/src/memory/segmenter.ts +68 -0
- package/src/memory/shared-app-links-store.ts +138 -0
- package/src/memory/tool-usage-store.ts +62 -0
- package/src/messaging/activity-analyzer.ts +76 -0
- package/src/messaging/draft-store.ts +88 -0
- package/src/messaging/index.ts +3 -0
- package/src/messaging/provider-types.ts +80 -0
- package/src/messaging/provider.ts +52 -0
- package/src/messaging/providers/gmail/adapter.ts +193 -0
- package/src/messaging/providers/gmail/client.ts +204 -0
- package/src/messaging/providers/gmail/types.ts +90 -0
- package/src/messaging/providers/slack/adapter.ts +202 -0
- package/src/messaging/providers/slack/client.ts +198 -0
- package/src/messaging/providers/slack/types.ts +119 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +35 -0
- package/src/messaging/style-analyzer.ts +159 -0
- package/src/messaging/thread-summarizer.ts +306 -0
- package/src/messaging/triage-engine.ts +323 -0
- package/src/messaging/types.ts +55 -0
- package/src/permissions/checker.ts +640 -0
- package/src/permissions/defaults.ts +254 -0
- package/src/permissions/prompter.ts +98 -0
- package/src/permissions/secret-prompter.ts +114 -0
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +607 -0
- package/src/permissions/types.ts +43 -0
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/playbooks/index.ts +2 -0
- package/src/playbooks/playbook-compiler.ts +90 -0
- package/src/playbooks/types.ts +55 -0
- package/src/providers/anthropic/client.ts +751 -0
- package/src/providers/failover.ts +129 -0
- package/src/providers/fireworks/client.ts +20 -0
- package/src/providers/gemini/client.ts +285 -0
- package/src/providers/ollama/client.ts +30 -0
- package/src/providers/openai/client.ts +337 -0
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/ratelimit.ts +93 -0
- package/src/providers/registry.ts +146 -0
- package/src/providers/retry.ts +81 -0
- package/src/providers/stream-timeout.ts +38 -0
- package/src/providers/types.ts +109 -0
- package/src/runtime/assistant-event-hub.ts +157 -0
- package/src/runtime/assistant-event.ts +82 -0
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +73 -0
- package/src/runtime/channel-approvals.ts +206 -0
- package/src/runtime/channel-guardian-service.ts +212 -0
- package/src/runtime/gateway-client.ts +58 -0
- package/src/runtime/http-server.ts +1076 -0
- package/src/runtime/http-types.ts +66 -0
- package/src/runtime/routes/app-routes.ts +174 -0
- package/src/runtime/routes/attachment-routes.ts +133 -0
- package/src/runtime/routes/call-routes.ts +190 -0
- package/src/runtime/routes/channel-routes.ts +1404 -0
- package/src/runtime/routes/conversation-routes.ts +352 -0
- package/src/runtime/routes/events-routes.ts +148 -0
- package/src/runtime/routes/run-routes.ts +257 -0
- package/src/runtime/routes/secret-routes.ts +76 -0
- package/src/runtime/run-orchestrator.ts +330 -0
- package/src/schedule/recurrence-engine.ts +162 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +506 -0
- package/src/schedule/scheduler.ts +171 -0
- package/src/security/encrypted-store.ts +238 -0
- package/src/security/keychain.ts +252 -0
- package/src/security/oauth-callback-registry.ts +66 -0
- package/src/security/oauth2.ts +274 -0
- package/src/security/redaction.ts +89 -0
- package/src/security/secret-allowlist.ts +164 -0
- package/src/security/secret-ingress.ts +57 -0
- package/src/security/secret-scanner.ts +550 -0
- package/src/security/secure-keys.ts +180 -0
- package/src/security/token-manager.ts +141 -0
- package/src/services/published-app-updater.ts +69 -0
- package/src/services/vercel-deploy.ts +73 -0
- package/src/skills/active-skill-tools.ts +81 -0
- package/src/skills/clawhub.ts +414 -0
- package/src/skills/include-graph.ts +146 -0
- package/src/skills/managed-store.ts +233 -0
- package/src/skills/path-classifier.ts +128 -0
- package/src/skills/slash-commands.ts +174 -0
- package/src/skills/tool-manifest.ts +165 -0
- package/src/skills/version-hash.ts +110 -0
- package/src/slack/slack-webhook.ts +61 -0
- package/src/subagent/index.ts +19 -0
- package/src/subagent/manager.ts +511 -0
- package/src/subagent/types.ts +69 -0
- package/src/swarm/backend-claude-code.ts +145 -0
- package/src/swarm/index.ts +44 -0
- package/src/swarm/limits.ts +37 -0
- package/src/swarm/orchestrator.ts +279 -0
- package/src/swarm/plan-validator.ts +151 -0
- package/src/swarm/router-planner.ts +100 -0
- package/src/swarm/router-prompts.ts +36 -0
- package/src/swarm/synthesizer.ts +62 -0
- package/src/swarm/types.ts +62 -0
- package/src/swarm/worker-backend.ts +121 -0
- package/src/swarm/worker-prompts.ts +79 -0
- package/src/swarm/worker-runner.ts +164 -0
- package/src/tasks/SPEC.md +139 -0
- package/src/tasks/candidate-store.ts +86 -0
- package/src/tasks/ephemeral-permissions.ts +48 -0
- package/src/tasks/task-compiler.ts +199 -0
- package/src/tasks/task-runner.ts +90 -0
- package/src/tasks/task-scheduler.ts +21 -0
- package/src/tasks/task-store.ts +127 -0
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/apps/definitions.ts +59 -0
- package/src/tools/apps/executors.ts +313 -0
- package/src/tools/apps/open-proxy.ts +43 -0
- package/src/tools/apps/registry.ts +16 -0
- package/src/tools/assets/materialize.ts +218 -0
- package/src/tools/assets/search.ts +361 -0
- package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
- package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
- package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auth-cache.ts +149 -0
- package/src/tools/browser/auth-detector.ts +347 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +980 -0
- package/src/tools/browser/browser-handoff.ts +79 -0
- package/src/tools/browser/browser-manager.ts +715 -0
- package/src/tools/browser/browser-screencast.ts +217 -0
- package/src/tools/browser/headless-browser.ts +450 -0
- package/src/tools/browser/jit-auth.ts +51 -0
- package/src/tools/browser/network-recorder.ts +349 -0
- package/src/tools/browser/network-recording-types.ts +49 -0
- package/src/tools/browser/recording-store.ts +49 -0
- package/src/tools/browser/runtime-check.ts +43 -0
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +81 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +428 -0
- package/src/tools/computer-use/definitions.ts +443 -0
- package/src/tools/computer-use/registry.ts +22 -0
- package/src/tools/computer-use/request-computer-control.ts +53 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
- package/src/tools/credentials/account-registry.ts +127 -0
- package/src/tools/credentials/broker-types.ts +107 -0
- package/src/tools/credentials/broker.ts +372 -0
- package/src/tools/credentials/domain-policy.ts +51 -0
- package/src/tools/credentials/host-pattern-match.ts +60 -0
- package/src/tools/credentials/metadata-store.ts +335 -0
- package/src/tools/credentials/policy-types.ts +52 -0
- package/src/tools/credentials/policy-validate.ts +80 -0
- package/src/tools/credentials/resolve.ts +122 -0
- package/src/tools/credentials/selection.ts +159 -0
- package/src/tools/credentials/tool-policy.ts +25 -0
- package/src/tools/credentials/vault.ts +657 -0
- package/src/tools/document/document-tool.ts +92 -0
- package/src/tools/document/editor-template.ts +237 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +815 -0
- package/src/tools/filesystem/edit.ts +127 -0
- package/src/tools/filesystem/fuzzy-match.ts +202 -0
- package/src/tools/filesystem/read.ts +71 -0
- package/src/tools/filesystem/view-image.ts +199 -0
- package/src/tools/filesystem/write.ts +79 -0
- package/src/tools/followups/followup_create.ts +76 -0
- package/src/tools/followups/followup_list.ts +60 -0
- package/src/tools/followups/followup_resolve.ts +56 -0
- package/src/tools/host-filesystem/edit.ts +125 -0
- package/src/tools/host-filesystem/read.ts +80 -0
- package/src/tools/host-filesystem/write.ts +76 -0
- package/src/tools/host-terminal/cli-discover.ts +180 -0
- package/src/tools/host-terminal/host-shell.ts +191 -0
- package/src/tools/memory/definitions.ts +69 -0
- package/src/tools/memory/handlers.ts +246 -0
- package/src/tools/memory/register.ts +66 -0
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/domain-normalize.ts +85 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/script-proxy/certs.ts +237 -0
- package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
- package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
- package/src/tools/network/script-proxy/index.ts +28 -0
- package/src/tools/network/script-proxy/logging.ts +196 -0
- package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
- package/src/tools/network/script-proxy/policy.ts +152 -0
- package/src/tools/network/script-proxy/router.ts +60 -0
- package/src/tools/network/script-proxy/server.ts +136 -0
- package/src/tools/network/script-proxy/session-manager.ts +534 -0
- package/src/tools/network/script-proxy/types.ts +125 -0
- package/src/tools/network/url-safety.ts +227 -0
- package/src/tools/network/web-fetch.ts +713 -0
- package/src/tools/network/web-search.ts +296 -0
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/registry.ts +295 -0
- package/src/tools/reminder/reminder-store.ts +148 -0
- package/src/tools/reminder/reminder.ts +80 -0
- package/src/tools/schedule/create.ts +81 -0
- package/src/tools/schedule/delete.ts +28 -0
- package/src/tools/schedule/list.ts +69 -0
- package/src/tools/schedule/update.ts +97 -0
- package/src/tools/shared/filesystem/edit-engine.ts +56 -0
- package/src/tools/shared/filesystem/errors.ts +85 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
- package/src/tools/shared/filesystem/format-diff.ts +35 -0
- package/src/tools/shared/filesystem/path-policy.ts +125 -0
- package/src/tools/shared/filesystem/size-guard.ts +41 -0
- package/src/tools/shared/filesystem/types.ts +80 -0
- package/src/tools/shared/shell-output.ts +52 -0
- package/src/tools/skills/delete-managed.ts +60 -0
- package/src/tools/skills/load.ts +139 -0
- package/src/tools/skills/sandbox-runner.ts +279 -0
- package/src/tools/skills/scaffold-managed.ts +150 -0
- package/src/tools/skills/script-contract.ts +6 -0
- package/src/tools/skills/skill-script-runner.ts +86 -0
- package/src/tools/skills/skill-tool-factory.ts +64 -0
- package/src/tools/skills/vellum-catalog.ts +217 -0
- package/src/tools/subagent/abort.ts +33 -0
- package/src/tools/subagent/message.ts +39 -0
- package/src/tools/subagent/read.ts +67 -0
- package/src/tools/subagent/spawn.ts +46 -0
- package/src/tools/subagent/status.ts +45 -0
- package/src/tools/swarm/delegate.ts +183 -0
- package/src/tools/system/request-permission.ts +98 -0
- package/src/tools/system/version.ts +43 -0
- package/src/tools/tasks/index.ts +27 -0
- package/src/tools/tasks/task-delete.ts +82 -0
- package/src/tools/tasks/task-list.ts +44 -0
- package/src/tools/tasks/task-run.ts +97 -0
- package/src/tools/tasks/task-save.ts +47 -0
- package/src/tools/tasks/work-item-enqueue.ts +234 -0
- package/src/tools/tasks/work-item-list.ts +55 -0
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-run.ts +78 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/docker.ts +372 -0
- package/src/tools/terminal/backends/native.ts +190 -0
- package/src/tools/terminal/backends/types.ts +26 -0
- package/src/tools/terminal/evaluate-typescript.ts +275 -0
- package/src/tools/terminal/parser.ts +413 -0
- package/src/tools/terminal/safe-env.ts +37 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
- package/src/tools/terminal/sandbox.ts +44 -0
- package/src/tools/terminal/shell.ts +257 -0
- package/src/tools/tool-manifest.ts +198 -0
- package/src/tools/types.ts +176 -0
- package/src/tools/ui-surface/definitions.ts +244 -0
- package/src/tools/ui-surface/registry.ts +14 -0
- package/src/tools/watch/screen-watch.ts +130 -0
- package/src/tools/watch/watch-state.ts +119 -0
- package/src/tools/watcher/create.ts +64 -0
- package/src/tools/watcher/delete.ts +27 -0
- package/src/tools/watcher/digest.ts +50 -0
- package/src/tools/watcher/list.ts +60 -0
- package/src/tools/watcher/update.ts +56 -0
- package/src/tools/weather/service.ts +551 -0
- package/src/twitter/client.ts +690 -0
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/actors.ts +24 -0
- package/src/usage/types.ts +37 -0
- package/src/util/clipboard.ts +33 -0
- package/src/util/content-id.ts +16 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/diff.ts +181 -0
- package/src/util/errors.ts +129 -0
- package/src/util/logger.ts +243 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +632 -0
- package/src/util/pricing.ts +150 -0
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/spinner.ts +51 -0
- package/src/util/time.ts +16 -0
- package/src/util/truncate.ts +6 -0
- package/src/util/xml.ts +4 -0
- package/src/version.ts +3 -0
- package/src/watcher/constants.ts +11 -0
- package/src/watcher/engine.ts +199 -0
- package/src/watcher/provider-registry.ts +15 -0
- package/src/watcher/provider-types.ts +48 -0
- package/src/watcher/providers/gmail.ts +198 -0
- package/src/watcher/providers/google-calendar.ts +228 -0
- package/src/watcher/providers/slack.ts +129 -0
- package/src/watcher/watcher-store.ts +419 -0
- package/src/work-items/work-item-runner.ts +171 -0
- package/src/work-items/work-item-store.ts +325 -0
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +857 -0
- package/src/workspace/heartbeat-service.ts +345 -0
- package/src/workspace/provider-commit-message-generator.ts +285 -0
- package/src/workspace/top-level-renderer.ts +19 -0
- package/src/workspace/top-level-scanner.ts +41 -0
- package/src/workspace/turn-commit.ts +175 -0
- package/tsconfig.json +21 -0
package/src/memory/db.ts
ADDED
|
@@ -0,0 +1,1991 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
3
|
+
import { computeMemoryFingerprint } from './fingerprint.js';
|
|
4
|
+
import * as schema from './schema.js';
|
|
5
|
+
import { getDbPath, ensureDataDir, migrateToDataLayout, migrateToWorkspaceLayout } from '../util/platform.js';
|
|
6
|
+
import { getLogger } from '../util/logger.js';
|
|
7
|
+
|
|
8
|
+
const log = getLogger('memory-db');
|
|
9
|
+
|
|
10
|
+
let db: ReturnType<typeof drizzle<typeof schema>> | null = null;
|
|
11
|
+
|
|
12
|
+
export function getDb() {
|
|
13
|
+
if (!db) {
|
|
14
|
+
migrateToDataLayout();
|
|
15
|
+
migrateToWorkspaceLayout();
|
|
16
|
+
ensureDataDir();
|
|
17
|
+
const sqlite = new Database(getDbPath());
|
|
18
|
+
sqlite.exec('PRAGMA journal_mode=WAL');
|
|
19
|
+
sqlite.exec('PRAGMA foreign_keys = ON');
|
|
20
|
+
db = drizzle(sqlite, { schema });
|
|
21
|
+
}
|
|
22
|
+
return db;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reset the db singleton. Used by tests to ensure isolation between test files. */
|
|
26
|
+
export function resetDb(): void {
|
|
27
|
+
if (db) {
|
|
28
|
+
const raw = (db as unknown as { $client: Database }).$client;
|
|
29
|
+
raw.close();
|
|
30
|
+
db = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function initializeDb(): void {
|
|
35
|
+
const database = getDb();
|
|
36
|
+
|
|
37
|
+
database.run(/*sql*/ `
|
|
38
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
title TEXT,
|
|
41
|
+
created_at INTEGER NOT NULL,
|
|
42
|
+
updated_at INTEGER NOT NULL,
|
|
43
|
+
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
44
|
+
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
total_estimated_cost REAL NOT NULL DEFAULT 0,
|
|
46
|
+
context_summary TEXT,
|
|
47
|
+
context_compacted_message_count INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
context_compacted_at INTEGER
|
|
49
|
+
)
|
|
50
|
+
`);
|
|
51
|
+
|
|
52
|
+
database.run(/*sql*/ `
|
|
53
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id),
|
|
56
|
+
role TEXT NOT NULL,
|
|
57
|
+
content TEXT NOT NULL,
|
|
58
|
+
created_at INTEGER NOT NULL
|
|
59
|
+
)
|
|
60
|
+
`);
|
|
61
|
+
|
|
62
|
+
database.run(/*sql*/ `
|
|
63
|
+
CREATE TABLE IF NOT EXISTS tool_invocations (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
66
|
+
tool_name TEXT NOT NULL,
|
|
67
|
+
input TEXT NOT NULL,
|
|
68
|
+
result TEXT NOT NULL,
|
|
69
|
+
decision TEXT NOT NULL,
|
|
70
|
+
risk_level TEXT NOT NULL,
|
|
71
|
+
duration_ms INTEGER NOT NULL,
|
|
72
|
+
created_at INTEGER NOT NULL
|
|
73
|
+
)
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
database.run(/*sql*/ `
|
|
77
|
+
CREATE TABLE IF NOT EXISTS memory_segments (
|
|
78
|
+
id TEXT PRIMARY KEY,
|
|
79
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
80
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
81
|
+
role TEXT NOT NULL,
|
|
82
|
+
segment_index INTEGER NOT NULL,
|
|
83
|
+
text TEXT NOT NULL,
|
|
84
|
+
token_estimate INTEGER NOT NULL,
|
|
85
|
+
created_at INTEGER NOT NULL,
|
|
86
|
+
updated_at INTEGER NOT NULL
|
|
87
|
+
)
|
|
88
|
+
`);
|
|
89
|
+
|
|
90
|
+
database.run(/*sql*/ `
|
|
91
|
+
CREATE TABLE IF NOT EXISTS memory_items (
|
|
92
|
+
id TEXT PRIMARY KEY,
|
|
93
|
+
kind TEXT NOT NULL,
|
|
94
|
+
subject TEXT NOT NULL,
|
|
95
|
+
statement TEXT NOT NULL,
|
|
96
|
+
status TEXT NOT NULL,
|
|
97
|
+
confidence REAL NOT NULL,
|
|
98
|
+
fingerprint TEXT NOT NULL,
|
|
99
|
+
first_seen_at INTEGER NOT NULL,
|
|
100
|
+
last_seen_at INTEGER NOT NULL,
|
|
101
|
+
last_used_at INTEGER
|
|
102
|
+
)
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
database.run(/*sql*/ `
|
|
106
|
+
CREATE TABLE IF NOT EXISTS memory_item_sources (
|
|
107
|
+
memory_item_id TEXT NOT NULL REFERENCES memory_items(id) ON DELETE CASCADE,
|
|
108
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
109
|
+
evidence TEXT,
|
|
110
|
+
created_at INTEGER NOT NULL,
|
|
111
|
+
PRIMARY KEY (memory_item_id, message_id)
|
|
112
|
+
)
|
|
113
|
+
`);
|
|
114
|
+
|
|
115
|
+
database.run(/*sql*/ `
|
|
116
|
+
CREATE TABLE IF NOT EXISTS memory_item_conflicts (
|
|
117
|
+
id TEXT PRIMARY KEY,
|
|
118
|
+
scope_id TEXT NOT NULL DEFAULT 'default',
|
|
119
|
+
existing_item_id TEXT NOT NULL REFERENCES memory_items(id) ON DELETE CASCADE,
|
|
120
|
+
candidate_item_id TEXT NOT NULL REFERENCES memory_items(id) ON DELETE CASCADE,
|
|
121
|
+
relationship TEXT NOT NULL,
|
|
122
|
+
status TEXT NOT NULL,
|
|
123
|
+
clarification_question TEXT,
|
|
124
|
+
resolution_note TEXT,
|
|
125
|
+
last_asked_at INTEGER,
|
|
126
|
+
resolved_at INTEGER,
|
|
127
|
+
created_at INTEGER NOT NULL,
|
|
128
|
+
updated_at INTEGER NOT NULL
|
|
129
|
+
)
|
|
130
|
+
`);
|
|
131
|
+
|
|
132
|
+
database.run(/*sql*/ `
|
|
133
|
+
CREATE TABLE IF NOT EXISTS memory_summaries (
|
|
134
|
+
id TEXT PRIMARY KEY,
|
|
135
|
+
scope TEXT NOT NULL,
|
|
136
|
+
scope_key TEXT NOT NULL,
|
|
137
|
+
summary TEXT NOT NULL,
|
|
138
|
+
token_estimate INTEGER NOT NULL,
|
|
139
|
+
start_at INTEGER NOT NULL,
|
|
140
|
+
end_at INTEGER NOT NULL,
|
|
141
|
+
created_at INTEGER NOT NULL,
|
|
142
|
+
updated_at INTEGER NOT NULL,
|
|
143
|
+
UNIQUE (scope, scope_key)
|
|
144
|
+
)
|
|
145
|
+
`);
|
|
146
|
+
|
|
147
|
+
database.run(/*sql*/ `
|
|
148
|
+
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
149
|
+
id TEXT PRIMARY KEY,
|
|
150
|
+
target_type TEXT NOT NULL,
|
|
151
|
+
target_id TEXT NOT NULL,
|
|
152
|
+
provider TEXT NOT NULL,
|
|
153
|
+
model TEXT NOT NULL,
|
|
154
|
+
dimensions INTEGER NOT NULL,
|
|
155
|
+
vector_json TEXT NOT NULL,
|
|
156
|
+
created_at INTEGER NOT NULL,
|
|
157
|
+
updated_at INTEGER NOT NULL,
|
|
158
|
+
UNIQUE (target_type, target_id, provider, model)
|
|
159
|
+
)
|
|
160
|
+
`);
|
|
161
|
+
|
|
162
|
+
database.run(/*sql*/ `
|
|
163
|
+
CREATE TABLE IF NOT EXISTS memory_jobs (
|
|
164
|
+
id TEXT PRIMARY KEY,
|
|
165
|
+
type TEXT NOT NULL,
|
|
166
|
+
payload TEXT NOT NULL,
|
|
167
|
+
status TEXT NOT NULL,
|
|
168
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
169
|
+
run_after INTEGER NOT NULL,
|
|
170
|
+
last_error TEXT,
|
|
171
|
+
created_at INTEGER NOT NULL,
|
|
172
|
+
updated_at INTEGER NOT NULL
|
|
173
|
+
)
|
|
174
|
+
`);
|
|
175
|
+
|
|
176
|
+
database.run(/*sql*/ `
|
|
177
|
+
CREATE TABLE IF NOT EXISTS memory_checkpoints (
|
|
178
|
+
key TEXT PRIMARY KEY,
|
|
179
|
+
value TEXT NOT NULL,
|
|
180
|
+
updated_at INTEGER NOT NULL
|
|
181
|
+
)
|
|
182
|
+
`);
|
|
183
|
+
|
|
184
|
+
database.run(/*sql*/ `
|
|
185
|
+
CREATE TABLE IF NOT EXISTS attachments (
|
|
186
|
+
id TEXT PRIMARY KEY,
|
|
187
|
+
original_filename TEXT NOT NULL,
|
|
188
|
+
mime_type TEXT NOT NULL,
|
|
189
|
+
size_bytes INTEGER NOT NULL,
|
|
190
|
+
kind TEXT NOT NULL,
|
|
191
|
+
data_base64 TEXT NOT NULL,
|
|
192
|
+
created_at INTEGER NOT NULL
|
|
193
|
+
)
|
|
194
|
+
`);
|
|
195
|
+
|
|
196
|
+
database.run(/*sql*/ `
|
|
197
|
+
CREATE TABLE IF NOT EXISTS message_attachments (
|
|
198
|
+
id TEXT PRIMARY KEY,
|
|
199
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
200
|
+
attachment_id TEXT NOT NULL REFERENCES attachments(id) ON DELETE CASCADE,
|
|
201
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
202
|
+
created_at INTEGER NOT NULL
|
|
203
|
+
)
|
|
204
|
+
`);
|
|
205
|
+
|
|
206
|
+
database.run(/*sql*/ `
|
|
207
|
+
CREATE TABLE IF NOT EXISTS message_surfaces (
|
|
208
|
+
id TEXT PRIMARY KEY,
|
|
209
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
210
|
+
surface_id TEXT NOT NULL,
|
|
211
|
+
surface_type TEXT NOT NULL,
|
|
212
|
+
title TEXT,
|
|
213
|
+
data TEXT NOT NULL,
|
|
214
|
+
actions TEXT,
|
|
215
|
+
surface_message TEXT,
|
|
216
|
+
display TEXT,
|
|
217
|
+
position INTEGER NOT NULL DEFAULT 0,
|
|
218
|
+
created_at INTEGER NOT NULL
|
|
219
|
+
)
|
|
220
|
+
`);
|
|
221
|
+
|
|
222
|
+
database.run(/*sql*/ `
|
|
223
|
+
CREATE TABLE IF NOT EXISTS channel_inbound_events (
|
|
224
|
+
id TEXT PRIMARY KEY,
|
|
225
|
+
source_channel TEXT NOT NULL,
|
|
226
|
+
external_chat_id TEXT NOT NULL,
|
|
227
|
+
external_message_id TEXT NOT NULL,
|
|
228
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
229
|
+
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
230
|
+
delivery_status TEXT NOT NULL DEFAULT 'pending',
|
|
231
|
+
created_at INTEGER NOT NULL,
|
|
232
|
+
updated_at INTEGER NOT NULL,
|
|
233
|
+
UNIQUE (source_channel, external_chat_id, external_message_id)
|
|
234
|
+
)
|
|
235
|
+
`);
|
|
236
|
+
|
|
237
|
+
database.run(/*sql*/ `
|
|
238
|
+
CREATE TABLE IF NOT EXISTS message_runs (
|
|
239
|
+
id TEXT PRIMARY KEY,
|
|
240
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
241
|
+
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
242
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
243
|
+
pending_confirmation TEXT,
|
|
244
|
+
pending_secret TEXT,
|
|
245
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
246
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
247
|
+
estimated_cost REAL NOT NULL DEFAULT 0,
|
|
248
|
+
error TEXT,
|
|
249
|
+
created_at INTEGER NOT NULL,
|
|
250
|
+
updated_at INTEGER NOT NULL
|
|
251
|
+
)
|
|
252
|
+
`);
|
|
253
|
+
|
|
254
|
+
try { database.run(/*sql*/ `ALTER TABLE message_runs ADD COLUMN pending_secret TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE message_runs ADD COLUMN pending_secret (likely already exists)'); }
|
|
255
|
+
|
|
256
|
+
database.run(/*sql*/ `
|
|
257
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
258
|
+
id TEXT PRIMARY KEY,
|
|
259
|
+
label TEXT NOT NULL,
|
|
260
|
+
message TEXT NOT NULL,
|
|
261
|
+
fire_at INTEGER NOT NULL,
|
|
262
|
+
mode TEXT NOT NULL,
|
|
263
|
+
status TEXT NOT NULL,
|
|
264
|
+
fired_at INTEGER,
|
|
265
|
+
conversation_id TEXT,
|
|
266
|
+
created_at INTEGER NOT NULL,
|
|
267
|
+
updated_at INTEGER NOT NULL
|
|
268
|
+
)
|
|
269
|
+
`);
|
|
270
|
+
|
|
271
|
+
database.run(/*sql*/ `
|
|
272
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
273
|
+
id TEXT PRIMARY KEY,
|
|
274
|
+
name TEXT NOT NULL,
|
|
275
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
276
|
+
cron_expression TEXT NOT NULL,
|
|
277
|
+
schedule_syntax TEXT NOT NULL DEFAULT 'cron',
|
|
278
|
+
timezone TEXT,
|
|
279
|
+
message TEXT NOT NULL,
|
|
280
|
+
next_run_at INTEGER NOT NULL,
|
|
281
|
+
last_run_at INTEGER,
|
|
282
|
+
last_status TEXT,
|
|
283
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
284
|
+
created_by TEXT NOT NULL,
|
|
285
|
+
created_at INTEGER NOT NULL,
|
|
286
|
+
updated_at INTEGER NOT NULL
|
|
287
|
+
)
|
|
288
|
+
`);
|
|
289
|
+
|
|
290
|
+
database.run(/*sql*/ `
|
|
291
|
+
CREATE TABLE IF NOT EXISTS cron_runs (
|
|
292
|
+
id TEXT PRIMARY KEY,
|
|
293
|
+
job_id TEXT NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
|
|
294
|
+
status TEXT NOT NULL,
|
|
295
|
+
started_at INTEGER NOT NULL,
|
|
296
|
+
finished_at INTEGER,
|
|
297
|
+
duration_ms INTEGER,
|
|
298
|
+
output TEXT,
|
|
299
|
+
error TEXT,
|
|
300
|
+
conversation_id TEXT,
|
|
301
|
+
created_at INTEGER NOT NULL
|
|
302
|
+
)
|
|
303
|
+
`);
|
|
304
|
+
|
|
305
|
+
database.run(/*sql*/ `
|
|
306
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
307
|
+
id TEXT PRIMARY KEY,
|
|
308
|
+
service TEXT NOT NULL,
|
|
309
|
+
username TEXT,
|
|
310
|
+
email TEXT,
|
|
311
|
+
display_name TEXT,
|
|
312
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
313
|
+
credential_ref TEXT,
|
|
314
|
+
metadata_json TEXT,
|
|
315
|
+
created_at INTEGER NOT NULL,
|
|
316
|
+
updated_at INTEGER NOT NULL
|
|
317
|
+
)
|
|
318
|
+
`);
|
|
319
|
+
|
|
320
|
+
database.run(/*sql*/ `
|
|
321
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
322
|
+
surface_id TEXT PRIMARY KEY,
|
|
323
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
324
|
+
title TEXT NOT NULL,
|
|
325
|
+
content TEXT NOT NULL,
|
|
326
|
+
word_count INTEGER NOT NULL DEFAULT 0,
|
|
327
|
+
created_at INTEGER NOT NULL,
|
|
328
|
+
updated_at INTEGER NOT NULL
|
|
329
|
+
)
|
|
330
|
+
`);
|
|
331
|
+
|
|
332
|
+
database.run(/*sql*/ `
|
|
333
|
+
CREATE TABLE IF NOT EXISTS published_pages (
|
|
334
|
+
id TEXT PRIMARY KEY,
|
|
335
|
+
deployment_id TEXT NOT NULL UNIQUE,
|
|
336
|
+
public_url TEXT NOT NULL,
|
|
337
|
+
page_title TEXT,
|
|
338
|
+
html_hash TEXT NOT NULL,
|
|
339
|
+
published_at INTEGER NOT NULL,
|
|
340
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
341
|
+
)
|
|
342
|
+
`);
|
|
343
|
+
|
|
344
|
+
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN app_id TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE published_pages ADD COLUMN app_id (likely already exists)'); }
|
|
345
|
+
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN project_slug TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE published_pages ADD COLUMN project_slug (likely already exists)'); }
|
|
346
|
+
|
|
347
|
+
database.run(/*sql*/ `
|
|
348
|
+
CREATE TABLE IF NOT EXISTS shared_app_links (
|
|
349
|
+
id TEXT PRIMARY KEY,
|
|
350
|
+
share_token TEXT NOT NULL UNIQUE,
|
|
351
|
+
bundle_data BLOB NOT NULL,
|
|
352
|
+
bundle_size_bytes INTEGER NOT NULL,
|
|
353
|
+
manifest_json TEXT NOT NULL,
|
|
354
|
+
download_count INTEGER NOT NULL DEFAULT 0,
|
|
355
|
+
created_at INTEGER NOT NULL,
|
|
356
|
+
expires_at INTEGER
|
|
357
|
+
)
|
|
358
|
+
`);
|
|
359
|
+
|
|
360
|
+
database.run(/*sql*/ `
|
|
361
|
+
CREATE TABLE IF NOT EXISTS home_base_app_links (
|
|
362
|
+
id TEXT PRIMARY KEY,
|
|
363
|
+
app_id TEXT NOT NULL,
|
|
364
|
+
source TEXT NOT NULL,
|
|
365
|
+
created_at INTEGER NOT NULL,
|
|
366
|
+
updated_at INTEGER NOT NULL
|
|
367
|
+
)
|
|
368
|
+
`);
|
|
369
|
+
|
|
370
|
+
// ── Watchers ─────────────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
database.run(/*sql*/ `
|
|
373
|
+
CREATE TABLE IF NOT EXISTS watchers (
|
|
374
|
+
id TEXT PRIMARY KEY,
|
|
375
|
+
name TEXT NOT NULL,
|
|
376
|
+
provider_id TEXT NOT NULL,
|
|
377
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
378
|
+
poll_interval_ms INTEGER NOT NULL DEFAULT 60000,
|
|
379
|
+
action_prompt TEXT NOT NULL,
|
|
380
|
+
watermark TEXT,
|
|
381
|
+
conversation_id TEXT,
|
|
382
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
383
|
+
consecutive_errors INTEGER NOT NULL DEFAULT 0,
|
|
384
|
+
last_error TEXT,
|
|
385
|
+
last_poll_at INTEGER,
|
|
386
|
+
next_poll_at INTEGER NOT NULL,
|
|
387
|
+
config_json TEXT,
|
|
388
|
+
credential_service TEXT NOT NULL,
|
|
389
|
+
created_at INTEGER NOT NULL,
|
|
390
|
+
updated_at INTEGER NOT NULL
|
|
391
|
+
)
|
|
392
|
+
`);
|
|
393
|
+
|
|
394
|
+
database.run(/*sql*/ `
|
|
395
|
+
CREATE TABLE IF NOT EXISTS watcher_events (
|
|
396
|
+
id TEXT PRIMARY KEY,
|
|
397
|
+
watcher_id TEXT NOT NULL REFERENCES watchers(id) ON DELETE CASCADE,
|
|
398
|
+
external_id TEXT NOT NULL,
|
|
399
|
+
event_type TEXT NOT NULL,
|
|
400
|
+
summary TEXT NOT NULL,
|
|
401
|
+
payload_json TEXT NOT NULL,
|
|
402
|
+
disposition TEXT NOT NULL DEFAULT 'pending',
|
|
403
|
+
llm_action TEXT,
|
|
404
|
+
processed_at INTEGER,
|
|
405
|
+
created_at INTEGER NOT NULL,
|
|
406
|
+
UNIQUE (watcher_id, external_id)
|
|
407
|
+
)
|
|
408
|
+
`);
|
|
409
|
+
|
|
410
|
+
database.run(/*sql*/ `
|
|
411
|
+
CREATE TABLE IF NOT EXISTS llm_request_logs (
|
|
412
|
+
id TEXT PRIMARY KEY,
|
|
413
|
+
conversation_id TEXT NOT NULL,
|
|
414
|
+
request_payload TEXT NOT NULL,
|
|
415
|
+
response_payload TEXT NOT NULL,
|
|
416
|
+
created_at INTEGER NOT NULL
|
|
417
|
+
)
|
|
418
|
+
`);
|
|
419
|
+
|
|
420
|
+
database.run(/*sql*/ `
|
|
421
|
+
CREATE TABLE IF NOT EXISTS llm_usage_events (
|
|
422
|
+
id TEXT PRIMARY KEY,
|
|
423
|
+
created_at INTEGER NOT NULL,
|
|
424
|
+
conversation_id TEXT,
|
|
425
|
+
run_id TEXT,
|
|
426
|
+
request_id TEXT,
|
|
427
|
+
actor TEXT NOT NULL,
|
|
428
|
+
provider TEXT NOT NULL,
|
|
429
|
+
model TEXT NOT NULL,
|
|
430
|
+
input_tokens INTEGER NOT NULL,
|
|
431
|
+
output_tokens INTEGER NOT NULL,
|
|
432
|
+
cache_creation_input_tokens INTEGER,
|
|
433
|
+
cache_read_input_tokens INTEGER,
|
|
434
|
+
estimated_cost_usd REAL,
|
|
435
|
+
pricing_status TEXT NOT NULL,
|
|
436
|
+
metadata_json TEXT
|
|
437
|
+
)
|
|
438
|
+
`);
|
|
439
|
+
|
|
440
|
+
database.run(/*sql*/ `
|
|
441
|
+
CREATE TABLE IF NOT EXISTS memory_entities (
|
|
442
|
+
id TEXT PRIMARY KEY,
|
|
443
|
+
name TEXT NOT NULL,
|
|
444
|
+
type TEXT NOT NULL,
|
|
445
|
+
aliases TEXT,
|
|
446
|
+
description TEXT,
|
|
447
|
+
first_seen_at INTEGER NOT NULL,
|
|
448
|
+
last_seen_at INTEGER NOT NULL,
|
|
449
|
+
mention_count INTEGER NOT NULL DEFAULT 1
|
|
450
|
+
)
|
|
451
|
+
`);
|
|
452
|
+
|
|
453
|
+
database.run(/*sql*/ `
|
|
454
|
+
CREATE TABLE IF NOT EXISTS memory_entity_relations (
|
|
455
|
+
id TEXT PRIMARY KEY,
|
|
456
|
+
source_entity_id TEXT NOT NULL,
|
|
457
|
+
target_entity_id TEXT NOT NULL,
|
|
458
|
+
relation TEXT NOT NULL,
|
|
459
|
+
evidence TEXT,
|
|
460
|
+
first_seen_at INTEGER NOT NULL,
|
|
461
|
+
last_seen_at INTEGER NOT NULL
|
|
462
|
+
)
|
|
463
|
+
`);
|
|
464
|
+
|
|
465
|
+
database.run(/*sql*/ `
|
|
466
|
+
CREATE TABLE IF NOT EXISTS memory_item_entities (
|
|
467
|
+
memory_item_id TEXT NOT NULL,
|
|
468
|
+
entity_id TEXT NOT NULL,
|
|
469
|
+
PRIMARY KEY (memory_item_id, entity_id)
|
|
470
|
+
)
|
|
471
|
+
`);
|
|
472
|
+
|
|
473
|
+
// FTS table for lexical retrieval over memory_segments.text.
|
|
474
|
+
database.run(/*sql*/ `
|
|
475
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_segment_fts USING fts5(
|
|
476
|
+
segment_id UNINDEXED,
|
|
477
|
+
text
|
|
478
|
+
)
|
|
479
|
+
`);
|
|
480
|
+
|
|
481
|
+
database.run(/*sql*/ `
|
|
482
|
+
CREATE TRIGGER IF NOT EXISTS memory_segments_ai
|
|
483
|
+
AFTER INSERT ON memory_segments
|
|
484
|
+
BEGIN
|
|
485
|
+
INSERT INTO memory_segment_fts(segment_id, text) VALUES (new.id, new.text);
|
|
486
|
+
END
|
|
487
|
+
`);
|
|
488
|
+
|
|
489
|
+
database.run(/*sql*/ `
|
|
490
|
+
CREATE TRIGGER IF NOT EXISTS memory_segments_ad
|
|
491
|
+
AFTER DELETE ON memory_segments
|
|
492
|
+
BEGIN
|
|
493
|
+
DELETE FROM memory_segment_fts WHERE segment_id = old.id;
|
|
494
|
+
END
|
|
495
|
+
`);
|
|
496
|
+
|
|
497
|
+
database.run(/*sql*/ `
|
|
498
|
+
CREATE TRIGGER IF NOT EXISTS memory_segments_au
|
|
499
|
+
AFTER UPDATE ON memory_segments
|
|
500
|
+
BEGIN
|
|
501
|
+
DELETE FROM memory_segment_fts WHERE segment_id = old.id;
|
|
502
|
+
INSERT INTO memory_segment_fts(segment_id, text) VALUES (new.id, new.text);
|
|
503
|
+
END
|
|
504
|
+
`);
|
|
505
|
+
|
|
506
|
+
database.run(/*sql*/ `
|
|
507
|
+
CREATE TABLE IF NOT EXISTS conversation_keys (
|
|
508
|
+
id TEXT PRIMARY KEY,
|
|
509
|
+
conversation_key TEXT NOT NULL UNIQUE,
|
|
510
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
511
|
+
created_at INTEGER NOT NULL
|
|
512
|
+
)
|
|
513
|
+
`);
|
|
514
|
+
|
|
515
|
+
// Migrations — ALTER TABLE ADD COLUMN throws if column already exists
|
|
516
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN total_input_tokens INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
517
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN total_output_tokens INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
518
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN total_estimated_cost REAL NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
519
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN context_summary TEXT`); } catch { /* already exists */ }
|
|
520
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN context_compacted_message_count INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
521
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN context_compacted_at INTEGER`); } catch { /* already exists */ }
|
|
522
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN importance REAL`); } catch { /* already exists */ }
|
|
523
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
524
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_summaries ADD COLUMN version INTEGER NOT NULL DEFAULT 1`); } catch { /* already exists */ }
|
|
525
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN valid_from INTEGER`); } catch { /* already exists */ }
|
|
526
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN invalid_at INTEGER`); } catch { /* already exists */ }
|
|
527
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN verification_state TEXT NOT NULL DEFAULT 'assistant_inferred'`); } catch { /* already exists */ }
|
|
528
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_jobs ADD COLUMN deferrals INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
529
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_segments ADD COLUMN scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ }
|
|
530
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_items ADD COLUMN scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ }
|
|
531
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_summaries ADD COLUMN scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ }
|
|
532
|
+
try { database.run(/*sql*/ `ALTER TABLE memory_segments ADD COLUMN content_hash TEXT`); } catch { /* already exists */ }
|
|
533
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN source_message_id TEXT`); } catch { /* already exists */ }
|
|
534
|
+
try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN content_hash TEXT`); } catch { /* already exists */ }
|
|
535
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN processing_status TEXT NOT NULL DEFAULT 'pending'`); } catch { /* already exists */ }
|
|
536
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN processing_attempts INTEGER NOT NULL DEFAULT 0`); } catch { /* already exists */ }
|
|
537
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN last_processing_error TEXT`); } catch { /* already exists */ }
|
|
538
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN retry_after INTEGER`); } catch { /* already exists */ }
|
|
539
|
+
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN raw_payload TEXT`); } catch { /* already exists */ }
|
|
540
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN thread_type TEXT NOT NULL DEFAULT 'standard'`); } catch { /* already exists */ }
|
|
541
|
+
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN memory_scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ }
|
|
542
|
+
try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN thumbnail_base64 TEXT`); } catch { /* already exists */ }
|
|
543
|
+
try { database.run(/*sql*/ `ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* already exists */ }
|
|
544
|
+
try { database.run(/*sql*/ `ALTER TABLE messages ADD COLUMN metadata TEXT`); } catch { /* already exists */ }
|
|
545
|
+
|
|
546
|
+
migrateJobDeferrals(database);
|
|
547
|
+
migrateToolInvocationsFk(database);
|
|
548
|
+
migrateMemoryEntityRelationDedup(database);
|
|
549
|
+
migrateMemoryItemsFingerprintScopeUnique(database);
|
|
550
|
+
migrateMemoryItemsScopeSaltedFingerprints(database);
|
|
551
|
+
migrateAssistantIdToSelf(database);
|
|
552
|
+
migrateRemoveAssistantIdColumns(database);
|
|
553
|
+
migrateLlmUsageEventsDropAssistantId(database);
|
|
554
|
+
|
|
555
|
+
// Indexes for query performance on large datasets
|
|
556
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_request_logs_conv_created ON llm_request_logs(conversation_id, created_at)`);
|
|
557
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id)`);
|
|
558
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_tool_invocations_conversation_id ON tool_invocations(conversation_id)`);
|
|
559
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conversations_updated_at ON conversations(updated_at)`);
|
|
560
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_segments_message_segment ON memory_segments(message_id, segment_index)`);
|
|
561
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_segments_conversation_created ON memory_segments(conversation_id, created_at DESC)`);
|
|
562
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_sources_message_id ON memory_item_sources(message_id)`);
|
|
563
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_conflicts_status_created ON memory_item_conflicts(status, created_at)`);
|
|
564
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_conflicts_status_resolved_at ON memory_item_conflicts(status, resolved_at)`);
|
|
565
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_conflicts_scope_status ON memory_item_conflicts(scope_id, status)`);
|
|
566
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_conflicts_existing_item_id ON memory_item_conflicts(existing_item_id)`);
|
|
567
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_conflicts_candidate_item_id ON memory_item_conflicts(candidate_item_id)`);
|
|
568
|
+
database.run(/*sql*/ `
|
|
569
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_item_conflicts_pending_pair_unique
|
|
570
|
+
ON memory_item_conflicts(scope_id, existing_item_id, candidate_item_id)
|
|
571
|
+
WHERE status = 'pending_clarification'
|
|
572
|
+
`);
|
|
573
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_items_fingerprint_scope ON memory_items(fingerprint, scope_id)`);
|
|
574
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_kind_status ON memory_items(kind, status)`);
|
|
575
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_status_invalid_at ON memory_items(status, invalid_at)`);
|
|
576
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_scope_status_kind ON memory_items(scope_id, status, kind)`);
|
|
577
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_last_seen_at ON memory_items(last_seen_at)`);
|
|
578
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_embeddings_target ON memory_embeddings(target_type, target_id)`);
|
|
579
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_embeddings_provider_model ON memory_embeddings(provider, model)`);
|
|
580
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_jobs_status_run_after ON memory_jobs(status, run_after)`);
|
|
581
|
+
database.run(/*sql*/ `
|
|
582
|
+
CREATE INDEX IF NOT EXISTS idx_memory_jobs_conflict_resolve_dedupe
|
|
583
|
+
ON memory_jobs(
|
|
584
|
+
type,
|
|
585
|
+
status,
|
|
586
|
+
json_extract(payload, '$.messageId'),
|
|
587
|
+
COALESCE(json_extract(payload, '$.scopeId'), 'default')
|
|
588
|
+
)
|
|
589
|
+
`);
|
|
590
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_summaries_scope_time ON memory_summaries(scope, end_at DESC)`);
|
|
591
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_segments_scope_id ON memory_segments(scope_id)`);
|
|
592
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_scope_id ON memory_items(scope_id)`);
|
|
593
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_summaries_scope_id ON memory_summaries(scope_id)`);
|
|
594
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conversation_keys_key ON conversation_keys(conversation_key)`);
|
|
595
|
+
// Deduplicate before creating unique index — existing DBs may have duplicate content_hash values.
|
|
596
|
+
// Re-point message_attachments to the survivor (MIN rowid per content_hash), then delete dupes.
|
|
597
|
+
{
|
|
598
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
599
|
+
raw.exec(/*sql*/ `
|
|
600
|
+
UPDATE message_attachments
|
|
601
|
+
SET attachment_id = (
|
|
602
|
+
SELECT a_survivor.id
|
|
603
|
+
FROM attachments a_survivor
|
|
604
|
+
WHERE a_survivor.content_hash = (
|
|
605
|
+
SELECT a_dup.content_hash FROM attachments a_dup
|
|
606
|
+
WHERE a_dup.id = message_attachments.attachment_id
|
|
607
|
+
)
|
|
608
|
+
ORDER BY a_survivor.rowid
|
|
609
|
+
LIMIT 1
|
|
610
|
+
)
|
|
611
|
+
WHERE attachment_id IN (
|
|
612
|
+
SELECT id FROM attachments
|
|
613
|
+
WHERE content_hash IS NOT NULL
|
|
614
|
+
AND rowid NOT IN (
|
|
615
|
+
SELECT MIN(rowid) FROM attachments
|
|
616
|
+
WHERE content_hash IS NOT NULL
|
|
617
|
+
GROUP BY content_hash
|
|
618
|
+
)
|
|
619
|
+
)
|
|
620
|
+
`);
|
|
621
|
+
raw.exec(/*sql*/ `
|
|
622
|
+
DELETE FROM attachments
|
|
623
|
+
WHERE content_hash IS NOT NULL
|
|
624
|
+
AND rowid NOT IN (
|
|
625
|
+
SELECT MIN(rowid) FROM attachments
|
|
626
|
+
WHERE content_hash IS NOT NULL
|
|
627
|
+
GROUP BY content_hash
|
|
628
|
+
)
|
|
629
|
+
`);
|
|
630
|
+
}
|
|
631
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_attachments_content_dedup ON attachments(content_hash) WHERE content_hash IS NOT NULL`);
|
|
632
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id)`);
|
|
633
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_attachments_attachment_id ON message_attachments(attachment_id)`);
|
|
634
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_inbound_events_lookup ON channel_inbound_events(source_channel, external_chat_id, external_message_id)`);
|
|
635
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_inbound_events_conversation ON channel_inbound_events(conversation_id)`);
|
|
636
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_inbound_events_source_msg ON channel_inbound_events(source_channel, external_chat_id, source_message_id)`);
|
|
637
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_inbound_events_processing_retry ON channel_inbound_events(processing_status, retry_after)`);
|
|
638
|
+
|
|
639
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_runs_status ON message_runs(status)`);
|
|
640
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_runs_conversation ON message_runs(conversation_id)`);
|
|
641
|
+
|
|
642
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_reminders_status_fire_at ON reminders(status, fire_at)`);
|
|
643
|
+
|
|
644
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled_next_run ON cron_jobs(enabled, next_run_at)`);
|
|
645
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_jobs_syntax_enabled_next_run ON cron_jobs(schedule_syntax, enabled, next_run_at)`);
|
|
646
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_runs_job_id ON cron_runs(job_id)`);
|
|
647
|
+
|
|
648
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_accounts_service ON accounts(service)`);
|
|
649
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts(status)`);
|
|
650
|
+
|
|
651
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_usage_events_created_at ON llm_usage_events(created_at)`);
|
|
652
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_usage_events_provider ON llm_usage_events(provider)`);
|
|
653
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_usage_events_model ON llm_usage_events(model)`);
|
|
654
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_usage_events_actor ON llm_usage_events(actor)`);
|
|
655
|
+
|
|
656
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_shared_app_links_share_token ON shared_app_links(share_token)`);
|
|
657
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_home_base_app_links_app_id ON home_base_app_links(app_id)`);
|
|
658
|
+
|
|
659
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_published_pages_html_hash ON published_pages(html_hash)`);
|
|
660
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_published_pages_status ON published_pages(status)`);
|
|
661
|
+
|
|
662
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_watchers_enabled_next_poll ON watchers(enabled, next_poll_at)`);
|
|
663
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_watchers_status ON watchers(status)`);
|
|
664
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_watcher_events_watcher_id ON watcher_events(watcher_id)`);
|
|
665
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_watcher_events_disposition ON watcher_events(disposition)`);
|
|
666
|
+
|
|
667
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_entities_name ON memory_entities(name)`);
|
|
668
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_entities_type ON memory_entities(type)`);
|
|
669
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_entity_relations_unique_edge ON memory_entity_relations(source_entity_id, target_entity_id, relation)`);
|
|
670
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_entity_relations_source ON memory_entity_relations(source_entity_id)`);
|
|
671
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_entity_relations_target ON memory_entity_relations(target_entity_id)`);
|
|
672
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_entities_memory_item ON memory_item_entities(memory_item_id)`);
|
|
673
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_item_entities_entity ON memory_item_entities(entity_id)`);
|
|
674
|
+
|
|
675
|
+
// ── Contacts ────────────────────────────────────────────────────────
|
|
676
|
+
|
|
677
|
+
database.run(/*sql*/ `
|
|
678
|
+
CREATE TABLE IF NOT EXISTS contacts (
|
|
679
|
+
id TEXT PRIMARY KEY,
|
|
680
|
+
display_name TEXT NOT NULL,
|
|
681
|
+
relationship TEXT,
|
|
682
|
+
importance REAL NOT NULL DEFAULT 0.5,
|
|
683
|
+
response_expectation TEXT,
|
|
684
|
+
preferred_tone TEXT,
|
|
685
|
+
last_interaction INTEGER,
|
|
686
|
+
interaction_count INTEGER NOT NULL DEFAULT 0,
|
|
687
|
+
created_at INTEGER NOT NULL,
|
|
688
|
+
updated_at INTEGER NOT NULL
|
|
689
|
+
)
|
|
690
|
+
`);
|
|
691
|
+
|
|
692
|
+
database.run(/*sql*/ `
|
|
693
|
+
CREATE TABLE IF NOT EXISTS contact_channels (
|
|
694
|
+
id TEXT PRIMARY KEY,
|
|
695
|
+
contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
|
|
696
|
+
type TEXT NOT NULL,
|
|
697
|
+
address TEXT NOT NULL,
|
|
698
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
699
|
+
created_at INTEGER NOT NULL
|
|
700
|
+
)
|
|
701
|
+
`);
|
|
702
|
+
|
|
703
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_display_name ON contacts(display_name)`);
|
|
704
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_importance ON contacts(importance DESC)`);
|
|
705
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_last_interaction ON contacts(last_interaction DESC)`);
|
|
706
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contact_channels_contact_id ON contact_channels(contact_id)`);
|
|
707
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_contact_channels_type_address ON contact_channels(type, address)`);
|
|
708
|
+
|
|
709
|
+
// ── Triage Results ─────────────────────────────────────────────────
|
|
710
|
+
|
|
711
|
+
database.run(/*sql*/ `
|
|
712
|
+
CREATE TABLE IF NOT EXISTS triage_results (
|
|
713
|
+
id TEXT PRIMARY KEY,
|
|
714
|
+
channel TEXT NOT NULL,
|
|
715
|
+
sender TEXT NOT NULL,
|
|
716
|
+
category TEXT NOT NULL,
|
|
717
|
+
confidence REAL NOT NULL,
|
|
718
|
+
suggested_action TEXT NOT NULL,
|
|
719
|
+
matched_playbook_ids TEXT,
|
|
720
|
+
message_id TEXT,
|
|
721
|
+
created_at INTEGER NOT NULL
|
|
722
|
+
)
|
|
723
|
+
`);
|
|
724
|
+
|
|
725
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_channel ON triage_results(channel)`);
|
|
726
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_category ON triage_results(category)`);
|
|
727
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_sender ON triage_results(sender)`);
|
|
728
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_created_at ON triage_results(created_at DESC)`);
|
|
729
|
+
|
|
730
|
+
// ── Call Sessions (outgoing AI phone calls) ────────────────────────
|
|
731
|
+
|
|
732
|
+
database.run(/*sql*/ `
|
|
733
|
+
CREATE TABLE IF NOT EXISTS call_sessions (
|
|
734
|
+
id TEXT PRIMARY KEY,
|
|
735
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
736
|
+
provider TEXT NOT NULL,
|
|
737
|
+
provider_call_sid TEXT,
|
|
738
|
+
from_number TEXT NOT NULL,
|
|
739
|
+
to_number TEXT NOT NULL,
|
|
740
|
+
task TEXT,
|
|
741
|
+
status TEXT NOT NULL DEFAULT 'initiated',
|
|
742
|
+
started_at INTEGER,
|
|
743
|
+
ended_at INTEGER,
|
|
744
|
+
last_error TEXT,
|
|
745
|
+
created_at INTEGER NOT NULL,
|
|
746
|
+
updated_at INTEGER NOT NULL
|
|
747
|
+
)
|
|
748
|
+
`);
|
|
749
|
+
|
|
750
|
+
database.run(/*sql*/ `
|
|
751
|
+
CREATE TABLE IF NOT EXISTS call_events (
|
|
752
|
+
id TEXT PRIMARY KEY,
|
|
753
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
754
|
+
event_type TEXT NOT NULL,
|
|
755
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
756
|
+
created_at INTEGER NOT NULL
|
|
757
|
+
)
|
|
758
|
+
`);
|
|
759
|
+
|
|
760
|
+
database.run(/*sql*/ `
|
|
761
|
+
CREATE TABLE IF NOT EXISTS call_pending_questions (
|
|
762
|
+
id TEXT PRIMARY KEY,
|
|
763
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
764
|
+
question_text TEXT NOT NULL,
|
|
765
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
766
|
+
asked_at INTEGER NOT NULL,
|
|
767
|
+
answered_at INTEGER,
|
|
768
|
+
answer_text TEXT
|
|
769
|
+
)
|
|
770
|
+
`);
|
|
771
|
+
|
|
772
|
+
database.run(/*sql*/ `
|
|
773
|
+
CREATE TABLE IF NOT EXISTS processed_callbacks (
|
|
774
|
+
id TEXT PRIMARY KEY,
|
|
775
|
+
dedupe_key TEXT NOT NULL UNIQUE,
|
|
776
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
777
|
+
created_at INTEGER NOT NULL
|
|
778
|
+
)
|
|
779
|
+
`);
|
|
780
|
+
|
|
781
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_conversation_id ON call_sessions(conversation_id)`);
|
|
782
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_provider_call_sid ON call_sessions(provider_call_sid)`);
|
|
783
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_status ON call_sessions(status)`);
|
|
784
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_events_call_session_id ON call_events(call_session_id)`);
|
|
785
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_call_session_id ON call_pending_questions(call_session_id)`);
|
|
786
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_status ON call_pending_questions(status)`);
|
|
787
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_processed_callbacks_dedupe_key ON processed_callbacks(dedupe_key)`);
|
|
788
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_processed_callbacks_call_session_id ON processed_callbacks(call_session_id)`);
|
|
789
|
+
|
|
790
|
+
// Add claim ownership token to prevent cross-handler claim interference
|
|
791
|
+
try { database.run(/*sql*/ `ALTER TABLE processed_callbacks ADD COLUMN claim_id TEXT`); } catch { /* already exists */ }
|
|
792
|
+
|
|
793
|
+
// Caller identity persistence for auditability
|
|
794
|
+
try { database.run(/*sql*/ `ALTER TABLE call_sessions ADD COLUMN caller_identity_mode TEXT`); } catch { /* already exists */ }
|
|
795
|
+
try { database.run(/*sql*/ `ALTER TABLE call_sessions ADD COLUMN caller_identity_source TEXT`); } catch { /* already exists */ }
|
|
796
|
+
|
|
797
|
+
// Unique constraint: at most one non-null provider_call_sid per (provider, provider_call_sid).
|
|
798
|
+
// On upgraded databases that pre-date this constraint, duplicate rows may exist; deduplicate
|
|
799
|
+
// them first to avoid a UNIQUE constraint failure that would prevent startup.
|
|
800
|
+
migrateCallSessionsProviderSidDedup(database);
|
|
801
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_call_sessions_provider_sid_unique ON call_sessions(provider, provider_call_sid) WHERE provider_call_sid IS NOT NULL`);
|
|
802
|
+
|
|
803
|
+
// ── Follow-ups ─────────────────────────────────────────────────────
|
|
804
|
+
|
|
805
|
+
database.run(/*sql*/ `
|
|
806
|
+
CREATE TABLE IF NOT EXISTS followups (
|
|
807
|
+
id TEXT PRIMARY KEY,
|
|
808
|
+
channel TEXT NOT NULL,
|
|
809
|
+
thread_id TEXT NOT NULL,
|
|
810
|
+
contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
|
|
811
|
+
sent_at INTEGER NOT NULL,
|
|
812
|
+
expected_response_by INTEGER,
|
|
813
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
814
|
+
reminder_cron_id TEXT,
|
|
815
|
+
created_at INTEGER NOT NULL,
|
|
816
|
+
updated_at INTEGER NOT NULL
|
|
817
|
+
)
|
|
818
|
+
`);
|
|
819
|
+
|
|
820
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_followups_status ON followups(status)`);
|
|
821
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_followups_channel ON followups(channel)`);
|
|
822
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_followups_contact_id ON followups(contact_id)`);
|
|
823
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_followups_channel_thread ON followups(channel, thread_id)`);
|
|
824
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_followups_status_expected ON followups(status, expected_response_by)`);
|
|
825
|
+
|
|
826
|
+
// ── Tasks ─────────────────────────────────────────────────────────
|
|
827
|
+
|
|
828
|
+
database.run(/*sql*/ `
|
|
829
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
830
|
+
id TEXT PRIMARY KEY,
|
|
831
|
+
title TEXT NOT NULL,
|
|
832
|
+
template TEXT NOT NULL,
|
|
833
|
+
input_schema TEXT,
|
|
834
|
+
context_flags TEXT,
|
|
835
|
+
required_tools TEXT,
|
|
836
|
+
created_from_conversation_id TEXT,
|
|
837
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
838
|
+
created_at INTEGER NOT NULL,
|
|
839
|
+
updated_at INTEGER NOT NULL
|
|
840
|
+
)
|
|
841
|
+
`);
|
|
842
|
+
|
|
843
|
+
database.run(/*sql*/ `
|
|
844
|
+
CREATE TABLE IF NOT EXISTS task_runs (
|
|
845
|
+
id TEXT PRIMARY KEY,
|
|
846
|
+
task_id TEXT NOT NULL REFERENCES tasks(id),
|
|
847
|
+
conversation_id TEXT,
|
|
848
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
849
|
+
started_at INTEGER,
|
|
850
|
+
finished_at INTEGER,
|
|
851
|
+
error TEXT,
|
|
852
|
+
principal_id TEXT,
|
|
853
|
+
memory_scope_id TEXT,
|
|
854
|
+
created_at INTEGER NOT NULL
|
|
855
|
+
)
|
|
856
|
+
`);
|
|
857
|
+
|
|
858
|
+
database.run(/*sql*/ `
|
|
859
|
+
CREATE TABLE IF NOT EXISTS task_candidates (
|
|
860
|
+
id TEXT PRIMARY KEY,
|
|
861
|
+
source_conversation_id TEXT NOT NULL,
|
|
862
|
+
compiled_template TEXT NOT NULL,
|
|
863
|
+
confidence REAL,
|
|
864
|
+
required_tools TEXT,
|
|
865
|
+
created_at INTEGER NOT NULL,
|
|
866
|
+
promoted_task_id TEXT
|
|
867
|
+
)
|
|
868
|
+
`);
|
|
869
|
+
|
|
870
|
+
// ── Work Items (Tasks) ──────────────────────────────────────────────
|
|
871
|
+
|
|
872
|
+
database.run(/*sql*/ `
|
|
873
|
+
CREATE TABLE IF NOT EXISTS work_items (
|
|
874
|
+
id TEXT PRIMARY KEY,
|
|
875
|
+
task_id TEXT NOT NULL REFERENCES tasks(id),
|
|
876
|
+
title TEXT NOT NULL,
|
|
877
|
+
notes TEXT,
|
|
878
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
879
|
+
priority_tier INTEGER NOT NULL DEFAULT 1,
|
|
880
|
+
sort_index INTEGER,
|
|
881
|
+
last_run_id TEXT,
|
|
882
|
+
last_run_conversation_id TEXT,
|
|
883
|
+
last_run_status TEXT,
|
|
884
|
+
source_type TEXT,
|
|
885
|
+
source_id TEXT,
|
|
886
|
+
created_at INTEGER NOT NULL,
|
|
887
|
+
updated_at INTEGER NOT NULL
|
|
888
|
+
)
|
|
889
|
+
`);
|
|
890
|
+
|
|
891
|
+
// Work item run contract snapshot
|
|
892
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN required_tools TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN required_tools (likely already exists)'); }
|
|
893
|
+
|
|
894
|
+
// Work item permission preflight columns
|
|
895
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN approved_tools TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN approved_tools (likely already exists)'); }
|
|
896
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN approval_status TEXT DEFAULT 'none'`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN approval_status (likely already exists)'); }
|
|
897
|
+
|
|
898
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(status)`);
|
|
899
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_task_id ON work_items(task_id)`);
|
|
900
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_priority_sort ON work_items(priority_tier, sort_index)`);
|
|
901
|
+
|
|
902
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)`);
|
|
903
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_task_runs_task_id ON task_runs(task_id)`);
|
|
904
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status)`);
|
|
905
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_task_candidates_promoted ON task_candidates(promoted_task_id)`);
|
|
906
|
+
|
|
907
|
+
// ── External Conversation Bindings ──────────────────────────────────
|
|
908
|
+
|
|
909
|
+
database.run(/*sql*/ `
|
|
910
|
+
CREATE TABLE IF NOT EXISTS external_conversation_bindings (
|
|
911
|
+
conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
|
|
912
|
+
source_channel TEXT NOT NULL,
|
|
913
|
+
external_chat_id TEXT NOT NULL,
|
|
914
|
+
external_user_id TEXT,
|
|
915
|
+
display_name TEXT,
|
|
916
|
+
username TEXT,
|
|
917
|
+
created_at INTEGER NOT NULL,
|
|
918
|
+
updated_at INTEGER NOT NULL,
|
|
919
|
+
last_inbound_at INTEGER,
|
|
920
|
+
last_outbound_at INTEGER
|
|
921
|
+
)
|
|
922
|
+
`);
|
|
923
|
+
|
|
924
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat ON external_conversation_bindings(source_channel, external_chat_id)`);
|
|
925
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel ON external_conversation_bindings(source_channel)`);
|
|
926
|
+
|
|
927
|
+
migrateExtConvBindingsChannelChatUnique(database);
|
|
928
|
+
|
|
929
|
+
// ── Channel Guardian ───────────────────────────────────────────────
|
|
930
|
+
|
|
931
|
+
database.run(/*sql*/ `
|
|
932
|
+
CREATE TABLE IF NOT EXISTS channel_guardian_bindings (
|
|
933
|
+
id TEXT PRIMARY KEY,
|
|
934
|
+
assistant_id TEXT NOT NULL,
|
|
935
|
+
channel TEXT NOT NULL,
|
|
936
|
+
guardian_external_user_id TEXT NOT NULL,
|
|
937
|
+
guardian_delivery_chat_id TEXT NOT NULL,
|
|
938
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
939
|
+
verified_at INTEGER NOT NULL,
|
|
940
|
+
verified_via TEXT NOT NULL DEFAULT 'challenge',
|
|
941
|
+
metadata_json TEXT,
|
|
942
|
+
created_at INTEGER NOT NULL,
|
|
943
|
+
updated_at INTEGER NOT NULL
|
|
944
|
+
)
|
|
945
|
+
`);
|
|
946
|
+
|
|
947
|
+
database.run(/*sql*/ `
|
|
948
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_guardian_bindings_active
|
|
949
|
+
ON channel_guardian_bindings(assistant_id, channel)
|
|
950
|
+
WHERE status = 'active'
|
|
951
|
+
`);
|
|
952
|
+
|
|
953
|
+
database.run(/*sql*/ `
|
|
954
|
+
CREATE TABLE IF NOT EXISTS channel_guardian_verification_challenges (
|
|
955
|
+
id TEXT PRIMARY KEY,
|
|
956
|
+
assistant_id TEXT NOT NULL,
|
|
957
|
+
channel TEXT NOT NULL,
|
|
958
|
+
challenge_hash TEXT NOT NULL,
|
|
959
|
+
expires_at INTEGER NOT NULL,
|
|
960
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
961
|
+
created_by_session_id TEXT,
|
|
962
|
+
consumed_by_external_user_id TEXT,
|
|
963
|
+
consumed_by_chat_id TEXT,
|
|
964
|
+
created_at INTEGER NOT NULL,
|
|
965
|
+
updated_at INTEGER NOT NULL
|
|
966
|
+
)
|
|
967
|
+
`);
|
|
968
|
+
|
|
969
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_guardian_challenges_lookup ON channel_guardian_verification_challenges(assistant_id, channel, challenge_hash, status)`);
|
|
970
|
+
|
|
971
|
+
database.run(/*sql*/ `
|
|
972
|
+
CREATE TABLE IF NOT EXISTS channel_guardian_approval_requests (
|
|
973
|
+
id TEXT PRIMARY KEY,
|
|
974
|
+
run_id TEXT NOT NULL,
|
|
975
|
+
conversation_id TEXT NOT NULL,
|
|
976
|
+
channel TEXT NOT NULL,
|
|
977
|
+
requester_external_user_id TEXT NOT NULL,
|
|
978
|
+
requester_chat_id TEXT NOT NULL,
|
|
979
|
+
guardian_external_user_id TEXT NOT NULL,
|
|
980
|
+
guardian_chat_id TEXT NOT NULL,
|
|
981
|
+
tool_name TEXT NOT NULL,
|
|
982
|
+
risk_level TEXT,
|
|
983
|
+
reason TEXT,
|
|
984
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
985
|
+
decided_by_external_user_id TEXT,
|
|
986
|
+
expires_at INTEGER NOT NULL,
|
|
987
|
+
created_at INTEGER NOT NULL,
|
|
988
|
+
updated_at INTEGER NOT NULL
|
|
989
|
+
)
|
|
990
|
+
`);
|
|
991
|
+
|
|
992
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_guardian_approval_run ON channel_guardian_approval_requests(run_id, status)`);
|
|
993
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_channel_guardian_approval_status ON channel_guardian_approval_requests(status)`);
|
|
994
|
+
|
|
995
|
+
// ── Channel Guardian Verification Rate Limits ─────────────────────
|
|
996
|
+
|
|
997
|
+
database.run(/*sql*/ `
|
|
998
|
+
CREATE TABLE IF NOT EXISTS channel_guardian_rate_limits (
|
|
999
|
+
id TEXT PRIMARY KEY,
|
|
1000
|
+
assistant_id TEXT NOT NULL,
|
|
1001
|
+
channel TEXT NOT NULL,
|
|
1002
|
+
actor_external_user_id TEXT NOT NULL,
|
|
1003
|
+
actor_chat_id TEXT NOT NULL,
|
|
1004
|
+
invalid_attempts INTEGER NOT NULL DEFAULT 0,
|
|
1005
|
+
window_started_at INTEGER NOT NULL,
|
|
1006
|
+
locked_until INTEGER,
|
|
1007
|
+
created_at INTEGER NOT NULL,
|
|
1008
|
+
updated_at INTEGER NOT NULL
|
|
1009
|
+
)
|
|
1010
|
+
`);
|
|
1011
|
+
|
|
1012
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_guardian_rate_limits_actor ON channel_guardian_rate_limits(assistant_id, channel, actor_external_user_id, actor_chat_id)`);
|
|
1013
|
+
|
|
1014
|
+
migrateMemoryFtsBackfill(database);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* One-shot migration: reconcile old deferral history into the new `deferrals` column.
|
|
1019
|
+
*
|
|
1020
|
+
* Before the `deferrals` column was added, `deferMemoryJob` incremented `attempts`.
|
|
1021
|
+
* After the column is added with DEFAULT 0, those legacy jobs still carry the old
|
|
1022
|
+
* attempt count (which was really a deferral count) while `deferrals` is 0. This
|
|
1023
|
+
* moves the attempt count into `deferrals` and resets `attempts` to 0.
|
|
1024
|
+
*
|
|
1025
|
+
* This migration MUST run only once. On subsequent startups, post-migration jobs
|
|
1026
|
+
* that genuinely failed via `failMemoryJob` (attempts > 0, deferrals = 0, non-null
|
|
1027
|
+
* last_error) must NOT be touched — resetting their attempts would let them bypass
|
|
1028
|
+
* the configured maxAttempts budget across restarts.
|
|
1029
|
+
*
|
|
1030
|
+
* We use a `memory_checkpoints` row to ensure the migration runs exactly once.
|
|
1031
|
+
*/
|
|
1032
|
+
function migrateJobDeferrals(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1033
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1034
|
+
const checkpoint = raw.query(
|
|
1035
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = 'migration_job_deferrals'`
|
|
1036
|
+
).get();
|
|
1037
|
+
if (checkpoint) return;
|
|
1038
|
+
|
|
1039
|
+
try {
|
|
1040
|
+
raw.exec(/*sql*/ `
|
|
1041
|
+
BEGIN;
|
|
1042
|
+
UPDATE memory_jobs
|
|
1043
|
+
SET deferrals = attempts,
|
|
1044
|
+
attempts = 0,
|
|
1045
|
+
last_error = NULL,
|
|
1046
|
+
updated_at = ${Date.now()}
|
|
1047
|
+
WHERE status = 'pending'
|
|
1048
|
+
AND attempts > 0
|
|
1049
|
+
AND deferrals = 0
|
|
1050
|
+
AND type IN ('embed_segment', 'embed_item', 'embed_summary');
|
|
1051
|
+
INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at)
|
|
1052
|
+
VALUES ('migration_job_deferrals', '1', ${Date.now()});
|
|
1053
|
+
COMMIT;
|
|
1054
|
+
`);
|
|
1055
|
+
} catch (e) {
|
|
1056
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1057
|
+
throw e;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Migrate existing tool_invocations table to add FK constraint with ON DELETE CASCADE.
|
|
1063
|
+
* SQLite doesn't support ALTER TABLE ADD CONSTRAINT, so we rebuild the table.
|
|
1064
|
+
* This is idempotent: it checks whether the FK already exists before migrating.
|
|
1065
|
+
*/
|
|
1066
|
+
function migrateToolInvocationsFk(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1067
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1068
|
+
const row = raw.query(`SELECT sql FROM sqlite_master WHERE type='table' AND name='tool_invocations'`).get() as { sql: string } | null;
|
|
1069
|
+
if (!row) return; // table doesn't exist yet (will be created above)
|
|
1070
|
+
|
|
1071
|
+
// If the DDL already contains REFERENCES, the FK is in place
|
|
1072
|
+
if (row.sql.includes('REFERENCES')) return;
|
|
1073
|
+
|
|
1074
|
+
raw.exec('PRAGMA foreign_keys = OFF');
|
|
1075
|
+
try {
|
|
1076
|
+
raw.exec(/*sql*/ `
|
|
1077
|
+
BEGIN;
|
|
1078
|
+
CREATE TABLE tool_invocations_new (
|
|
1079
|
+
id TEXT PRIMARY KEY,
|
|
1080
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
1081
|
+
tool_name TEXT NOT NULL,
|
|
1082
|
+
input TEXT NOT NULL,
|
|
1083
|
+
result TEXT NOT NULL,
|
|
1084
|
+
decision TEXT NOT NULL,
|
|
1085
|
+
risk_level TEXT NOT NULL,
|
|
1086
|
+
duration_ms INTEGER NOT NULL,
|
|
1087
|
+
created_at INTEGER NOT NULL
|
|
1088
|
+
);
|
|
1089
|
+
INSERT INTO tool_invocations_new SELECT t.* FROM tool_invocations t
|
|
1090
|
+
WHERE EXISTS (SELECT 1 FROM conversations c WHERE c.id = t.conversation_id);
|
|
1091
|
+
DROP TABLE tool_invocations;
|
|
1092
|
+
ALTER TABLE tool_invocations_new RENAME TO tool_invocations;
|
|
1093
|
+
COMMIT;
|
|
1094
|
+
`);
|
|
1095
|
+
} catch (e) {
|
|
1096
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1097
|
+
throw e;
|
|
1098
|
+
} finally {
|
|
1099
|
+
raw.exec('PRAGMA foreign_keys = ON');
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Backfill FTS rows for existing memory_segments records when upgrading from a
|
|
1105
|
+
* version that may not have had trigger-managed FTS.
|
|
1106
|
+
*/
|
|
1107
|
+
function migrateMemoryFtsBackfill(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1108
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1109
|
+
const ftsCountRow = raw.query(`SELECT COUNT(*) AS c FROM memory_segment_fts`).get() as { c: number } | null;
|
|
1110
|
+
const ftsCount = ftsCountRow?.c ?? 0;
|
|
1111
|
+
if (ftsCount > 0) return;
|
|
1112
|
+
|
|
1113
|
+
raw.exec(/*sql*/ `
|
|
1114
|
+
INSERT INTO memory_segment_fts(segment_id, text)
|
|
1115
|
+
SELECT id, text FROM memory_segments
|
|
1116
|
+
`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* One-shot migration: merge duplicate relation edges so uniqueness can be
|
|
1121
|
+
* enforced on (source_entity_id, target_entity_id, relation).
|
|
1122
|
+
*/
|
|
1123
|
+
function migrateMemoryEntityRelationDedup(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1124
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1125
|
+
const checkpointKey = 'migration_memory_entity_relations_dedup_v1';
|
|
1126
|
+
const checkpoint = raw.query(
|
|
1127
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1128
|
+
).get(checkpointKey);
|
|
1129
|
+
if (checkpoint) return;
|
|
1130
|
+
|
|
1131
|
+
try {
|
|
1132
|
+
raw.exec('BEGIN');
|
|
1133
|
+
|
|
1134
|
+
raw.exec(/*sql*/ `
|
|
1135
|
+
CREATE TEMP TABLE memory_entity_relation_merge AS
|
|
1136
|
+
WITH ranked AS (
|
|
1137
|
+
SELECT
|
|
1138
|
+
source_entity_id,
|
|
1139
|
+
target_entity_id,
|
|
1140
|
+
relation,
|
|
1141
|
+
first_seen_at,
|
|
1142
|
+
last_seen_at,
|
|
1143
|
+
evidence,
|
|
1144
|
+
ROW_NUMBER() OVER (
|
|
1145
|
+
PARTITION BY source_entity_id, target_entity_id, relation
|
|
1146
|
+
ORDER BY last_seen_at DESC, first_seen_at DESC, id DESC
|
|
1147
|
+
) AS rank_latest
|
|
1148
|
+
FROM memory_entity_relations
|
|
1149
|
+
)
|
|
1150
|
+
SELECT
|
|
1151
|
+
source_entity_id,
|
|
1152
|
+
target_entity_id,
|
|
1153
|
+
relation,
|
|
1154
|
+
MIN(first_seen_at) AS merged_first_seen_at,
|
|
1155
|
+
MAX(last_seen_at) AS merged_last_seen_at,
|
|
1156
|
+
MAX(CASE WHEN rank_latest = 1 THEN evidence ELSE NULL END) AS merged_evidence
|
|
1157
|
+
FROM ranked
|
|
1158
|
+
GROUP BY source_entity_id, target_entity_id, relation
|
|
1159
|
+
`);
|
|
1160
|
+
|
|
1161
|
+
raw.exec(/*sql*/ `DELETE FROM memory_entity_relations`);
|
|
1162
|
+
|
|
1163
|
+
raw.exec(/*sql*/ `
|
|
1164
|
+
INSERT INTO memory_entity_relations (
|
|
1165
|
+
id,
|
|
1166
|
+
source_entity_id,
|
|
1167
|
+
target_entity_id,
|
|
1168
|
+
relation,
|
|
1169
|
+
evidence,
|
|
1170
|
+
first_seen_at,
|
|
1171
|
+
last_seen_at
|
|
1172
|
+
)
|
|
1173
|
+
SELECT
|
|
1174
|
+
lower(hex(randomblob(16))),
|
|
1175
|
+
source_entity_id,
|
|
1176
|
+
target_entity_id,
|
|
1177
|
+
relation,
|
|
1178
|
+
merged_evidence,
|
|
1179
|
+
merged_first_seen_at,
|
|
1180
|
+
merged_last_seen_at
|
|
1181
|
+
FROM memory_entity_relation_merge
|
|
1182
|
+
`);
|
|
1183
|
+
|
|
1184
|
+
raw.exec(/*sql*/ `DROP TABLE memory_entity_relation_merge`);
|
|
1185
|
+
|
|
1186
|
+
raw.query(
|
|
1187
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1188
|
+
).run(checkpointKey, Date.now());
|
|
1189
|
+
|
|
1190
|
+
raw.exec('COMMIT');
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1193
|
+
throw e;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* Migrate from a column-level UNIQUE on fingerprint to a compound unique
|
|
1199
|
+
* index on (fingerprint, scope_id) so that the same item can exist in
|
|
1200
|
+
* different scopes independently.
|
|
1201
|
+
*/
|
|
1202
|
+
function migrateMemoryItemsFingerprintScopeUnique(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1203
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1204
|
+
const checkpointKey = 'migration_memory_items_fingerprint_scope_unique_v1';
|
|
1205
|
+
const checkpoint = raw.query(
|
|
1206
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1207
|
+
).get(checkpointKey);
|
|
1208
|
+
if (checkpoint) return;
|
|
1209
|
+
|
|
1210
|
+
// Check if the old column-level UNIQUE constraint still exists by inspecting
|
|
1211
|
+
// the CREATE TABLE DDL for the word UNIQUE (the PK also creates an autoindex,
|
|
1212
|
+
// so we cannot rely on sqlite_autoindex_* presence alone).
|
|
1213
|
+
const tableDdl = raw.query(
|
|
1214
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_items'`,
|
|
1215
|
+
).get() as { sql: string } | null;
|
|
1216
|
+
if (!tableDdl || !tableDdl.sql.match(/fingerprint\s+TEXT\s+NOT\s+NULL\s+UNIQUE/i)) {
|
|
1217
|
+
// No column-level UNIQUE on fingerprint — either fresh DB or already migrated.
|
|
1218
|
+
raw.query(
|
|
1219
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1220
|
+
).run(checkpointKey, Date.now());
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// Rebuild the table without the column-level UNIQUE constraint.
|
|
1225
|
+
raw.exec('PRAGMA foreign_keys = OFF');
|
|
1226
|
+
try {
|
|
1227
|
+
raw.exec('BEGIN');
|
|
1228
|
+
|
|
1229
|
+
// Create new table without UNIQUE on fingerprint — all other columns
|
|
1230
|
+
// match the latest schema (including migration-added columns).
|
|
1231
|
+
raw.exec(/*sql*/ `
|
|
1232
|
+
CREATE TABLE memory_items_new (
|
|
1233
|
+
id TEXT PRIMARY KEY,
|
|
1234
|
+
kind TEXT NOT NULL,
|
|
1235
|
+
subject TEXT NOT NULL,
|
|
1236
|
+
statement TEXT NOT NULL,
|
|
1237
|
+
status TEXT NOT NULL,
|
|
1238
|
+
confidence REAL NOT NULL,
|
|
1239
|
+
fingerprint TEXT NOT NULL,
|
|
1240
|
+
first_seen_at INTEGER NOT NULL,
|
|
1241
|
+
last_seen_at INTEGER NOT NULL,
|
|
1242
|
+
last_used_at INTEGER,
|
|
1243
|
+
importance REAL,
|
|
1244
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
1245
|
+
valid_from INTEGER,
|
|
1246
|
+
invalid_at INTEGER,
|
|
1247
|
+
verification_state TEXT NOT NULL DEFAULT 'assistant_inferred',
|
|
1248
|
+
scope_id TEXT NOT NULL DEFAULT 'default'
|
|
1249
|
+
)
|
|
1250
|
+
`);
|
|
1251
|
+
|
|
1252
|
+
raw.exec(/*sql*/ `
|
|
1253
|
+
INSERT INTO memory_items_new
|
|
1254
|
+
SELECT id, kind, subject, statement, status, confidence, fingerprint,
|
|
1255
|
+
first_seen_at, last_seen_at, last_used_at, importance, access_count,
|
|
1256
|
+
valid_from, invalid_at, verification_state, scope_id
|
|
1257
|
+
FROM memory_items
|
|
1258
|
+
`);
|
|
1259
|
+
|
|
1260
|
+
raw.exec(/*sql*/ `DROP TABLE memory_items`);
|
|
1261
|
+
raw.exec(/*sql*/ `ALTER TABLE memory_items_new RENAME TO memory_items`);
|
|
1262
|
+
|
|
1263
|
+
raw.query(
|
|
1264
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1265
|
+
).run(checkpointKey, Date.now());
|
|
1266
|
+
|
|
1267
|
+
raw.exec('COMMIT');
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1270
|
+
throw e;
|
|
1271
|
+
} finally {
|
|
1272
|
+
raw.exec('PRAGMA foreign_keys = ON');
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* One-shot migration: recompute fingerprints for existing memory items to
|
|
1278
|
+
* include the scope_id prefix introduced in the scope-salted fingerprint PR.
|
|
1279
|
+
*
|
|
1280
|
+
* Old format: sha256(`${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
|
|
1281
|
+
* New format: sha256(`${scopeId}|${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
|
|
1282
|
+
*
|
|
1283
|
+
* Without this migration, pre-upgrade items would never match on re-extraction,
|
|
1284
|
+
* causing duplicates and broken deduplication.
|
|
1285
|
+
*/
|
|
1286
|
+
function migrateMemoryItemsScopeSaltedFingerprints(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1287
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1288
|
+
const checkpointKey = 'migration_memory_items_scope_salted_fingerprints_v1';
|
|
1289
|
+
const checkpoint = raw.query(
|
|
1290
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1291
|
+
).get(checkpointKey);
|
|
1292
|
+
if (checkpoint) return;
|
|
1293
|
+
|
|
1294
|
+
interface ItemRow {
|
|
1295
|
+
id: string;
|
|
1296
|
+
kind: string;
|
|
1297
|
+
subject: string;
|
|
1298
|
+
statement: string;
|
|
1299
|
+
scope_id: string;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const items = raw.query(
|
|
1303
|
+
`SELECT id, kind, subject, statement, scope_id FROM memory_items`,
|
|
1304
|
+
).all() as ItemRow[];
|
|
1305
|
+
|
|
1306
|
+
if (items.length === 0) {
|
|
1307
|
+
raw.query(
|
|
1308
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1309
|
+
).run(checkpointKey, Date.now());
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
try {
|
|
1314
|
+
raw.exec('BEGIN');
|
|
1315
|
+
|
|
1316
|
+
const updateStmt = raw.prepare(
|
|
1317
|
+
`UPDATE memory_items SET fingerprint = ? WHERE id = ?`,
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
for (const item of items) {
|
|
1321
|
+
const fingerprint = computeMemoryFingerprint(item.scope_id, item.kind, item.subject, item.statement);
|
|
1322
|
+
updateStmt.run(fingerprint, item.id);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
raw.query(
|
|
1326
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1327
|
+
).run(checkpointKey, Date.now());
|
|
1328
|
+
|
|
1329
|
+
raw.exec('COMMIT');
|
|
1330
|
+
} catch (e) {
|
|
1331
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1332
|
+
throw e;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* One-shot migration: normalize all assistant_id values in assistant-scoped tables
|
|
1338
|
+
* to "self" so they are visible after the daemon switched to the implicit single-tenant
|
|
1339
|
+
* identity.
|
|
1340
|
+
*
|
|
1341
|
+
* Before this change, rows were keyed by the real assistantId string passed via the
|
|
1342
|
+
* HTTP route. After the route change, all lookups use the constant "self". Without this
|
|
1343
|
+
* migration an upgraded daemon would see empty history / attachment lists for existing
|
|
1344
|
+
* data that was stored under the old assistantId.
|
|
1345
|
+
*
|
|
1346
|
+
* Affected tables:
|
|
1347
|
+
* - conversation_keys UNIQUE (assistant_id, conversation_key)
|
|
1348
|
+
* - attachments UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1349
|
+
* - channel_inbound_events UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1350
|
+
* - message_runs no unique constraint on assistant_id
|
|
1351
|
+
*
|
|
1352
|
+
* Data-safety guarantees:
|
|
1353
|
+
* - conversation_keys: when a key exists under both 'self' and a real assistantId, the
|
|
1354
|
+
* 'self' row is updated to point to the real-assistantId conversation (which holds the
|
|
1355
|
+
* historical message thread). The 'self' conversation may be orphaned but is not deleted.
|
|
1356
|
+
* - attachments: message_attachments links are remapped to the surviving attachment before
|
|
1357
|
+
* any duplicate row is deleted, so no message loses its attachment metadata.
|
|
1358
|
+
* - channel_inbound_events: only delivery-tracking metadata, not user content; dedup
|
|
1359
|
+
* keeps one row per unique (channel, chat, message) tuple.
|
|
1360
|
+
* - All conversations and messages remain untouched — only assistant_id index columns
|
|
1361
|
+
* and key-lookup rows are modified.
|
|
1362
|
+
*/
|
|
1363
|
+
function migrateAssistantIdToSelf(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1364
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1365
|
+
const checkpointKey = 'migration_normalize_assistant_id_to_self_v1';
|
|
1366
|
+
const checkpoint = raw.query(
|
|
1367
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1368
|
+
).get(checkpointKey);
|
|
1369
|
+
if (checkpoint) return;
|
|
1370
|
+
|
|
1371
|
+
// On fresh installs the tables are created without assistant_id (PR 7+). Skip the
|
|
1372
|
+
// migration if NONE of the four affected tables have the column — pre-seed the
|
|
1373
|
+
// checkpoint so subsequent startups are also skipped. Checking all four (not just
|
|
1374
|
+
// conversation_keys) avoids a false negative on very old installs where
|
|
1375
|
+
// conversation_keys may not exist yet but other tables still carry assistant_id data.
|
|
1376
|
+
const affectedTables = ['conversation_keys', 'attachments', 'channel_inbound_events', 'message_runs'];
|
|
1377
|
+
const anyHasAssistantId = affectedTables.some((tbl) => {
|
|
1378
|
+
const ddl = raw.query(
|
|
1379
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,
|
|
1380
|
+
).get(tbl) as { sql: string } | null;
|
|
1381
|
+
return ddl?.sql.includes('assistant_id') ?? false;
|
|
1382
|
+
});
|
|
1383
|
+
if (!anyHasAssistantId) {
|
|
1384
|
+
raw.query(
|
|
1385
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1386
|
+
).run(checkpointKey, Date.now());
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Helper: returns true if the given table's current DDL contains 'assistant_id'.
|
|
1391
|
+
const tableHasAssistantId = (tbl: string): boolean => {
|
|
1392
|
+
const ddl = raw.query(
|
|
1393
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`,
|
|
1394
|
+
).get(tbl) as { sql: string } | null;
|
|
1395
|
+
return ddl?.sql.includes('assistant_id') ?? false;
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
try {
|
|
1399
|
+
raw.exec('BEGIN');
|
|
1400
|
+
|
|
1401
|
+
// Each section is guarded so that SQL referencing assistant_id is only executed
|
|
1402
|
+
// when the column still exists in that table. This handles mixed-schema states
|
|
1403
|
+
// (e.g., very old installs where some tables may already lack the column).
|
|
1404
|
+
|
|
1405
|
+
// conversation_keys: UNIQUE (assistant_id, conversation_key)
|
|
1406
|
+
if (tableHasAssistantId('conversation_keys')) {
|
|
1407
|
+
// Step 1: Among non-self rows, keep only one per conversation_key so the
|
|
1408
|
+
// bulk UPDATE cannot hit a (non-self-A, key) + (non-self-B, key) collision.
|
|
1409
|
+
raw.exec(/*sql*/ `
|
|
1410
|
+
DELETE FROM conversation_keys
|
|
1411
|
+
WHERE assistant_id != 'self'
|
|
1412
|
+
AND rowid NOT IN (
|
|
1413
|
+
SELECT MIN(rowid) FROM conversation_keys
|
|
1414
|
+
WHERE assistant_id != 'self'
|
|
1415
|
+
GROUP BY conversation_key
|
|
1416
|
+
)
|
|
1417
|
+
`);
|
|
1418
|
+
// Step 2: For 'self' rows that have a non-self counterpart with the same
|
|
1419
|
+
// conversation_key, update the 'self' row to use the non-self row's
|
|
1420
|
+
// conversation_id. This preserves the historical conversation (which
|
|
1421
|
+
// has the message history from before the route change) rather than
|
|
1422
|
+
// discarding it in favour of a potentially-empty 'self' conversation.
|
|
1423
|
+
raw.exec(/*sql*/ `
|
|
1424
|
+
UPDATE conversation_keys
|
|
1425
|
+
SET conversation_id = (
|
|
1426
|
+
SELECT ck_ns.conversation_id
|
|
1427
|
+
FROM conversation_keys ck_ns
|
|
1428
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1429
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1430
|
+
ORDER BY ck_ns.rowid
|
|
1431
|
+
LIMIT 1
|
|
1432
|
+
)
|
|
1433
|
+
WHERE assistant_id = 'self'
|
|
1434
|
+
AND EXISTS (
|
|
1435
|
+
SELECT 1 FROM conversation_keys ck_ns
|
|
1436
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1437
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1438
|
+
)
|
|
1439
|
+
`);
|
|
1440
|
+
// Step 3: Delete the now-redundant non-self rows (their conversation_ids
|
|
1441
|
+
// have been preserved in the 'self' rows above).
|
|
1442
|
+
raw.exec(/*sql*/ `
|
|
1443
|
+
DELETE FROM conversation_keys
|
|
1444
|
+
WHERE assistant_id != 'self'
|
|
1445
|
+
AND EXISTS (
|
|
1446
|
+
SELECT 1 FROM conversation_keys ck2
|
|
1447
|
+
WHERE ck2.assistant_id = 'self'
|
|
1448
|
+
AND ck2.conversation_key = conversation_keys.conversation_key
|
|
1449
|
+
)
|
|
1450
|
+
`);
|
|
1451
|
+
// Step 4: Remaining non-self rows have no 'self' counterpart — safe to bulk-update.
|
|
1452
|
+
raw.exec(/*sql*/ `
|
|
1453
|
+
UPDATE conversation_keys SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1454
|
+
`);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// attachments: UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1458
|
+
//
|
|
1459
|
+
// message_attachments rows reference attachment IDs with ON DELETE CASCADE, so we
|
|
1460
|
+
// must remap links to the surviving row BEFORE deleting duplicates to avoid
|
|
1461
|
+
// silently dropping attachment metadata from messages.
|
|
1462
|
+
if (tableHasAssistantId('attachments')) {
|
|
1463
|
+
// Step 1: Remap message_attachments from non-self duplicates to their survivor
|
|
1464
|
+
// (MIN rowid per content_hash group), then delete the duplicates.
|
|
1465
|
+
raw.exec(/*sql*/ `
|
|
1466
|
+
UPDATE message_attachments
|
|
1467
|
+
SET attachment_id = (
|
|
1468
|
+
SELECT a_survivor.id
|
|
1469
|
+
FROM attachments a_survivor
|
|
1470
|
+
WHERE a_survivor.assistant_id != 'self'
|
|
1471
|
+
AND a_survivor.content_hash = (
|
|
1472
|
+
SELECT a_dup.content_hash FROM attachments a_dup
|
|
1473
|
+
WHERE a_dup.id = message_attachments.attachment_id
|
|
1474
|
+
)
|
|
1475
|
+
ORDER BY a_survivor.rowid
|
|
1476
|
+
LIMIT 1
|
|
1477
|
+
)
|
|
1478
|
+
WHERE attachment_id IN (
|
|
1479
|
+
SELECT id FROM attachments
|
|
1480
|
+
WHERE assistant_id != 'self'
|
|
1481
|
+
AND content_hash IS NOT NULL
|
|
1482
|
+
AND rowid NOT IN (
|
|
1483
|
+
SELECT MIN(rowid) FROM attachments
|
|
1484
|
+
WHERE assistant_id != 'self' AND content_hash IS NOT NULL
|
|
1485
|
+
GROUP BY content_hash
|
|
1486
|
+
)
|
|
1487
|
+
)
|
|
1488
|
+
`);
|
|
1489
|
+
raw.exec(/*sql*/ `
|
|
1490
|
+
DELETE FROM attachments
|
|
1491
|
+
WHERE assistant_id != 'self'
|
|
1492
|
+
AND content_hash IS NOT NULL
|
|
1493
|
+
AND rowid NOT IN (
|
|
1494
|
+
SELECT MIN(rowid) FROM attachments
|
|
1495
|
+
WHERE assistant_id != 'self'
|
|
1496
|
+
AND content_hash IS NOT NULL
|
|
1497
|
+
GROUP BY content_hash
|
|
1498
|
+
)
|
|
1499
|
+
`);
|
|
1500
|
+
// Step 2: Remap message_attachments from non-self rows conflicting with a 'self'
|
|
1501
|
+
// row to the 'self' row, then delete the now-unlinked non-self rows.
|
|
1502
|
+
raw.exec(/*sql*/ `
|
|
1503
|
+
UPDATE message_attachments
|
|
1504
|
+
SET attachment_id = (
|
|
1505
|
+
SELECT a_self.id
|
|
1506
|
+
FROM attachments a_self
|
|
1507
|
+
WHERE a_self.assistant_id = 'self'
|
|
1508
|
+
AND a_self.content_hash = (
|
|
1509
|
+
SELECT a_ns.content_hash FROM attachments a_ns
|
|
1510
|
+
WHERE a_ns.id = message_attachments.attachment_id
|
|
1511
|
+
)
|
|
1512
|
+
LIMIT 1
|
|
1513
|
+
)
|
|
1514
|
+
WHERE attachment_id IN (
|
|
1515
|
+
SELECT id FROM attachments
|
|
1516
|
+
WHERE assistant_id != 'self'
|
|
1517
|
+
AND content_hash IS NOT NULL
|
|
1518
|
+
AND EXISTS (
|
|
1519
|
+
SELECT 1 FROM attachments a2
|
|
1520
|
+
WHERE a2.assistant_id = 'self'
|
|
1521
|
+
AND a2.content_hash = attachments.content_hash
|
|
1522
|
+
)
|
|
1523
|
+
)
|
|
1524
|
+
`);
|
|
1525
|
+
raw.exec(/*sql*/ `
|
|
1526
|
+
DELETE FROM attachments
|
|
1527
|
+
WHERE assistant_id != 'self'
|
|
1528
|
+
AND content_hash IS NOT NULL
|
|
1529
|
+
AND EXISTS (
|
|
1530
|
+
SELECT 1 FROM attachments a2
|
|
1531
|
+
WHERE a2.assistant_id = 'self'
|
|
1532
|
+
AND a2.content_hash = attachments.content_hash
|
|
1533
|
+
)
|
|
1534
|
+
`);
|
|
1535
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1536
|
+
raw.exec(/*sql*/ `
|
|
1537
|
+
UPDATE attachments SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1538
|
+
`);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// channel_inbound_events: UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1542
|
+
if (tableHasAssistantId('channel_inbound_events')) {
|
|
1543
|
+
// Step 1: Dedup non-self rows sharing the same (source_channel, external_chat_id, external_message_id).
|
|
1544
|
+
raw.exec(/*sql*/ `
|
|
1545
|
+
DELETE FROM channel_inbound_events
|
|
1546
|
+
WHERE assistant_id != 'self'
|
|
1547
|
+
AND rowid NOT IN (
|
|
1548
|
+
SELECT MIN(rowid) FROM channel_inbound_events
|
|
1549
|
+
WHERE assistant_id != 'self'
|
|
1550
|
+
GROUP BY source_channel, external_chat_id, external_message_id
|
|
1551
|
+
)
|
|
1552
|
+
`);
|
|
1553
|
+
// Step 2: Delete non-self rows conflicting with existing 'self' rows.
|
|
1554
|
+
raw.exec(/*sql*/ `
|
|
1555
|
+
DELETE FROM channel_inbound_events
|
|
1556
|
+
WHERE assistant_id != 'self'
|
|
1557
|
+
AND EXISTS (
|
|
1558
|
+
SELECT 1 FROM channel_inbound_events e2
|
|
1559
|
+
WHERE e2.assistant_id = 'self'
|
|
1560
|
+
AND e2.source_channel = channel_inbound_events.source_channel
|
|
1561
|
+
AND e2.external_chat_id = channel_inbound_events.external_chat_id
|
|
1562
|
+
AND e2.external_message_id = channel_inbound_events.external_message_id
|
|
1563
|
+
)
|
|
1564
|
+
`);
|
|
1565
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1566
|
+
raw.exec(/*sql*/ `
|
|
1567
|
+
UPDATE channel_inbound_events SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1568
|
+
`);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// message_runs: no unique constraint on assistant_id — simple bulk update
|
|
1572
|
+
if (tableHasAssistantId('message_runs')) {
|
|
1573
|
+
raw.exec(/*sql*/ `
|
|
1574
|
+
UPDATE message_runs SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1575
|
+
`);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
raw.query(
|
|
1579
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1580
|
+
).run(checkpointKey, Date.now());
|
|
1581
|
+
|
|
1582
|
+
raw.exec('COMMIT');
|
|
1583
|
+
} catch (e) {
|
|
1584
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1585
|
+
throw e;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* One-shot migration: rebuild tables that previously stored assistant_id to remove
|
|
1591
|
+
* that column now that all rows are keyed to the implicit single-tenant identity ("self").
|
|
1592
|
+
*
|
|
1593
|
+
* Must run AFTER migrateAssistantIdToSelf (which normalises all values to "self")
|
|
1594
|
+
* so there are no constraint violations when recreating the tables without the
|
|
1595
|
+
* assistant_id dimension.
|
|
1596
|
+
*
|
|
1597
|
+
* Each table section is guarded by a DDL check so this is safe on fresh installs
|
|
1598
|
+
* where the column was never created in the first place.
|
|
1599
|
+
*
|
|
1600
|
+
* Tables rebuilt:
|
|
1601
|
+
* - conversation_keys UNIQUE (conversation_key)
|
|
1602
|
+
* - attachments no structural unique; content-dedup index updated
|
|
1603
|
+
* - channel_inbound_events UNIQUE (source_channel, external_chat_id, external_message_id)
|
|
1604
|
+
* - message_runs no unique constraint on assistant_id
|
|
1605
|
+
* - llm_usage_events nullable column with no constraint
|
|
1606
|
+
*/
|
|
1607
|
+
function migrateRemoveAssistantIdColumns(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1608
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1609
|
+
const checkpointKey = 'migration_remove_assistant_id_columns_v1';
|
|
1610
|
+
const checkpoint = raw.query(
|
|
1611
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1612
|
+
).get(checkpointKey);
|
|
1613
|
+
if (checkpoint) return;
|
|
1614
|
+
|
|
1615
|
+
raw.exec('PRAGMA foreign_keys = OFF');
|
|
1616
|
+
try {
|
|
1617
|
+
raw.exec('BEGIN');
|
|
1618
|
+
|
|
1619
|
+
// --- conversation_keys ---
|
|
1620
|
+
const ckDdl = raw.query(
|
|
1621
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'conversation_keys'`,
|
|
1622
|
+
).get() as { sql: string } | null;
|
|
1623
|
+
if (ckDdl?.sql.includes('assistant_id')) {
|
|
1624
|
+
raw.exec(/*sql*/ `
|
|
1625
|
+
CREATE TABLE conversation_keys_new (
|
|
1626
|
+
id TEXT PRIMARY KEY,
|
|
1627
|
+
conversation_key TEXT NOT NULL UNIQUE,
|
|
1628
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
1629
|
+
created_at INTEGER NOT NULL
|
|
1630
|
+
)
|
|
1631
|
+
`);
|
|
1632
|
+
raw.exec(/*sql*/ `
|
|
1633
|
+
INSERT INTO conversation_keys_new (id, conversation_key, conversation_id, created_at)
|
|
1634
|
+
SELECT id, conversation_key, conversation_id, created_at FROM conversation_keys
|
|
1635
|
+
`);
|
|
1636
|
+
raw.exec(/*sql*/ `DROP TABLE conversation_keys`);
|
|
1637
|
+
raw.exec(/*sql*/ `ALTER TABLE conversation_keys_new RENAME TO conversation_keys`);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// --- attachments ---
|
|
1641
|
+
const attDdl = raw.query(
|
|
1642
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'attachments'`,
|
|
1643
|
+
).get() as { sql: string } | null;
|
|
1644
|
+
if (attDdl?.sql.includes('assistant_id')) {
|
|
1645
|
+
raw.exec(/*sql*/ `
|
|
1646
|
+
CREATE TABLE attachments_new (
|
|
1647
|
+
id TEXT PRIMARY KEY,
|
|
1648
|
+
original_filename TEXT NOT NULL,
|
|
1649
|
+
mime_type TEXT NOT NULL,
|
|
1650
|
+
size_bytes INTEGER NOT NULL,
|
|
1651
|
+
kind TEXT NOT NULL,
|
|
1652
|
+
data_base64 TEXT NOT NULL,
|
|
1653
|
+
content_hash TEXT,
|
|
1654
|
+
thumbnail_base64 TEXT,
|
|
1655
|
+
created_at INTEGER NOT NULL
|
|
1656
|
+
)
|
|
1657
|
+
`);
|
|
1658
|
+
raw.exec(/*sql*/ `
|
|
1659
|
+
INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at)
|
|
1660
|
+
SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at FROM attachments
|
|
1661
|
+
`);
|
|
1662
|
+
raw.exec(/*sql*/ `DROP TABLE attachments`);
|
|
1663
|
+
raw.exec(/*sql*/ `ALTER TABLE attachments_new RENAME TO attachments`);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// --- channel_inbound_events ---
|
|
1667
|
+
const cieDdl = raw.query(
|
|
1668
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'channel_inbound_events'`,
|
|
1669
|
+
).get() as { sql: string } | null;
|
|
1670
|
+
if (cieDdl?.sql.includes('assistant_id')) {
|
|
1671
|
+
raw.exec(/*sql*/ `
|
|
1672
|
+
CREATE TABLE channel_inbound_events_new (
|
|
1673
|
+
id TEXT PRIMARY KEY,
|
|
1674
|
+
source_channel TEXT NOT NULL,
|
|
1675
|
+
external_chat_id TEXT NOT NULL,
|
|
1676
|
+
external_message_id TEXT NOT NULL,
|
|
1677
|
+
source_message_id TEXT,
|
|
1678
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
1679
|
+
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
1680
|
+
delivery_status TEXT NOT NULL DEFAULT 'pending',
|
|
1681
|
+
processing_status TEXT NOT NULL DEFAULT 'pending',
|
|
1682
|
+
processing_attempts INTEGER NOT NULL DEFAULT 0,
|
|
1683
|
+
last_processing_error TEXT,
|
|
1684
|
+
retry_after INTEGER,
|
|
1685
|
+
raw_payload TEXT,
|
|
1686
|
+
created_at INTEGER NOT NULL,
|
|
1687
|
+
updated_at INTEGER NOT NULL,
|
|
1688
|
+
UNIQUE (source_channel, external_chat_id, external_message_id)
|
|
1689
|
+
)
|
|
1690
|
+
`);
|
|
1691
|
+
raw.exec(/*sql*/ `
|
|
1692
|
+
INSERT INTO channel_inbound_events_new (
|
|
1693
|
+
id, source_channel, external_chat_id, external_message_id, source_message_id,
|
|
1694
|
+
conversation_id, message_id, delivery_status, processing_status,
|
|
1695
|
+
processing_attempts, last_processing_error, retry_after, raw_payload,
|
|
1696
|
+
created_at, updated_at
|
|
1697
|
+
)
|
|
1698
|
+
SELECT
|
|
1699
|
+
id, source_channel, external_chat_id, external_message_id, source_message_id,
|
|
1700
|
+
conversation_id, message_id, delivery_status, processing_status,
|
|
1701
|
+
processing_attempts, last_processing_error, retry_after, raw_payload,
|
|
1702
|
+
created_at, updated_at
|
|
1703
|
+
FROM channel_inbound_events
|
|
1704
|
+
`);
|
|
1705
|
+
raw.exec(/*sql*/ `DROP TABLE channel_inbound_events`);
|
|
1706
|
+
raw.exec(/*sql*/ `ALTER TABLE channel_inbound_events_new RENAME TO channel_inbound_events`);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// --- message_runs ---
|
|
1710
|
+
const mrDdl = raw.query(
|
|
1711
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'message_runs'`,
|
|
1712
|
+
).get() as { sql: string } | null;
|
|
1713
|
+
if (mrDdl?.sql.includes('assistant_id')) {
|
|
1714
|
+
raw.exec(/*sql*/ `
|
|
1715
|
+
CREATE TABLE message_runs_new (
|
|
1716
|
+
id TEXT PRIMARY KEY,
|
|
1717
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
1718
|
+
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
1719
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
1720
|
+
pending_confirmation TEXT,
|
|
1721
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1722
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1723
|
+
estimated_cost REAL NOT NULL DEFAULT 0,
|
|
1724
|
+
error TEXT,
|
|
1725
|
+
created_at INTEGER NOT NULL,
|
|
1726
|
+
updated_at INTEGER NOT NULL
|
|
1727
|
+
)
|
|
1728
|
+
`);
|
|
1729
|
+
raw.exec(/*sql*/ `
|
|
1730
|
+
INSERT INTO message_runs_new (
|
|
1731
|
+
id, conversation_id, message_id, status, pending_confirmation,
|
|
1732
|
+
input_tokens, output_tokens, estimated_cost, error, created_at, updated_at
|
|
1733
|
+
)
|
|
1734
|
+
SELECT
|
|
1735
|
+
id, conversation_id, message_id, status, pending_confirmation,
|
|
1736
|
+
input_tokens, output_tokens, estimated_cost, error, created_at, updated_at
|
|
1737
|
+
FROM message_runs
|
|
1738
|
+
`);
|
|
1739
|
+
raw.exec(/*sql*/ `DROP TABLE message_runs`);
|
|
1740
|
+
raw.exec(/*sql*/ `ALTER TABLE message_runs_new RENAME TO message_runs`);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// --- llm_usage_events ---
|
|
1744
|
+
const lueDdl = raw.query(
|
|
1745
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
|
|
1746
|
+
).get() as { sql: string } | null;
|
|
1747
|
+
if (lueDdl?.sql.includes('assistant_id')) {
|
|
1748
|
+
raw.exec(/*sql*/ `
|
|
1749
|
+
CREATE TABLE llm_usage_events_new (
|
|
1750
|
+
id TEXT PRIMARY KEY,
|
|
1751
|
+
created_at INTEGER NOT NULL,
|
|
1752
|
+
conversation_id TEXT,
|
|
1753
|
+
run_id TEXT,
|
|
1754
|
+
request_id TEXT,
|
|
1755
|
+
actor TEXT NOT NULL,
|
|
1756
|
+
provider TEXT NOT NULL,
|
|
1757
|
+
model TEXT NOT NULL,
|
|
1758
|
+
input_tokens INTEGER NOT NULL,
|
|
1759
|
+
output_tokens INTEGER NOT NULL,
|
|
1760
|
+
cache_creation_input_tokens INTEGER,
|
|
1761
|
+
cache_read_input_tokens INTEGER,
|
|
1762
|
+
estimated_cost_usd REAL,
|
|
1763
|
+
pricing_status TEXT NOT NULL,
|
|
1764
|
+
metadata_json TEXT
|
|
1765
|
+
)
|
|
1766
|
+
`);
|
|
1767
|
+
raw.exec(/*sql*/ `
|
|
1768
|
+
INSERT INTO llm_usage_events_new (
|
|
1769
|
+
id, created_at, conversation_id, run_id, request_id, actor, provider, model,
|
|
1770
|
+
input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
|
|
1771
|
+
estimated_cost_usd, pricing_status, metadata_json
|
|
1772
|
+
)
|
|
1773
|
+
SELECT
|
|
1774
|
+
id, created_at, conversation_id, run_id, request_id, actor, provider, model,
|
|
1775
|
+
input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
|
|
1776
|
+
estimated_cost_usd, pricing_status, metadata_json
|
|
1777
|
+
FROM llm_usage_events
|
|
1778
|
+
`);
|
|
1779
|
+
raw.exec(/*sql*/ `DROP TABLE llm_usage_events`);
|
|
1780
|
+
raw.exec(/*sql*/ `ALTER TABLE llm_usage_events_new RENAME TO llm_usage_events`);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
raw.query(
|
|
1784
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1785
|
+
).run(checkpointKey, Date.now());
|
|
1786
|
+
|
|
1787
|
+
raw.exec('COMMIT');
|
|
1788
|
+
} catch (e) {
|
|
1789
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1790
|
+
throw e;
|
|
1791
|
+
} finally {
|
|
1792
|
+
raw.exec('PRAGMA foreign_keys = ON');
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* One-shot migration: rebuild llm_usage_events to drop the assistant_id column.
|
|
1798
|
+
*
|
|
1799
|
+
* This is a SEPARATE migration from migrateRemoveAssistantIdColumns so that installs
|
|
1800
|
+
* where the 4-table version of that migration already ran (checkpoint already set)
|
|
1801
|
+
* still get the llm_usage_events column removed. Without a separate checkpoint key,
|
|
1802
|
+
* those installs would skip the llm_usage_events rebuild entirely.
|
|
1803
|
+
*
|
|
1804
|
+
* Safe on fresh installs (DDL guard exits early) and idempotent via checkpoint.
|
|
1805
|
+
*/
|
|
1806
|
+
function migrateLlmUsageEventsDropAssistantId(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1807
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1808
|
+
const checkpointKey = 'migration_remove_assistant_id_lue_v1';
|
|
1809
|
+
const checkpoint = raw.query(
|
|
1810
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1811
|
+
).get(checkpointKey);
|
|
1812
|
+
if (checkpoint) return;
|
|
1813
|
+
|
|
1814
|
+
// DDL guard: if the column was already removed (fresh install or migrateRemoveAssistantIdColumns
|
|
1815
|
+
// ran with the llm_usage_events block), just record the checkpoint and exit.
|
|
1816
|
+
const lueDdl = raw.query(
|
|
1817
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
|
|
1818
|
+
).get() as { sql: string } | null;
|
|
1819
|
+
|
|
1820
|
+
if (!lueDdl?.sql.includes('assistant_id')) {
|
|
1821
|
+
raw.query(
|
|
1822
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1823
|
+
).run(checkpointKey, Date.now());
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
raw.exec('PRAGMA foreign_keys = OFF');
|
|
1828
|
+
try {
|
|
1829
|
+
raw.exec('BEGIN');
|
|
1830
|
+
|
|
1831
|
+
raw.exec(/*sql*/ `
|
|
1832
|
+
CREATE TABLE llm_usage_events_new (
|
|
1833
|
+
id TEXT PRIMARY KEY,
|
|
1834
|
+
created_at INTEGER NOT NULL,
|
|
1835
|
+
conversation_id TEXT,
|
|
1836
|
+
run_id TEXT,
|
|
1837
|
+
request_id TEXT,
|
|
1838
|
+
actor TEXT NOT NULL,
|
|
1839
|
+
provider TEXT NOT NULL,
|
|
1840
|
+
model TEXT NOT NULL,
|
|
1841
|
+
input_tokens INTEGER NOT NULL,
|
|
1842
|
+
output_tokens INTEGER NOT NULL,
|
|
1843
|
+
cache_creation_input_tokens INTEGER,
|
|
1844
|
+
cache_read_input_tokens INTEGER,
|
|
1845
|
+
estimated_cost_usd REAL,
|
|
1846
|
+
pricing_status TEXT NOT NULL,
|
|
1847
|
+
metadata_json TEXT
|
|
1848
|
+
)
|
|
1849
|
+
`);
|
|
1850
|
+
raw.exec(/*sql*/ `
|
|
1851
|
+
INSERT INTO llm_usage_events_new (
|
|
1852
|
+
id, created_at, conversation_id, run_id, request_id, actor, provider, model,
|
|
1853
|
+
input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
|
|
1854
|
+
estimated_cost_usd, pricing_status, metadata_json
|
|
1855
|
+
)
|
|
1856
|
+
SELECT
|
|
1857
|
+
id, created_at, conversation_id, run_id, request_id, actor, provider, model,
|
|
1858
|
+
input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,
|
|
1859
|
+
estimated_cost_usd, pricing_status, metadata_json
|
|
1860
|
+
FROM llm_usage_events
|
|
1861
|
+
`);
|
|
1862
|
+
raw.exec(/*sql*/ `DROP TABLE llm_usage_events`);
|
|
1863
|
+
raw.exec(/*sql*/ `ALTER TABLE llm_usage_events_new RENAME TO llm_usage_events`);
|
|
1864
|
+
|
|
1865
|
+
raw.query(
|
|
1866
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1867
|
+
).run(checkpointKey, Date.now());
|
|
1868
|
+
|
|
1869
|
+
raw.exec('COMMIT');
|
|
1870
|
+
} catch (e) {
|
|
1871
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1872
|
+
throw e;
|
|
1873
|
+
} finally {
|
|
1874
|
+
raw.exec('PRAGMA foreign_keys = ON');
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* One-shot migration: deduplicate external_conversation_bindings rows that
|
|
1880
|
+
* share the same (source_channel, external_chat_id), then create a unique
|
|
1881
|
+
* index to enforce the invariant at DB level.
|
|
1882
|
+
*
|
|
1883
|
+
* For each duplicate group, the binding with the newest updatedAt (then
|
|
1884
|
+
* createdAt) is kept; older duplicates are deleted.
|
|
1885
|
+
*/
|
|
1886
|
+
function migrateExtConvBindingsChannelChatUnique(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1887
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1888
|
+
|
|
1889
|
+
// If the unique index already exists, nothing to do.
|
|
1890
|
+
const idxExists = raw.query(
|
|
1891
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_ext_conv_bindings_channel_chat_unique'`,
|
|
1892
|
+
).get();
|
|
1893
|
+
if (idxExists) return;
|
|
1894
|
+
|
|
1895
|
+
// Check if the table exists (first boot edge case).
|
|
1896
|
+
const tableExists = raw.query(
|
|
1897
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'external_conversation_bindings'`,
|
|
1898
|
+
).get();
|
|
1899
|
+
if (!tableExists) return;
|
|
1900
|
+
|
|
1901
|
+
// Remove duplicates: keep the row with the newest updatedAt, then createdAt.
|
|
1902
|
+
// Since conversation_id is the PK (rowid alias), we use it for ordering ties.
|
|
1903
|
+
try {
|
|
1904
|
+
raw.exec('BEGIN');
|
|
1905
|
+
|
|
1906
|
+
raw.exec(/*sql*/ `
|
|
1907
|
+
DELETE FROM external_conversation_bindings
|
|
1908
|
+
WHERE rowid NOT IN (
|
|
1909
|
+
SELECT rowid FROM (
|
|
1910
|
+
SELECT rowid,
|
|
1911
|
+
ROW_NUMBER() OVER (
|
|
1912
|
+
PARTITION BY source_channel, external_chat_id
|
|
1913
|
+
ORDER BY updated_at DESC, created_at DESC, rowid DESC
|
|
1914
|
+
) AS rn
|
|
1915
|
+
FROM external_conversation_bindings
|
|
1916
|
+
)
|
|
1917
|
+
WHERE rn = 1
|
|
1918
|
+
)
|
|
1919
|
+
`);
|
|
1920
|
+
|
|
1921
|
+
raw.exec(/*sql*/ `
|
|
1922
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_unique
|
|
1923
|
+
ON external_conversation_bindings(source_channel, external_chat_id)
|
|
1924
|
+
`);
|
|
1925
|
+
|
|
1926
|
+
raw.exec('COMMIT');
|
|
1927
|
+
} catch (e) {
|
|
1928
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1929
|
+
throw e;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* One-shot migration: remove duplicate (provider, provider_call_sid) rows from
|
|
1935
|
+
* call_sessions so that the unique index can be created safely on upgraded databases
|
|
1936
|
+
* that pre-date the constraint.
|
|
1937
|
+
*
|
|
1938
|
+
* For each set of duplicates, the most recently updated row is kept.
|
|
1939
|
+
*/
|
|
1940
|
+
function migrateCallSessionsProviderSidDedup(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1941
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1942
|
+
|
|
1943
|
+
// Quick check: if the unique index already exists, no dedup is needed.
|
|
1944
|
+
const idxExists = raw.query(
|
|
1945
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_call_sessions_provider_sid_unique'`,
|
|
1946
|
+
).get();
|
|
1947
|
+
if (idxExists) return;
|
|
1948
|
+
|
|
1949
|
+
// Check if the table even exists yet (first boot).
|
|
1950
|
+
const tableExists = raw.query(
|
|
1951
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'call_sessions'`,
|
|
1952
|
+
).get();
|
|
1953
|
+
if (!tableExists) return;
|
|
1954
|
+
|
|
1955
|
+
// Count duplicates before doing any work.
|
|
1956
|
+
const dupCount = raw.query(/*sql*/ `
|
|
1957
|
+
SELECT COUNT(*) AS c FROM (
|
|
1958
|
+
SELECT provider, provider_call_sid
|
|
1959
|
+
FROM call_sessions
|
|
1960
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1961
|
+
GROUP BY provider, provider_call_sid
|
|
1962
|
+
HAVING COUNT(*) > 1
|
|
1963
|
+
)
|
|
1964
|
+
`).get() as { c: number } | null;
|
|
1965
|
+
|
|
1966
|
+
if (!dupCount || dupCount.c === 0) return;
|
|
1967
|
+
|
|
1968
|
+
log.warn({ duplicateGroups: dupCount.c }, 'Deduplicating call_sessions with duplicate provider_call_sid before creating unique index');
|
|
1969
|
+
|
|
1970
|
+
try {
|
|
1971
|
+
raw.exec('BEGIN');
|
|
1972
|
+
|
|
1973
|
+
// Keep the most recently updated row per (provider, provider_call_sid);
|
|
1974
|
+
// delete the rest.
|
|
1975
|
+
raw.exec(/*sql*/ `
|
|
1976
|
+
DELETE FROM call_sessions
|
|
1977
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1978
|
+
AND rowid NOT IN (
|
|
1979
|
+
SELECT MAX(rowid) FROM call_sessions
|
|
1980
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1981
|
+
GROUP BY provider, provider_call_sid
|
|
1982
|
+
)
|
|
1983
|
+
`);
|
|
1984
|
+
|
|
1985
|
+
raw.exec('COMMIT');
|
|
1986
|
+
} catch (e) {
|
|
1987
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1988
|
+
throw e;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
|