@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2
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 +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- package/src/tools/computer-use/definitions.ts +0 -607
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { and, asc, eq, inArray, sql } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type ModeSessionMode,
|
|
5
|
+
modeSessionRowStartsDisplay,
|
|
6
|
+
type ModeSessionStatus,
|
|
7
|
+
type ModeSessionSummary,
|
|
8
|
+
ModeSessionSummarySchema,
|
|
9
|
+
} from "../api/mode-session.js";
|
|
10
|
+
import { type DrizzleDb, getDb } from "./db-connection.js";
|
|
11
|
+
import {
|
|
12
|
+
readMessageSentAt,
|
|
13
|
+
readModeSessionMetadata,
|
|
14
|
+
} from "./message-metadata.js";
|
|
15
|
+
import { conversationModeSessions, messages } from "./schema/index.js";
|
|
16
|
+
|
|
17
|
+
export type ModeSessionWriteFailureReason =
|
|
18
|
+
| "already_exists"
|
|
19
|
+
| "not_found"
|
|
20
|
+
| "stale_revision"
|
|
21
|
+
| "terminal";
|
|
22
|
+
|
|
23
|
+
export type ModeSessionWriteResult =
|
|
24
|
+
| { ok: true; session: ModeSessionSummary }
|
|
25
|
+
| {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: ModeSessionWriteFailureReason;
|
|
28
|
+
session?: ModeSessionSummary;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface ModeSessionStoreOptions {
|
|
32
|
+
db?: DrizzleDb;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ModeSessionReader = Pick<DrizzleDb, "select">;
|
|
36
|
+
|
|
37
|
+
interface ModeSessionMutationOptions extends ModeSessionStoreOptions {
|
|
38
|
+
acceptTerminal?: (
|
|
39
|
+
current: typeof conversationModeSessions.$inferSelect,
|
|
40
|
+
) => boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BeginModeSessionInput {
|
|
44
|
+
id: string;
|
|
45
|
+
conversationId: string;
|
|
46
|
+
mode: ModeSessionMode;
|
|
47
|
+
sourceStartedAt: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RevisionedModeSessionInput {
|
|
51
|
+
id: string;
|
|
52
|
+
conversationId: string;
|
|
53
|
+
expectedRevision: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UpdateModeSessionActivityInput extends RevisionedModeSessionInput {
|
|
57
|
+
lastActivityAt: number;
|
|
58
|
+
lastOwnedMessageId?: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UpdateModeSessionBoundariesInput extends RevisionedModeSessionInput {
|
|
62
|
+
firstIncluded: { at: number; messageId: string } | null;
|
|
63
|
+
lastActivityAt: number;
|
|
64
|
+
lastOwnedMessageId: string | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface FinalizeModeSessionInputBase extends RevisionedModeSessionInput {
|
|
68
|
+
endReason: string;
|
|
69
|
+
lastActivityAt?: number;
|
|
70
|
+
lastOwnedMessageId?: string | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type FinalizeModeSessionInput = FinalizeModeSessionInputBase &
|
|
74
|
+
(
|
|
75
|
+
| { status: Extract<ModeSessionStatus, "completed">; endedAt: number }
|
|
76
|
+
| {
|
|
77
|
+
status: Extract<ModeSessionStatus, "interrupted">;
|
|
78
|
+
endedAt: number | null;
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
function toSummary(
|
|
83
|
+
row: typeof conversationModeSessions.$inferSelect,
|
|
84
|
+
): ModeSessionSummary {
|
|
85
|
+
return ModeSessionSummarySchema.parse(row);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSession(
|
|
89
|
+
database: ModeSessionReader,
|
|
90
|
+
conversationId: string,
|
|
91
|
+
id: string,
|
|
92
|
+
): typeof conversationModeSessions.$inferSelect | undefined {
|
|
93
|
+
return database
|
|
94
|
+
.select()
|
|
95
|
+
.from(conversationModeSessions)
|
|
96
|
+
.where(
|
|
97
|
+
and(
|
|
98
|
+
eq(conversationModeSessions.conversationId, conversationId),
|
|
99
|
+
eq(conversationModeSessions.id, id),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
.get();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function classifyRejectedWrite(
|
|
106
|
+
current: typeof conversationModeSessions.$inferSelect | undefined,
|
|
107
|
+
expectedRevision: number,
|
|
108
|
+
): ModeSessionWriteResult {
|
|
109
|
+
if (!current) {
|
|
110
|
+
return { ok: false, reason: "not_found" };
|
|
111
|
+
}
|
|
112
|
+
const session = toSummary(current);
|
|
113
|
+
if (current.status !== "active") {
|
|
114
|
+
return { ok: false, reason: "terminal", session };
|
|
115
|
+
}
|
|
116
|
+
if (current.revision !== expectedRevision) {
|
|
117
|
+
return { ok: false, reason: "stale_revision", session };
|
|
118
|
+
}
|
|
119
|
+
throw new Error("Mode session write was rejected without a state conflict");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mutateActiveSession(
|
|
123
|
+
input: RevisionedModeSessionInput,
|
|
124
|
+
buildValues: (
|
|
125
|
+
current: typeof conversationModeSessions.$inferSelect,
|
|
126
|
+
) => Partial<typeof conversationModeSessions.$inferInsert>,
|
|
127
|
+
options?: ModeSessionMutationOptions,
|
|
128
|
+
): ModeSessionWriteResult {
|
|
129
|
+
const database = options?.db ?? getDb();
|
|
130
|
+
return database.transaction(
|
|
131
|
+
(tx) => {
|
|
132
|
+
const current = getSession(tx, input.conversationId, input.id);
|
|
133
|
+
if (
|
|
134
|
+
!current ||
|
|
135
|
+
current.status !== "active" ||
|
|
136
|
+
current.revision !== input.expectedRevision
|
|
137
|
+
) {
|
|
138
|
+
if (
|
|
139
|
+
current &&
|
|
140
|
+
current.status !== "active" &&
|
|
141
|
+
options?.acceptTerminal?.(current)
|
|
142
|
+
) {
|
|
143
|
+
return { ok: true, session: toSummary(current) };
|
|
144
|
+
}
|
|
145
|
+
return classifyRejectedWrite(current, input.expectedRevision);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
tx.update(conversationModeSessions)
|
|
149
|
+
.set({
|
|
150
|
+
...buildValues(current),
|
|
151
|
+
revision: sql`${conversationModeSessions.revision} + 1`,
|
|
152
|
+
})
|
|
153
|
+
.where(
|
|
154
|
+
and(
|
|
155
|
+
eq(conversationModeSessions.conversationId, input.conversationId),
|
|
156
|
+
eq(conversationModeSessions.id, input.id),
|
|
157
|
+
eq(conversationModeSessions.status, "active"),
|
|
158
|
+
eq(conversationModeSessions.revision, input.expectedRevision),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
.run();
|
|
162
|
+
|
|
163
|
+
const updated = getSession(tx, input.conversationId, input.id);
|
|
164
|
+
if (!updated || updated.revision !== input.expectedRevision + 1) {
|
|
165
|
+
return classifyRejectedWrite(updated, input.expectedRevision);
|
|
166
|
+
}
|
|
167
|
+
return { ok: true, session: toSummary(updated) };
|
|
168
|
+
},
|
|
169
|
+
{ behavior: "immediate" },
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function beginConversationModeSession(
|
|
174
|
+
input: BeginModeSessionInput,
|
|
175
|
+
options?: ModeSessionStoreOptions,
|
|
176
|
+
): ModeSessionWriteResult {
|
|
177
|
+
const database = options?.db ?? getDb();
|
|
178
|
+
return database.transaction(
|
|
179
|
+
(tx) => {
|
|
180
|
+
const existing = tx
|
|
181
|
+
.select()
|
|
182
|
+
.from(conversationModeSessions)
|
|
183
|
+
.where(eq(conversationModeSessions.id, input.id))
|
|
184
|
+
.get();
|
|
185
|
+
if (existing) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
reason: "already_exists",
|
|
189
|
+
...(existing.conversationId === input.conversationId
|
|
190
|
+
? { session: toSummary(existing) }
|
|
191
|
+
: {}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
tx.insert(conversationModeSessions)
|
|
196
|
+
.values({
|
|
197
|
+
...input,
|
|
198
|
+
status: "active",
|
|
199
|
+
firstIncludedAt: null,
|
|
200
|
+
firstIncludedMessageId: null,
|
|
201
|
+
lastActivityAt: input.sourceStartedAt,
|
|
202
|
+
lastOwnedMessageId: null,
|
|
203
|
+
endedAt: null,
|
|
204
|
+
endReason: null,
|
|
205
|
+
revision: 1,
|
|
206
|
+
})
|
|
207
|
+
.run();
|
|
208
|
+
const created = getSession(tx, input.conversationId, input.id);
|
|
209
|
+
if (!created) {
|
|
210
|
+
throw new Error("Mode session insert did not create a readable row");
|
|
211
|
+
}
|
|
212
|
+
return { ok: true, session: toSummary(created) };
|
|
213
|
+
},
|
|
214
|
+
{ behavior: "immediate" },
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function updateConversationModeSessionActivity(
|
|
219
|
+
input: UpdateModeSessionActivityInput,
|
|
220
|
+
options?: ModeSessionStoreOptions,
|
|
221
|
+
): ModeSessionWriteResult {
|
|
222
|
+
return mutateActiveSession(
|
|
223
|
+
input,
|
|
224
|
+
(current) => ({
|
|
225
|
+
lastActivityAt: Math.max(current.lastActivityAt, input.lastActivityAt),
|
|
226
|
+
...(input.lastOwnedMessageId !== undefined
|
|
227
|
+
? { lastOwnedMessageId: input.lastOwnedMessageId }
|
|
228
|
+
: {}),
|
|
229
|
+
}),
|
|
230
|
+
options,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function updateConversationModeSessionBoundaries(
|
|
235
|
+
input: UpdateModeSessionBoundariesInput,
|
|
236
|
+
options?: ModeSessionStoreOptions,
|
|
237
|
+
): ModeSessionWriteResult {
|
|
238
|
+
return mutateActiveSession(
|
|
239
|
+
input,
|
|
240
|
+
(current) => ({
|
|
241
|
+
firstIncludedAt: input.firstIncluded?.at ?? null,
|
|
242
|
+
firstIncludedMessageId: input.firstIncluded?.messageId ?? null,
|
|
243
|
+
lastActivityAt: Math.max(current.lastActivityAt, input.lastActivityAt),
|
|
244
|
+
lastOwnedMessageId: input.lastOwnedMessageId,
|
|
245
|
+
}),
|
|
246
|
+
options,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function advanceConversationModeSessionRevision(
|
|
251
|
+
input: RevisionedModeSessionInput,
|
|
252
|
+
options?: ModeSessionStoreOptions,
|
|
253
|
+
): ModeSessionWriteResult {
|
|
254
|
+
return mutateActiveSession(input, () => ({}), options);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function finalizeConversationModeSession(
|
|
258
|
+
input: FinalizeModeSessionInput,
|
|
259
|
+
options?: ModeSessionStoreOptions,
|
|
260
|
+
): ModeSessionWriteResult {
|
|
261
|
+
return mutateActiveSession(
|
|
262
|
+
input,
|
|
263
|
+
(current) => {
|
|
264
|
+
const lastActivityAt = Math.max(
|
|
265
|
+
current.lastActivityAt,
|
|
266
|
+
input.lastActivityAt ?? current.lastActivityAt,
|
|
267
|
+
);
|
|
268
|
+
if (input.endedAt !== null && input.endedAt < lastActivityAt) {
|
|
269
|
+
throw new Error("Mode session end cannot precede its last activity");
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
status: input.status,
|
|
273
|
+
endedAt: input.endedAt,
|
|
274
|
+
endReason: input.endReason,
|
|
275
|
+
lastActivityAt,
|
|
276
|
+
...(input.lastOwnedMessageId !== undefined
|
|
277
|
+
? { lastOwnedMessageId: input.lastOwnedMessageId }
|
|
278
|
+
: {}),
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
...options,
|
|
283
|
+
acceptTerminal: (current) =>
|
|
284
|
+
current.status === input.status &&
|
|
285
|
+
current.endedAt === input.endedAt &&
|
|
286
|
+
current.endReason === input.endReason &&
|
|
287
|
+
(input.lastActivityAt === undefined ||
|
|
288
|
+
input.lastActivityAt <= current.lastActivityAt) &&
|
|
289
|
+
(input.lastOwnedMessageId === undefined ||
|
|
290
|
+
input.lastOwnedMessageId === current.lastOwnedMessageId),
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function getConversationModeSession(
|
|
296
|
+
conversationId: string,
|
|
297
|
+
id: string,
|
|
298
|
+
options?: ModeSessionStoreOptions,
|
|
299
|
+
): ModeSessionSummary | null {
|
|
300
|
+
const row = getSession(options?.db ?? getDb(), conversationId, id);
|
|
301
|
+
return row ? toSummary(row) : null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function listConversationModeSessionsByIds(
|
|
305
|
+
conversationId: string,
|
|
306
|
+
ids: readonly string[],
|
|
307
|
+
options?: ModeSessionStoreOptions,
|
|
308
|
+
): ModeSessionSummary[] {
|
|
309
|
+
if (ids.length === 0) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
const rows = (options?.db ?? getDb())
|
|
313
|
+
.select()
|
|
314
|
+
.from(conversationModeSessions)
|
|
315
|
+
.where(
|
|
316
|
+
and(
|
|
317
|
+
eq(conversationModeSessions.conversationId, conversationId),
|
|
318
|
+
inArray(conversationModeSessions.id, [...new Set(ids)]),
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
.all();
|
|
322
|
+
return rows.map(toSummary);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function recoverActiveConversationModeSessions(
|
|
326
|
+
options?: ModeSessionStoreOptions,
|
|
327
|
+
): number {
|
|
328
|
+
const result = (options?.db ?? getDb())
|
|
329
|
+
.update(conversationModeSessions)
|
|
330
|
+
.set({
|
|
331
|
+
status: "interrupted",
|
|
332
|
+
endedAt: null,
|
|
333
|
+
endReason: "assistant_restarted",
|
|
334
|
+
revision: sql`${conversationModeSessions.revision} + 1`,
|
|
335
|
+
})
|
|
336
|
+
.where(eq(conversationModeSessions.status, "active"))
|
|
337
|
+
.run() as unknown as { changes: number };
|
|
338
|
+
return result.changes;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Recompute replaceable row boundaries after destructive transcript edits. */
|
|
342
|
+
export function repairConversationModeSessionBoundaries(
|
|
343
|
+
conversationId: string,
|
|
344
|
+
options?: ModeSessionStoreOptions,
|
|
345
|
+
): number {
|
|
346
|
+
const database = options?.db ?? getDb();
|
|
347
|
+
return database.transaction(
|
|
348
|
+
(tx) => {
|
|
349
|
+
const sessions = tx
|
|
350
|
+
.select()
|
|
351
|
+
.from(conversationModeSessions)
|
|
352
|
+
.where(eq(conversationModeSessions.conversationId, conversationId))
|
|
353
|
+
.all();
|
|
354
|
+
if (sessions.length === 0) {
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
const sessionsById = new Map(
|
|
358
|
+
sessions.map((session) => [session.id, session]),
|
|
359
|
+
);
|
|
360
|
+
const boundaries = new Map<
|
|
361
|
+
string,
|
|
362
|
+
{
|
|
363
|
+
firstAt: number | null;
|
|
364
|
+
firstMessageId: string | null;
|
|
365
|
+
lastAt: number;
|
|
366
|
+
lastMessageId: string;
|
|
367
|
+
}
|
|
368
|
+
>();
|
|
369
|
+
for (const message of tx
|
|
370
|
+
.select({
|
|
371
|
+
id: messages.id,
|
|
372
|
+
role: messages.role,
|
|
373
|
+
createdAt: messages.createdAt,
|
|
374
|
+
metadata: messages.metadata,
|
|
375
|
+
})
|
|
376
|
+
.from(messages)
|
|
377
|
+
.where(eq(messages.conversationId, conversationId))
|
|
378
|
+
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
379
|
+
.all()) {
|
|
380
|
+
const owner = readModeSessionMetadata(message.metadata);
|
|
381
|
+
if (!owner) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const session = sessionsById.get(owner.id);
|
|
385
|
+
if (!session || session.mode !== owner.mode) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const at = readMessageSentAt(message.metadata) ?? message.createdAt;
|
|
389
|
+
const startsDisplayBoundary = modeSessionRowStartsDisplay(
|
|
390
|
+
session.mode,
|
|
391
|
+
message.role === "assistant",
|
|
392
|
+
);
|
|
393
|
+
const current = boundaries.get(owner.id);
|
|
394
|
+
if (!current) {
|
|
395
|
+
boundaries.set(owner.id, {
|
|
396
|
+
firstAt: startsDisplayBoundary ? at : null,
|
|
397
|
+
firstMessageId: startsDisplayBoundary ? message.id : null,
|
|
398
|
+
lastAt: at,
|
|
399
|
+
lastMessageId: message.id,
|
|
400
|
+
});
|
|
401
|
+
} else {
|
|
402
|
+
if (
|
|
403
|
+
startsDisplayBoundary &&
|
|
404
|
+
(current.firstAt === null || at < current.firstAt)
|
|
405
|
+
) {
|
|
406
|
+
current.firstAt = at;
|
|
407
|
+
current.firstMessageId = message.id;
|
|
408
|
+
}
|
|
409
|
+
current.lastAt = Math.max(current.lastAt, at);
|
|
410
|
+
current.lastMessageId = message.id;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let repaired = 0;
|
|
415
|
+
for (const session of sessions) {
|
|
416
|
+
const boundary = boundaries.get(session.id);
|
|
417
|
+
const firstIncludedAt = boundary?.firstAt ?? null;
|
|
418
|
+
const firstIncludedMessageId = boundary?.firstMessageId ?? null;
|
|
419
|
+
const lastOwnedMessageId = boundary?.lastMessageId ?? null;
|
|
420
|
+
const observedLastActivityAt =
|
|
421
|
+
boundary?.lastAt ?? session.lastActivityAt;
|
|
422
|
+
const lastActivityAt =
|
|
423
|
+
session.endedAt === null
|
|
424
|
+
? Math.max(session.lastActivityAt, observedLastActivityAt)
|
|
425
|
+
: Math.min(
|
|
426
|
+
session.endedAt,
|
|
427
|
+
Math.max(session.lastActivityAt, observedLastActivityAt),
|
|
428
|
+
);
|
|
429
|
+
if (
|
|
430
|
+
session.firstIncludedAt === firstIncludedAt &&
|
|
431
|
+
session.firstIncludedMessageId === firstIncludedMessageId &&
|
|
432
|
+
session.lastOwnedMessageId === lastOwnedMessageId &&
|
|
433
|
+
session.lastActivityAt === lastActivityAt
|
|
434
|
+
) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const result = tx
|
|
438
|
+
.update(conversationModeSessions)
|
|
439
|
+
.set({
|
|
440
|
+
firstIncludedAt,
|
|
441
|
+
firstIncludedMessageId,
|
|
442
|
+
lastOwnedMessageId,
|
|
443
|
+
lastActivityAt,
|
|
444
|
+
revision: sql`${conversationModeSessions.revision} + 1`,
|
|
445
|
+
})
|
|
446
|
+
.where(
|
|
447
|
+
and(
|
|
448
|
+
eq(conversationModeSessions.conversationId, conversationId),
|
|
449
|
+
eq(conversationModeSessions.id, session.id),
|
|
450
|
+
eq(conversationModeSessions.revision, session.revision),
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
.run() as unknown as { changes: number };
|
|
454
|
+
repaired += result.changes;
|
|
455
|
+
}
|
|
456
|
+
return repaired;
|
|
457
|
+
},
|
|
458
|
+
{ behavior: "immediate" },
|
|
459
|
+
);
|
|
460
|
+
}
|
|
@@ -955,19 +955,6 @@ function likeContainsPattern(query: string): string {
|
|
|
955
955
|
.replace(/_/g, "\\_")}%`;
|
|
956
956
|
}
|
|
957
957
|
|
|
958
|
-
/**
|
|
959
|
-
* Whether the sparse Qdrant `messages_lexical` index — the only source of
|
|
960
|
-
* message-content matches — is a safe read source. Content matching is
|
|
961
|
-
* unavailable (title matches only) until the one-time upgrade backfill has
|
|
962
|
-
* fully drained: a partially populated collection would silently miss older
|
|
963
|
-
* content (an empty result — not a throw). Indexing itself is unconditional
|
|
964
|
-
* host infrastructure, so completion is the only gate; the recall read site
|
|
965
|
-
* applies the same one via the shared {@link isLexicalBackfillComplete}.
|
|
966
|
-
*/
|
|
967
|
-
function isMessageContentSearchAvailable(): boolean {
|
|
968
|
-
return isLexicalBackfillComplete();
|
|
969
|
-
}
|
|
970
|
-
|
|
971
958
|
/**
|
|
972
959
|
* Full-text search across message content.
|
|
973
960
|
*
|
|
@@ -976,9 +963,9 @@ function isMessageContentSearchAvailable(): boolean {
|
|
|
976
963
|
* merged with a `LIKE` match on conversation titles; matching conversations
|
|
977
964
|
* return with their relevant messages, ordered by most recently updated.
|
|
978
965
|
*
|
|
979
|
-
* Content matching is index-only
|
|
966
|
+
* Content matching is index-only: there is no `messages.content` scan
|
|
980
967
|
* fallback and no other content source. Only the title arm can match while
|
|
981
|
-
* the index is not a safe read source ({@link
|
|
968
|
+
* the index is not a safe read source ({@link isLexicalBackfillComplete}),
|
|
982
969
|
* for a query that tokenizes to nothing under the shared tokenizer (non-ASCII
|
|
983
970
|
* or single-char input like "你", "é", "C++"), or when the Qdrant lexical
|
|
984
971
|
* lookup fails (logged). An unindexed or unreachable index yields fewer
|
|
@@ -1017,7 +1004,7 @@ export async function searchConversations(
|
|
|
1017
1004
|
const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
|
|
1018
1005
|
|
|
1019
1006
|
const hasTokens = hasLexicalTokens(trimmed);
|
|
1020
|
-
const contentSearchAvailable =
|
|
1007
|
+
const contentSearchAvailable = isLexicalBackfillComplete();
|
|
1021
1008
|
|
|
1022
1009
|
// LIKE pattern for title matching (message-content indexes don't cover titles).
|
|
1023
1010
|
const titlePattern = likeContainsPattern(query);
|
|
@@ -1453,9 +1440,9 @@ function buildExcerptFromText(
|
|
|
1453
1440
|
text.length,
|
|
1454
1441
|
match.index + match.length + EXCERPT_WINDOW,
|
|
1455
1442
|
);
|
|
1456
|
-
|
|
1443
|
+
return (
|
|
1457
1444
|
(start > 0 ? "\u2026" : "") +
|
|
1458
1445
|
text.slice(start, end).replace(/\s+/g, " ").trim() +
|
|
1459
|
-
(end < text.length ? "\u2026" : "")
|
|
1460
|
-
|
|
1446
|
+
(end < text.length ? "\u2026" : "")
|
|
1447
|
+
);
|
|
1461
1448
|
}
|
|
@@ -515,19 +515,13 @@ interface GroupRow {
|
|
|
515
515
|
|
|
516
516
|
type UsageQueryParam = ScheduleAttributionSqlParam;
|
|
517
517
|
|
|
518
|
-
function normalizeUsageAggregationFilter(
|
|
519
|
-
filter?: UsageAggregationFilter,
|
|
520
|
-
): UsageAggregationFilter {
|
|
521
|
-
return normalizeScheduleAttributionFilter(filter);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
518
|
function buildUsageAggregationWhere(
|
|
525
519
|
range: UsageTimeRange,
|
|
526
520
|
filter?: UsageAggregationFilter,
|
|
527
521
|
eventAlias?: string,
|
|
528
522
|
now: number = Date.now(),
|
|
529
523
|
): { sql: string; params: UsageQueryParam[] } {
|
|
530
|
-
const normalized =
|
|
524
|
+
const normalized = normalizeScheduleAttributionFilter(filter);
|
|
531
525
|
const eventTable = eventAlias ?? "llm_usage_events";
|
|
532
526
|
const createdAt = `${eventTable}.created_at`;
|
|
533
527
|
const clauses = [`${createdAt} >= ? AND ${createdAt} <= ?`];
|
|
@@ -852,7 +846,7 @@ export function getUsageGroupBreakdown(
|
|
|
852
846
|
// Runtime allowlist — defense-in-depth against SQL injection via type assertions.
|
|
853
847
|
assertGroupByDimension(groupBy);
|
|
854
848
|
|
|
855
|
-
const normalizedFilter =
|
|
849
|
+
const normalizedFilter = normalizeScheduleAttributionFilter(filter);
|
|
856
850
|
|
|
857
851
|
// Conversation grouping requires a JOIN with conversations to resolve titles.
|
|
858
852
|
if (groupBy === "conversation") {
|
|
@@ -984,7 +978,7 @@ export function getUsageGroupedSeries(
|
|
|
984
978
|
throw new Error("Grouped usage series does not support conversation");
|
|
985
979
|
}
|
|
986
980
|
|
|
987
|
-
const normalizedFilter =
|
|
981
|
+
const normalizedFilter = normalizeScheduleAttributionFilter(filter);
|
|
988
982
|
let rows: UsageGroupedBucketRow[];
|
|
989
983
|
|
|
990
984
|
if (groupBy === "schedule") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ModeSession, parseModeSession } from "../api/mode-session.js";
|
|
1
2
|
import { safeParseRecord } from "../util/json.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,3 +19,27 @@ export function mergeMessageMetadata(
|
|
|
18
19
|
...updates,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
export function readModeSessionMetadata(
|
|
24
|
+
metadata: string | null | undefined,
|
|
25
|
+
): ModeSession | undefined {
|
|
26
|
+
if (!metadata) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return parseModeSession(safeParseRecord(metadata).modeSession);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function readMessageSentAt(
|
|
33
|
+
metadata: string | null | undefined,
|
|
34
|
+
): number | undefined {
|
|
35
|
+
if (!metadata) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const sentAt = safeParseRecord(metadata).sentAt;
|
|
39
|
+
return typeof sentAt === "number" &&
|
|
40
|
+
Number.isInteger(sentAt) &&
|
|
41
|
+
sentAt >= 0 &&
|
|
42
|
+
Number.isFinite(new Date(sentAt).getTime())
|
|
43
|
+
? sentAt
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const TABLE = "conversation_mode_sessions";
|
|
5
|
+
|
|
6
|
+
export function migrateCreateConversationModeSessions(
|
|
7
|
+
database: DrizzleDb,
|
|
8
|
+
): void {
|
|
9
|
+
const sqlite = getSqliteFrom(database);
|
|
10
|
+
sqlite.exec(/* sql */ `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
14
|
+
mode TEXT NOT NULL CHECK (mode IN ('computer_use', 'browser', 'live_vision', 'ambient')),
|
|
15
|
+
status TEXT NOT NULL CHECK (status IN ('active', 'completed', 'interrupted')),
|
|
16
|
+
source_started_at INTEGER NOT NULL,
|
|
17
|
+
first_included_at INTEGER,
|
|
18
|
+
first_included_message_id TEXT,
|
|
19
|
+
last_activity_at INTEGER NOT NULL,
|
|
20
|
+
last_owned_message_id TEXT,
|
|
21
|
+
ended_at INTEGER,
|
|
22
|
+
end_reason TEXT,
|
|
23
|
+
revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0),
|
|
24
|
+
CHECK ((first_included_at IS NULL) = (first_included_message_id IS NULL)),
|
|
25
|
+
CHECK (
|
|
26
|
+
(status = 'active' AND ended_at IS NULL AND end_reason IS NULL)
|
|
27
|
+
OR (status = 'completed' AND ended_at IS NOT NULL AND end_reason IS NOT NULL AND length(end_reason) > 0)
|
|
28
|
+
OR (status = 'interrupted' AND end_reason IS NOT NULL AND length(end_reason) > 0)
|
|
29
|
+
),
|
|
30
|
+
CHECK (ended_at IS NULL OR ended_at >= last_activity_at),
|
|
31
|
+
CHECK (last_activity_at >= source_started_at),
|
|
32
|
+
CHECK (first_included_at IS NULL OR first_included_at <= last_activity_at)
|
|
33
|
+
);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_conversation_mode_sessions_conversation_status
|
|
35
|
+
ON ${TABLE} (conversation_id, status);
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function downCreateConversationModeSessions(database: DrizzleDb): void {
|
|
40
|
+
getSqliteFrom(database).exec(`DROP TABLE IF EXISTS ${TABLE}`);
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
const TABLE = "client_connection_events";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create `client_connection_events`: the durable hub subscribe/dispose
|
|
8
|
+
* ledger used by `assistant clients history`.
|
|
9
|
+
*
|
|
10
|
+
* One row per client-subscriber lifecycle event. Reconnects shorter than
|
|
11
|
+
* the flap window are coalesced at read time, not at write time, so the
|
|
12
|
+
* raw event stream stays a complete audit.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent via IF NOT EXISTS.
|
|
15
|
+
*/
|
|
16
|
+
export function migrateCreateClientConnectionEvents(database: DrizzleDb): void {
|
|
17
|
+
const sqlite = getSqliteFrom(database);
|
|
18
|
+
sqlite.exec(
|
|
19
|
+
`CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
client_id TEXT NOT NULL,
|
|
22
|
+
interface_id TEXT NOT NULL,
|
|
23
|
+
connection_id TEXT NOT NULL,
|
|
24
|
+
reason TEXT NOT NULL,
|
|
25
|
+
occurred_at INTEGER NOT NULL,
|
|
26
|
+
actor_principal_id TEXT,
|
|
27
|
+
client_version TEXT,
|
|
28
|
+
sse_watchdog INTEGER,
|
|
29
|
+
machine_name TEXT
|
|
30
|
+
)`,
|
|
31
|
+
);
|
|
32
|
+
sqlite.exec(
|
|
33
|
+
`CREATE INDEX IF NOT EXISTS idx_client_connection_events_client_occurred
|
|
34
|
+
ON ${TABLE} (client_id, occurred_at)`,
|
|
35
|
+
);
|
|
36
|
+
sqlite.exec(
|
|
37
|
+
`CREATE INDEX IF NOT EXISTS idx_client_connection_events_occurred
|
|
38
|
+
ON ${TABLE} (occurred_at)`,
|
|
39
|
+
);
|
|
40
|
+
}
|