@vellumai/assistant 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -2,6 +2,13 @@ import { getConfig } from "../config/loader.js";
|
|
|
2
2
|
import type { AssistantConfig } from "../config/types.js";
|
|
3
3
|
import { getLogger } from "../util/logger.js";
|
|
4
4
|
import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
|
|
5
|
+
import {
|
|
6
|
+
getLastScheduledCleanupEnqueueMs,
|
|
7
|
+
markScheduledCleanupEnqueued,
|
|
8
|
+
resetCleanupScheduleThrottle as resetCleanupScheduleThrottleImpl,
|
|
9
|
+
} from "./cleanup-schedule-state.js";
|
|
10
|
+
import { conversationAnalyzeJob } from "./conversation-analyze-job.js";
|
|
11
|
+
import { maybeRunDbMaintenance } from "./db-maintenance.js";
|
|
5
12
|
import { bootstrapFromHistory } from "./graph/bootstrap.js";
|
|
6
13
|
import { runConsolidation } from "./graph/consolidation.js";
|
|
7
14
|
import { runDecayTick } from "./graph/decay.js";
|
|
@@ -160,6 +167,7 @@ export async function runMemoryJobsOnce(
|
|
|
160
167
|
maybeEnqueueScheduledCleanupJobs(config);
|
|
161
168
|
}
|
|
162
169
|
maybeEnqueueGraphMaintenanceJobs();
|
|
170
|
+
maybeRunDbMaintenance();
|
|
163
171
|
return 0;
|
|
164
172
|
}
|
|
165
173
|
|
|
@@ -256,6 +264,7 @@ export async function runMemoryJobsOnce(
|
|
|
256
264
|
maybeEnqueueScheduledCleanupJobs(config);
|
|
257
265
|
}
|
|
258
266
|
maybeEnqueueGraphMaintenanceJobs();
|
|
267
|
+
maybeRunDbMaintenance();
|
|
259
268
|
return processed;
|
|
260
269
|
}
|
|
261
270
|
|
|
@@ -417,6 +426,9 @@ async function processJob(
|
|
|
417
426
|
case "graph_extract":
|
|
418
427
|
await graphExtractJob(job, config);
|
|
419
428
|
return;
|
|
429
|
+
case "conversation_analyze":
|
|
430
|
+
await conversationAnalyzeJob(job, config);
|
|
431
|
+
return;
|
|
420
432
|
case "graph_decay":
|
|
421
433
|
graphDecayJob(job);
|
|
422
434
|
return;
|
|
@@ -449,12 +461,13 @@ async function processJob(
|
|
|
449
461
|
|
|
450
462
|
// ── Cleanup scheduling ─────────────────────────────────────────────
|
|
451
463
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Re-export of the shared throttle-reset helper. The underlying state lives
|
|
466
|
+
* in cleanup-schedule-state.ts so that lighter-weight callers (e.g.
|
|
467
|
+
* ConfigWatcher) can reset it without pulling in jobs-worker's transitive
|
|
468
|
+
* imports.
|
|
469
|
+
*/
|
|
470
|
+
export const resetCleanupScheduleThrottle = resetCleanupScheduleThrottleImpl;
|
|
458
471
|
|
|
459
472
|
/**
|
|
460
473
|
* Enqueue periodic cleanup jobs using config-driven retention windows.
|
|
@@ -466,7 +479,7 @@ export function maybeEnqueueScheduledCleanupJobs(
|
|
|
466
479
|
): boolean {
|
|
467
480
|
const cleanup = config.memory.cleanup;
|
|
468
481
|
if (!cleanup.enabled) return false;
|
|
469
|
-
if (nowMs -
|
|
482
|
+
if (nowMs - getLastScheduledCleanupEnqueueMs() < cleanup.enqueueIntervalMs)
|
|
470
483
|
return false;
|
|
471
484
|
|
|
472
485
|
const pruneConversationsJobId =
|
|
@@ -474,10 +487,10 @@ export function maybeEnqueueScheduledCleanupJobs(
|
|
|
474
487
|
? enqueuePruneOldConversationsJob(cleanup.conversationRetentionDays)
|
|
475
488
|
: null;
|
|
476
489
|
const pruneLlmRequestLogsJobId =
|
|
477
|
-
cleanup.llmRequestLogRetentionMs
|
|
490
|
+
cleanup.llmRequestLogRetentionMs !== null
|
|
478
491
|
? enqueuePruneOldLlmRequestLogsJob(cleanup.llmRequestLogRetentionMs)
|
|
479
492
|
: null;
|
|
480
|
-
|
|
493
|
+
markScheduledCleanupEnqueued(nowMs);
|
|
481
494
|
log.debug(
|
|
482
495
|
{
|
|
483
496
|
pruneConversationsJobId,
|
|
@@ -9,6 +9,11 @@ import type {
|
|
|
9
9
|
import { getDb } from "./db.js";
|
|
10
10
|
import { rawAll } from "./raw-query.js";
|
|
11
11
|
import { llmUsageEvents } from "./schema.js";
|
|
12
|
+
import {
|
|
13
|
+
bucketEventsByDay,
|
|
14
|
+
bucketEventsByHour,
|
|
15
|
+
type UsageEventBucketRow,
|
|
16
|
+
} from "./usage-buckets.js";
|
|
12
17
|
|
|
13
18
|
// ---------------------------------------------------------------------------
|
|
14
19
|
// Write
|
|
@@ -152,8 +157,25 @@ export type UsageGranularity = "daily" | "hourly";
|
|
|
152
157
|
|
|
153
158
|
/** A single time bucket with its aggregate totals. */
|
|
154
159
|
export interface UsageDayBucket {
|
|
155
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* Stable unique identifier for the bucket. Safe for use as a SwiftUI/React
|
|
162
|
+
* list key. Distinct even for DST fall-back duplicate hours (which share the
|
|
163
|
+
* same `date` string). Daily buckets use `date` directly; hourly buckets use
|
|
164
|
+
* "YYYY-MM-DD HH:00|<offsetMinutes>" to disambiguate repeated local hours.
|
|
165
|
+
*/
|
|
166
|
+
bucketId: string;
|
|
167
|
+
/**
|
|
168
|
+
* Local-time bucket key in the requested tz:
|
|
169
|
+
* "YYYY-MM-DD" (daily) or "YYYY-MM-DD HH:00" (hourly).
|
|
170
|
+
* NOT unique: on DST fall-back days, two 01:00 hourly buckets share this key.
|
|
171
|
+
* Use `bucketId` as a list identifier and `date` for display/sort only.
|
|
172
|
+
*/
|
|
156
173
|
date: string;
|
|
174
|
+
/**
|
|
175
|
+
* Human-readable label for the bucket, formatted in the requested tz.
|
|
176
|
+
* Hourly: "3pm". Daily: "Apr 11".
|
|
177
|
+
*/
|
|
178
|
+
displayLabel?: string;
|
|
157
179
|
/** Direct input tokens only; cache traffic is tracked separately in totals. */
|
|
158
180
|
totalInputTokens: number;
|
|
159
181
|
totalOutputTokens: number;
|
|
@@ -164,6 +186,14 @@ export interface UsageDayBucket {
|
|
|
164
186
|
/** A grouped breakdown row (by actor, provider, or model). */
|
|
165
187
|
export interface UsageGroupBreakdown {
|
|
166
188
|
group: string;
|
|
189
|
+
/**
|
|
190
|
+
* Stable identifier for the group. Populated with the conversation id when
|
|
191
|
+
* `groupBy === "conversation"` (and `null` for that mode's "Other" bucket,
|
|
192
|
+
* which aggregates events with no conversation id). For all other group-bys
|
|
193
|
+
* (`actor`, `provider`, `model`) this is always `null` — the raw grouping
|
|
194
|
+
* column is already exposed via `group`.
|
|
195
|
+
*/
|
|
196
|
+
groupId: string | null;
|
|
167
197
|
/** Direct input tokens only; cache traffic is reported separately below. */
|
|
168
198
|
totalInputTokens: number;
|
|
169
199
|
totalOutputTokens: number;
|
|
@@ -186,16 +216,10 @@ interface TotalsRow {
|
|
|
186
216
|
unpriced_event_count: number;
|
|
187
217
|
}
|
|
188
218
|
|
|
189
|
-
interface DayBucketRow {
|
|
190
|
-
date: string;
|
|
191
|
-
total_input_tokens: number;
|
|
192
|
-
total_output_tokens: number;
|
|
193
|
-
total_estimated_cost_usd: number | null;
|
|
194
|
-
event_count: number;
|
|
195
|
-
}
|
|
196
219
|
|
|
197
220
|
interface GroupRow {
|
|
198
221
|
group_key: string;
|
|
222
|
+
group_id: string | null;
|
|
199
223
|
total_input_tokens: number;
|
|
200
224
|
total_output_tokens: number;
|
|
201
225
|
total_cache_creation_tokens: number;
|
|
@@ -238,67 +262,68 @@ export function getUsageTotals(range: UsageTimeRange): UsageTotals {
|
|
|
238
262
|
};
|
|
239
263
|
}
|
|
240
264
|
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
* Each bucket key is a YYYY-MM-DD string derived by dividing the epoch-millis
|
|
245
|
-
* timestamp by 86400000 and formatting as a date.
|
|
246
|
-
*/
|
|
247
|
-
export function getUsageDayBuckets(range: UsageTimeRange): UsageDayBucket[] {
|
|
248
|
-
const rows = rawAll<DayBucketRow>(
|
|
265
|
+
/** Fetch raw events in a time range for in-memory bucketing. */
|
|
266
|
+
function fetchRawBucketRows(range: UsageTimeRange): UsageEventBucketRow[] {
|
|
267
|
+
return rawAll<UsageEventBucketRow>(
|
|
249
268
|
/*sql*/ `
|
|
250
269
|
SELECT
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
270
|
+
created_at,
|
|
271
|
+
input_tokens,
|
|
272
|
+
output_tokens,
|
|
273
|
+
estimated_cost_usd,
|
|
274
|
+
llm_call_count
|
|
256
275
|
FROM llm_usage_events
|
|
257
276
|
WHERE created_at >= ?1 AND created_at <= ?2
|
|
258
|
-
GROUP BY date
|
|
259
|
-
ORDER BY date ASC
|
|
260
277
|
`,
|
|
261
278
|
range.from,
|
|
262
279
|
range.to,
|
|
263
280
|
);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Options for bucket aggregation. */
|
|
284
|
+
export interface UsageBucketOptions {
|
|
285
|
+
/**
|
|
286
|
+
* When true, emit a zero-value bucket for every day (or hour) in the range
|
|
287
|
+
* even if no events fall inside it. Defaults to false so the CLI and other
|
|
288
|
+
* callers only see active periods; the chart route opts in.
|
|
289
|
+
*/
|
|
290
|
+
fillEmpty?: boolean;
|
|
271
291
|
}
|
|
272
292
|
|
|
273
293
|
/**
|
|
274
|
-
* Return per-
|
|
294
|
+
* Return per-day aggregates within the given time range, keyed by local date
|
|
295
|
+
* in the requested timezone (default UTC).
|
|
275
296
|
*
|
|
276
|
-
* Each bucket key is a "YYYY-MM-DD
|
|
297
|
+
* Each bucket key is a "YYYY-MM-DD" string anchored on local midnight in `tz`.
|
|
298
|
+
* When `options.fillEmpty` is true, empty days within the range are filled
|
|
299
|
+
* with zero-value buckets. DST-short and DST-long local days are handled
|
|
300
|
+
* correctly.
|
|
277
301
|
*/
|
|
278
|
-
export function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
export function getUsageDayBuckets(
|
|
303
|
+
range: UsageTimeRange,
|
|
304
|
+
tz: string = "UTC",
|
|
305
|
+
options: UsageBucketOptions = {},
|
|
306
|
+
): UsageDayBucket[] {
|
|
307
|
+
const rows = fetchRawBucketRows(range);
|
|
308
|
+
return bucketEventsByDay(rows, range, tz, options);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Return per-hour aggregates within the given time range, keyed by local hour
|
|
313
|
+
* in the requested timezone (default UTC).
|
|
314
|
+
*
|
|
315
|
+
* Each bucket key is a "YYYY-MM-DD HH:00" string anchored on local hour starts.
|
|
316
|
+
* When `options.fillEmpty` is true, empty hours are filled with zero-value
|
|
317
|
+
* buckets. DST fall-back produces two distinct buckets for the duplicated hour;
|
|
318
|
+
* DST spring-forward produces 23 buckets for the affected day.
|
|
319
|
+
*/
|
|
320
|
+
export function getUsageHourBuckets(
|
|
321
|
+
range: UsageTimeRange,
|
|
322
|
+
tz: string = "UTC",
|
|
323
|
+
options: UsageBucketOptions = {},
|
|
324
|
+
): UsageDayBucket[] {
|
|
325
|
+
const rows = fetchRawBucketRows(range);
|
|
326
|
+
return bucketEventsByHour(rows, range, tz, options);
|
|
302
327
|
}
|
|
303
328
|
|
|
304
329
|
type GroupByDimension = "actor" | "provider" | "model" | "conversation";
|
|
@@ -330,6 +355,7 @@ export function getUsageGroupBreakdown(
|
|
|
330
355
|
CASE WHEN e.conversation_id IS NULL THEN 'Other'
|
|
331
356
|
ELSE COALESCE(c.title, 'Untitled')
|
|
332
357
|
END AS group_key,
|
|
358
|
+
e.conversation_id AS group_id,
|
|
333
359
|
COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
|
|
334
360
|
COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
|
|
335
361
|
COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
@@ -348,6 +374,10 @@ export function getUsageGroupBreakdown(
|
|
|
348
374
|
);
|
|
349
375
|
return rows.map((r) => ({
|
|
350
376
|
group: r.group_key,
|
|
377
|
+
// `GROUP BY e.conversation_id` makes `e.conversation_id` unambiguous
|
|
378
|
+
// inside each group — it is the seeded conversation id for real rows
|
|
379
|
+
// and `null` for the "Other" bucket (events with no conversation).
|
|
380
|
+
groupId: r.group_id,
|
|
351
381
|
totalInputTokens: r.total_input_tokens,
|
|
352
382
|
totalOutputTokens: r.total_output_tokens,
|
|
353
383
|
totalCacheCreationTokens: r.total_cache_creation_tokens,
|
|
@@ -362,6 +392,7 @@ export function getUsageGroupBreakdown(
|
|
|
362
392
|
/*sql*/ `
|
|
363
393
|
SELECT
|
|
364
394
|
${column} AS group_key,
|
|
395
|
+
NULL AS group_id,
|
|
365
396
|
COALESCE(SUM(input_tokens), 0) AS total_input_tokens,
|
|
366
397
|
COALESCE(SUM(output_tokens), 0) AS total_output_tokens,
|
|
367
398
|
COALESCE(SUM(cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
@@ -378,6 +409,11 @@ export function getUsageGroupBreakdown(
|
|
|
378
409
|
);
|
|
379
410
|
return rows.map((r) => ({
|
|
380
411
|
group: r.group_key,
|
|
412
|
+
// Non-conversation group-bys (actor/provider/model) don't have a
|
|
413
|
+
// separate stable id — the grouping column itself is the identifier
|
|
414
|
+
// and is already exposed via `group`. The SELECT projects
|
|
415
|
+
// `NULL AS group_id` so the runtime shape matches `GroupRow`.
|
|
416
|
+
groupId: r.group_id,
|
|
381
417
|
totalInputTokens: r.total_input_tokens,
|
|
382
418
|
totalOutputTokens: r.total_output_tokens,
|
|
383
419
|
totalCacheCreationTokens: r.total_cache_creation_tokens,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersTokenExchangeBodyFormat(
|
|
5
|
+
database: DrizzleDb,
|
|
6
|
+
): void {
|
|
7
|
+
const raw = getSqliteFrom(database);
|
|
8
|
+
try {
|
|
9
|
+
raw.exec(
|
|
10
|
+
/*sql*/ `ALTER TABLE oauth_providers ADD COLUMN token_exchange_body_format TEXT NOT NULL DEFAULT 'form'`,
|
|
11
|
+
);
|
|
12
|
+
} catch {
|
|
13
|
+
// Column already exists — nothing to do.
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { computeUserFileBaseSlug } from "../../contacts/contact-store.js";
|
|
2
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reverse is a no-op. This migration only consolidates `user_file` across
|
|
7
|
+
* contacts sharing the same `principal_id`; the pre-migration split values
|
|
8
|
+
* cannot be reconstructed after normalization, and no schema changes.
|
|
9
|
+
*/
|
|
10
|
+
export function downNormalizeUserFileByPrincipal(_database: DrizzleDb): void {
|
|
11
|
+
/* no-op */
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Heuristic: does `userFile` look like an auto-incremented persona slug?
|
|
16
|
+
*
|
|
17
|
+
* `generateUserFileSlug` appends `-<N>.md` where N is any positive integer
|
|
18
|
+
* (the loop is unbounded, so a very dense principal space could reach 4+
|
|
19
|
+
* digits). Matching is anchored to the end so a slug that happens to contain
|
|
20
|
+
* digits earlier (e.g. `alex-2024.md` for display name "Alex 2024") is not
|
|
21
|
+
* affected.
|
|
22
|
+
*
|
|
23
|
+
* The final-integer suffix also matches year-like or date-like names
|
|
24
|
+
* (`-2025.md`, `-2025-04-13.md` via the trailing `-13.md`). Those must NOT
|
|
25
|
+
* be classified as auto-increments, so we exclude any filename that ends
|
|
26
|
+
* with a date-shaped tail: `-YYYY.md`, `-YYYY-MM.md`, or `-YYYY-MM-DD.md`
|
|
27
|
+
* where YYYY is a 4-digit year starting with 19, 20, or 21. A counter that
|
|
28
|
+
* happens to fall in that range (e.g. `-1999.md`) is indistinguishable from
|
|
29
|
+
* a year by filename alone, so we conservatively treat it as non-auto.
|
|
30
|
+
*
|
|
31
|
+
* Month/day segments must be 2 digits (ISO style) to discriminate them from
|
|
32
|
+
* single-digit collision counters: `generateUserFileSlug` emits `-2.md`,
|
|
33
|
+
* `-3.md`, etc. without leading zeros, so `alex-2025-2.md` is a counter on
|
|
34
|
+
* base `alex-2025.md` — not a date — and must remain classified as auto.
|
|
35
|
+
*
|
|
36
|
+
* Filename-only classification is still ambiguous at the margins: a display
|
|
37
|
+
* name like "Alex 2025 4" legitimately produces `alex-2025-4.md` as a base
|
|
38
|
+
* slug, which looks identical to a year-prefixed counter. When the caller can
|
|
39
|
+
* supply the row's display name, we disambiguate by recomputing the expected
|
|
40
|
+
* base slug: if it matches the filename, the name is a base slug and we
|
|
41
|
+
* classify as non-auto. This closes the only remaining false-positive hole.
|
|
42
|
+
*/
|
|
43
|
+
const DATE_LIKE_SUFFIX = /-(19|20|21)\d{2}((-\d{2}){1,2})?\.md$/;
|
|
44
|
+
const INTEGER_SUFFIX = /-\d+\.md$/;
|
|
45
|
+
|
|
46
|
+
export function isAutoIncrementedUserFile(
|
|
47
|
+
userFile: string,
|
|
48
|
+
displayName?: string,
|
|
49
|
+
): boolean {
|
|
50
|
+
if (DATE_LIKE_SUFFIX.test(userFile)) return false;
|
|
51
|
+
if (!INTEGER_SUFFIX.test(userFile)) return false;
|
|
52
|
+
if (displayName !== undefined) {
|
|
53
|
+
const expectedBase = `${computeUserFileBaseSlug(displayName)}.md`;
|
|
54
|
+
if (expectedBase === userFile) return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Normalize `contacts.user_file` across contact rows that share the same
|
|
61
|
+
* `principal_id`.
|
|
62
|
+
*
|
|
63
|
+
* Multiple contact rows may represent the same principal (one per channel:
|
|
64
|
+
* desktop, phone, Slack, etc.). When a new row was created for a second
|
|
65
|
+
* channel, `generateUserFileSlug(displayName)` auto-incremented to avoid a
|
|
66
|
+
* filename collision (e.g. `sidd.md` → `sidd-2.md`), even though no
|
|
67
|
+
* `sidd-2.md` file ever existed on disk. The persona resolver then silently
|
|
68
|
+
* fell back to `users/default.md` for that channel's messages — and the same
|
|
69
|
+
* slug is used for the journal directory, so the user lost per-principal
|
|
70
|
+
* continuity on every non-primary channel.
|
|
71
|
+
*
|
|
72
|
+
* This migration picks one canonical `user_file` per principal and updates
|
|
73
|
+
* every sibling row to match. Selection heuristic:
|
|
74
|
+
*
|
|
75
|
+
* 1. Prefer values that do NOT look auto-incremented (see
|
|
76
|
+
* `isAutoIncrementedUserFile`).
|
|
77
|
+
* 2. Among those, prefer the oldest contact row (earliest `created_at`).
|
|
78
|
+
* 3. Ties broken by `id` for determinism.
|
|
79
|
+
*
|
|
80
|
+
* Skips principals where only one distinct (non-null) value exists — nothing
|
|
81
|
+
* to normalize. Principals whose contacts all have `user_file = NULL` are
|
|
82
|
+
* left untouched; the code path in `upsertContact` will populate them on the
|
|
83
|
+
* next write.
|
|
84
|
+
*/
|
|
85
|
+
export function migrateNormalizeUserFileByPrincipal(
|
|
86
|
+
database: DrizzleDb,
|
|
87
|
+
): void {
|
|
88
|
+
withCrashRecovery(
|
|
89
|
+
database,
|
|
90
|
+
"migration_normalize_user_file_by_principal_v1",
|
|
91
|
+
() => {
|
|
92
|
+
const raw = getSqliteFrom(database);
|
|
93
|
+
|
|
94
|
+
const tableExists = raw
|
|
95
|
+
.query(
|
|
96
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'contacts'`,
|
|
97
|
+
)
|
|
98
|
+
.get();
|
|
99
|
+
if (!tableExists) return;
|
|
100
|
+
|
|
101
|
+
const userFileColExists = raw
|
|
102
|
+
.query(
|
|
103
|
+
`SELECT 1 FROM pragma_table_info('contacts') WHERE name = 'user_file'`,
|
|
104
|
+
)
|
|
105
|
+
.get();
|
|
106
|
+
const principalColExists = raw
|
|
107
|
+
.query(
|
|
108
|
+
`SELECT 1 FROM pragma_table_info('contacts') WHERE name = 'principal_id'`,
|
|
109
|
+
)
|
|
110
|
+
.get();
|
|
111
|
+
if (!userFileColExists || !principalColExists) return;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
raw.exec("BEGIN");
|
|
115
|
+
|
|
116
|
+
const principals = raw
|
|
117
|
+
.query(
|
|
118
|
+
/*sql*/ `
|
|
119
|
+
SELECT principal_id
|
|
120
|
+
FROM contacts
|
|
121
|
+
WHERE principal_id IS NOT NULL
|
|
122
|
+
GROUP BY principal_id
|
|
123
|
+
HAVING COUNT(DISTINCT COALESCE(user_file, '')) > 1
|
|
124
|
+
`,
|
|
125
|
+
)
|
|
126
|
+
.all() as Array<{ principal_id: string }>;
|
|
127
|
+
|
|
128
|
+
// Fetch all non-null candidates and rank in JS. The auto-increment
|
|
129
|
+
// classification is a regex that SQLite's GLOB can't express cleanly
|
|
130
|
+
// (unbounded digit count, date-pattern exclusion), and keeping the
|
|
131
|
+
// logic in one place avoids SQL/JS drift.
|
|
132
|
+
const selectCandidates = raw.prepare(
|
|
133
|
+
/*sql*/ `
|
|
134
|
+
SELECT user_file, display_name, created_at, id FROM contacts
|
|
135
|
+
WHERE principal_id = ? AND user_file IS NOT NULL
|
|
136
|
+
`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const updateSiblings = raw.prepare(
|
|
140
|
+
/*sql*/ `
|
|
141
|
+
UPDATE contacts
|
|
142
|
+
SET user_file = ?, updated_at = ?
|
|
143
|
+
WHERE principal_id = ?
|
|
144
|
+
AND (user_file IS NULL OR user_file != ?)
|
|
145
|
+
`,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
for (const { principal_id } of principals) {
|
|
149
|
+
const candidates = selectCandidates.all(principal_id) as Array<{
|
|
150
|
+
user_file: string;
|
|
151
|
+
display_name: string;
|
|
152
|
+
created_at: number;
|
|
153
|
+
id: string;
|
|
154
|
+
}>;
|
|
155
|
+
if (candidates.length === 0) continue;
|
|
156
|
+
|
|
157
|
+
candidates.sort((a, b) => {
|
|
158
|
+
const aAuto = isAutoIncrementedUserFile(a.user_file, a.display_name)
|
|
159
|
+
? 1
|
|
160
|
+
: 0;
|
|
161
|
+
const bAuto = isAutoIncrementedUserFile(b.user_file, b.display_name)
|
|
162
|
+
? 1
|
|
163
|
+
: 0;
|
|
164
|
+
if (aAuto !== bAuto) return aAuto - bAuto;
|
|
165
|
+
if (a.created_at !== b.created_at)
|
|
166
|
+
return a.created_at - b.created_at;
|
|
167
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const canonical = candidates[0]!.user_file;
|
|
171
|
+
updateSiblings.run(
|
|
172
|
+
canonical,
|
|
173
|
+
Date.now(),
|
|
174
|
+
principal_id,
|
|
175
|
+
canonical,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
raw.exec("COMMIT");
|
|
180
|
+
} catch (e) {
|
|
181
|
+
try {
|
|
182
|
+
raw.exec("ROLLBACK");
|
|
183
|
+
} catch {
|
|
184
|
+
/* no active transaction */
|
|
185
|
+
}
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateConversationsArchivedAt(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(
|
|
8
|
+
`ALTER TABLE conversations ADD COLUMN archived_at INTEGER DEFAULT NULL`,
|
|
9
|
+
);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already exists — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
raw.exec(
|
|
14
|
+
`CREATE INDEX IF NOT EXISTS idx_conversations_archived_at ON conversations (archived_at)`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -160,6 +160,12 @@ export { migrateOAuthProvidersRevoke } from "./215-oauth-providers-revoke.js";
|
|
|
160
160
|
export { migrateOAuthProvidersTokenAuthMethodDefault } from "./216-oauth-providers-token-auth-method.js";
|
|
161
161
|
export { migrateConversationHostAccess } from "./217-conversation-host-access.js";
|
|
162
162
|
export { migrateOAuthProvidersLogoUrl } from "./218-oauth-providers-logo-url.js";
|
|
163
|
+
export { migrateOAuthProvidersTokenExchangeBodyFormat } from "./219-oauth-providers-token-exchange-body-format.js";
|
|
164
|
+
export {
|
|
165
|
+
downNormalizeUserFileByPrincipal,
|
|
166
|
+
migrateNormalizeUserFileByPrincipal,
|
|
167
|
+
} from "./220-normalize-user-file-by-principal.js";
|
|
168
|
+
export { migrateConversationsArchivedAt } from "./221-conversations-archived-at.js";
|
|
163
169
|
export {
|
|
164
170
|
MIGRATION_REGISTRY,
|
|
165
171
|
type MigrationRegistryEntry,
|
|
@@ -42,6 +42,7 @@ import { migrateStripIntegrationPrefixFromProviderKeysDown } from "./196-strip-i
|
|
|
42
42
|
import { migrateRenameMemoryGraphTypeValuesDown } from "./204-rename-memory-graph-type-values.js";
|
|
43
43
|
import { migrateScrubCorruptedImageAttachmentsDown } from "./206-scrub-corrupted-image-attachments.js";
|
|
44
44
|
import { downConversationHostAccess } from "./217-conversation-host-access.js";
|
|
45
|
+
import { downNormalizeUserFileByPrincipal } from "./220-normalize-user-file-by-principal.js";
|
|
45
46
|
|
|
46
47
|
export interface MigrationRegistryEntry {
|
|
47
48
|
/** The checkpoint key written to memory_checkpoints on completion. */
|
|
@@ -365,6 +366,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
365
366
|
"Add a host_access column to conversations so computer access is persisted per conversation with a safe default of disabled",
|
|
366
367
|
down: downConversationHostAccess,
|
|
367
368
|
},
|
|
369
|
+
{
|
|
370
|
+
key: "migration_normalize_user_file_by_principal_v1",
|
|
371
|
+
version: 42,
|
|
372
|
+
description:
|
|
373
|
+
"Normalize contacts.user_file across rows sharing the same principal_id so every channel for one principal loads the same users/<slug>.md persona and journal directory",
|
|
374
|
+
down: downNormalizeUserFileByPrincipal,
|
|
375
|
+
},
|
|
368
376
|
];
|
|
369
377
|
|
|
370
378
|
export function getMaxMigrationVersion(): number {
|
|
@@ -269,23 +269,50 @@ export class QdrantManager {
|
|
|
269
269
|
|
|
270
270
|
private async waitForReady(): Promise<void> {
|
|
271
271
|
const start = Date.now();
|
|
272
|
+
// Build a single exited-promise once so each race reuses the same handle.
|
|
273
|
+
// Reading `proc.exitCode` synchronously inside the poll loop is unreliable
|
|
274
|
+
// in Bun: while the loop is busy with fetch() + Bun.sleep(), the
|
|
275
|
+
// subprocess-exit event may not be processed on the event loop, so
|
|
276
|
+
// `exitCode` stays null even after the process has died. Racing
|
|
277
|
+
// `proc.exited` directly forces the loop to yield and observe the exit.
|
|
278
|
+
type ExitedOutcome = { type: "exited"; code: number };
|
|
279
|
+
const exitedRace: Promise<ExitedOutcome> =
|
|
280
|
+
this.process != null
|
|
281
|
+
? this.process.exited.then((code) => ({ type: "exited", code }))
|
|
282
|
+
: new Promise<ExitedOutcome>(() => {});
|
|
283
|
+
|
|
284
|
+
const throwOnExit = async (code: number): Promise<never> => {
|
|
285
|
+
await this.stderrDrained;
|
|
286
|
+
const stderr = this.stderrBuffer.trim();
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Qdrant process exited with code ${code} before becoming ready` +
|
|
289
|
+
(stderr ? `\nstderr:\n${stderr}` : ""),
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
272
293
|
while (Date.now() - start < this.readyzTimeoutMs) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
294
|
+
const fetchOutcome = await Promise.race([
|
|
295
|
+
exitedRace,
|
|
296
|
+
fetch(`${this.url}/readyz`).then(
|
|
297
|
+
(r) => ({ type: "fetch" as const, ok: r.ok }),
|
|
298
|
+
() => ({ type: "fetch" as const, ok: false }),
|
|
299
|
+
),
|
|
300
|
+
]);
|
|
301
|
+
if (fetchOutcome.type === "exited") await throwOnExit(fetchOutcome.code);
|
|
302
|
+
if (fetchOutcome.type === "fetch" && fetchOutcome.ok) return;
|
|
303
|
+
|
|
304
|
+
// Race the poll-interval sleep with process exit so we don't waste time
|
|
305
|
+
// sleeping after the subprocess has already died.
|
|
306
|
+
const sleepOutcome = await Promise.race([
|
|
307
|
+
exitedRace,
|
|
308
|
+
new Promise<{ type: "timeout" }>((resolve) =>
|
|
309
|
+
setTimeout(
|
|
310
|
+
() => resolve({ type: "timeout" }),
|
|
311
|
+
this.readyzPollIntervalMs,
|
|
312
|
+
),
|
|
313
|
+
),
|
|
314
|
+
]);
|
|
315
|
+
if (sleepOutcome.type === "exited") await throwOnExit(sleepOutcome.code);
|
|
289
316
|
}
|
|
290
317
|
const stderr = this.stderrBuffer.trim();
|
|
291
318
|
throw new Error(
|
|
@@ -32,11 +32,13 @@ export const conversations = sqliteTable(
|
|
|
32
32
|
isAutoTitle: integer("is_auto_title").notNull().default(1),
|
|
33
33
|
scheduleJobId: text("schedule_job_id"),
|
|
34
34
|
lastMessageAt: integer("last_message_at"),
|
|
35
|
+
archivedAt: integer("archived_at"),
|
|
35
36
|
},
|
|
36
37
|
(table) => [
|
|
37
38
|
index("idx_conversations_updated_at").on(table.updatedAt),
|
|
38
39
|
index("idx_conversations_last_message_at").on(table.lastMessageAt),
|
|
39
40
|
index("idx_conversations_conversation_type").on(table.conversationType),
|
|
41
|
+
index("idx_conversations_archived_at").on(table.archivedAt),
|
|
40
42
|
index("idx_conversations_fork_parent_conversation_id").on(
|
|
41
43
|
table.forkParentConversationId,
|
|
42
44
|
),
|
|
@@ -14,6 +14,9 @@ export const oauthProviders = sqliteTable("oauth_providers", {
|
|
|
14
14
|
tokenEndpointAuthMethod: text("token_endpoint_auth_method")
|
|
15
15
|
.notNull()
|
|
16
16
|
.default("client_secret_post"),
|
|
17
|
+
tokenExchangeBodyFormat: text("token_exchange_body_format")
|
|
18
|
+
.notNull()
|
|
19
|
+
.default("form"),
|
|
17
20
|
userinfoUrl: text("userinfo_url"),
|
|
18
21
|
baseUrl: text("base_url"),
|
|
19
22
|
defaultScopes: text("default_scopes").notNull().default("[]"),
|