@vellumai/assistant 0.8.1 → 0.8.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 +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readSlackMetadata,
|
|
3
|
+
type SlackMessageMetadata,
|
|
4
|
+
} from "../../messaging/providers/slack/message-metadata.js";
|
|
5
|
+
import {
|
|
6
|
+
parseExternalContentEnvelope,
|
|
7
|
+
unwrapExternalContentForDisplay,
|
|
8
|
+
} from "../../security/untrusted-content.js";
|
|
9
|
+
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
10
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
11
|
+
|
|
12
|
+
const CHECKPOINT_KEY = "migration_normalize_slack_external_content_v1";
|
|
13
|
+
const BATCH_SIZE = 100;
|
|
14
|
+
|
|
15
|
+
interface CandidateMessageRow {
|
|
16
|
+
rowid: number;
|
|
17
|
+
id: string;
|
|
18
|
+
role: string;
|
|
19
|
+
content: string;
|
|
20
|
+
metadata: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface NormalizedMessageRow {
|
|
24
|
+
content: string;
|
|
25
|
+
metadata: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function migrateNormalizeSlackExternalContent(
|
|
29
|
+
database: DrizzleDb,
|
|
30
|
+
): void {
|
|
31
|
+
withCrashRecovery(database, CHECKPOINT_KEY, () => {
|
|
32
|
+
const raw = getSqliteFrom(database);
|
|
33
|
+
|
|
34
|
+
const tableExists = raw
|
|
35
|
+
.query(
|
|
36
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'messages'`,
|
|
37
|
+
)
|
|
38
|
+
.get();
|
|
39
|
+
if (!tableExists) return;
|
|
40
|
+
|
|
41
|
+
let lastRowid = 0;
|
|
42
|
+
|
|
43
|
+
for (;;) {
|
|
44
|
+
const rows = raw
|
|
45
|
+
.query(
|
|
46
|
+
/*sql*/ `
|
|
47
|
+
SELECT rowid, id, role, content, metadata
|
|
48
|
+
FROM messages
|
|
49
|
+
WHERE rowid > ?
|
|
50
|
+
AND metadata LIKE '%"slackMeta"%'
|
|
51
|
+
AND (
|
|
52
|
+
content LIKE '%<external_content%'
|
|
53
|
+
OR metadata NOT LIKE '%"provenanceTrustClass"%'
|
|
54
|
+
)
|
|
55
|
+
ORDER BY rowid
|
|
56
|
+
LIMIT ?
|
|
57
|
+
`,
|
|
58
|
+
)
|
|
59
|
+
.all(lastRowid, BATCH_SIZE) as CandidateMessageRow[];
|
|
60
|
+
|
|
61
|
+
if (rows.length === 0) break;
|
|
62
|
+
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
lastRowid = row.rowid;
|
|
65
|
+
const normalized = normalizeSlackMessageRow(row);
|
|
66
|
+
if (!normalized) continue;
|
|
67
|
+
|
|
68
|
+
raw
|
|
69
|
+
.query(`UPDATE messages SET content = ?, metadata = ? WHERE id = ?`)
|
|
70
|
+
.run(normalized.content, normalized.metadata, row.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function downNormalizeSlackExternalContent(_database: DrizzleDb): void {
|
|
77
|
+
// Irreversible by design: this migration discards redundant persisted
|
|
78
|
+
// wrappers and leaves runtime assembly responsible for model boundaries.
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeSlackMessageRow(
|
|
82
|
+
row: CandidateMessageRow,
|
|
83
|
+
): NormalizedMessageRow | null {
|
|
84
|
+
const parsed = parseSlackMetadataEnvelope(row.metadata);
|
|
85
|
+
if (!parsed) return null;
|
|
86
|
+
|
|
87
|
+
const normalizedContent = normalizeMessageContent(row.content);
|
|
88
|
+
if (normalizedContent !== null) {
|
|
89
|
+
const { metadata } = parsed;
|
|
90
|
+
if (
|
|
91
|
+
!Object.prototype.hasOwnProperty.call(metadata, "provenanceTrustClass")
|
|
92
|
+
) {
|
|
93
|
+
metadata.provenanceTrustClass = "unknown";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: normalizedContent,
|
|
98
|
+
metadata: JSON.stringify(metadata),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isLegacyGuardianBackfillRow(row, parsed)) {
|
|
103
|
+
const { metadata } = parsed;
|
|
104
|
+
metadata.provenanceTrustClass = "guardian";
|
|
105
|
+
if (
|
|
106
|
+
!Object.prototype.hasOwnProperty.call(metadata, "provenanceSourceChannel")
|
|
107
|
+
) {
|
|
108
|
+
metadata.provenanceSourceChannel = "slack";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
content: row.content,
|
|
113
|
+
metadata: JSON.stringify(metadata),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseSlackMetadataEnvelope(rawMetadata: string): {
|
|
121
|
+
metadata: Record<string, unknown>;
|
|
122
|
+
slackMeta: SlackMessageMetadata;
|
|
123
|
+
} | null {
|
|
124
|
+
let parsed: unknown;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(rawMetadata);
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const metadata = parsed as Record<string, unknown>;
|
|
136
|
+
if (typeof metadata.slackMeta !== "string") return null;
|
|
137
|
+
const slackMeta = readSlackMetadata(metadata.slackMeta);
|
|
138
|
+
if (!slackMeta) return null;
|
|
139
|
+
return { metadata, slackMeta };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeMessageContent(content: string): string | null {
|
|
143
|
+
const wholeEnvelope = parseExternalContentEnvelope(content);
|
|
144
|
+
if (wholeEnvelope) {
|
|
145
|
+
return wholeEnvelope.content;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let parsed: unknown;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(content);
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!Array.isArray(parsed)) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let changed = false;
|
|
160
|
+
const normalizedBlocks = parsed.map((block) => {
|
|
161
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) {
|
|
162
|
+
return block;
|
|
163
|
+
}
|
|
164
|
+
const record = block as Record<string, unknown>;
|
|
165
|
+
if (record.type !== "text" || typeof record.text !== "string") {
|
|
166
|
+
return block;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const unwrapped = unwrapExternalContentForDisplay(record.text);
|
|
170
|
+
if (unwrapped === record.text) {
|
|
171
|
+
return block;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
changed = true;
|
|
175
|
+
return { ...record, text: unwrapped };
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return changed ? JSON.stringify(normalizedBlocks) : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isLegacyGuardianBackfillRow(
|
|
182
|
+
row: CandidateMessageRow,
|
|
183
|
+
parsed: {
|
|
184
|
+
metadata: Record<string, unknown>;
|
|
185
|
+
slackMeta: SlackMessageMetadata;
|
|
186
|
+
},
|
|
187
|
+
): boolean {
|
|
188
|
+
if (row.role !== "user") return false;
|
|
189
|
+
if (parsed.slackMeta.eventKind !== "message") return false;
|
|
190
|
+
if (
|
|
191
|
+
Object.prototype.hasOwnProperty.call(
|
|
192
|
+
parsed.metadata,
|
|
193
|
+
"provenanceTrustClass",
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Old live Slack turns were written with turn-channel metadata. Old
|
|
200
|
+
// backfill rows were written directly with only `slackMeta`, and the old
|
|
201
|
+
// backfill invariant was: non-guardian non-empty text was stored wrapped,
|
|
202
|
+
// while guardian-authored non-empty text was stored raw. Only stamp the
|
|
203
|
+
// non-empty raw-text case; attachment-only / empty rows stay conservative.
|
|
204
|
+
if (
|
|
205
|
+
Object.prototype.hasOwnProperty.call(parsed.metadata, "userMessageChannel")
|
|
206
|
+
) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return hasNonEmptyRawText(row.content);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function hasNonEmptyRawText(content: string): boolean {
|
|
214
|
+
if (parseExternalContentEnvelope(content)) return false;
|
|
215
|
+
|
|
216
|
+
let parsed: unknown;
|
|
217
|
+
try {
|
|
218
|
+
parsed = JSON.parse(content);
|
|
219
|
+
} catch {
|
|
220
|
+
return content.trim().length > 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (typeof parsed === "string") {
|
|
224
|
+
return parsed.trim().length > 0 && !parseExternalContentEnvelope(parsed);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!Array.isArray(parsed)) return false;
|
|
228
|
+
|
|
229
|
+
return parsed.some((block) => {
|
|
230
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const text = (block as Record<string, unknown>).text;
|
|
234
|
+
return (
|
|
235
|
+
typeof text === "string" &&
|
|
236
|
+
text.trim().length > 0 &&
|
|
237
|
+
!parseExternalContentEnvelope(text)
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -208,6 +208,12 @@ export { migrateCreateProviderConnections } from "./243-provider-connections.js"
|
|
|
208
208
|
export { migrateProviderConnectionStatusLabel } from "./244-provider-connection-status-label.js";
|
|
209
209
|
export { migrateMemoryRetrospectiveState } from "./245-memory-retrospective-state.js";
|
|
210
210
|
export { migrateBackfillProviderConnectionLabel } from "./246-backfill-provider-connection-label.js";
|
|
211
|
+
export { migrateExternalConversationBindingThreadId } from "./247-external-conversation-binding-thread-id.js";
|
|
212
|
+
export { createOnboardingEventsTable } from "./248-create-onboarding-events.js";
|
|
213
|
+
export {
|
|
214
|
+
downNormalizeSlackExternalContent,
|
|
215
|
+
migrateNormalizeSlackExternalContent,
|
|
216
|
+
} from "./249-normalize-slack-external-content.js";
|
|
211
217
|
export {
|
|
212
218
|
MIGRATION_REGISTRY,
|
|
213
219
|
type MigrationRegistryEntry,
|
|
@@ -48,6 +48,7 @@ import { downMemoryV2ActivationLogs } from "./234-memory-v2-activation-logs.js";
|
|
|
48
48
|
import { downSlackCompactionWatermark } from "./235-slack-compaction-watermark.js";
|
|
49
49
|
import { downToolInvocationsMatchedRuleId } from "./236-tool-invocations-matched-rule-id.js";
|
|
50
50
|
import { downHeartbeatRuns } from "./237-heartbeat-runs.js";
|
|
51
|
+
import { downNormalizeSlackExternalContent } from "./249-normalize-slack-external-content.js";
|
|
51
52
|
|
|
52
53
|
export interface MigrationRegistryEntry {
|
|
53
54
|
/** The checkpoint key written to memory_checkpoints on completion. */
|
|
@@ -412,6 +413,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
412
413
|
"Create heartbeat_runs table for tracking heartbeat execution lifecycle with CAS state transitions",
|
|
413
414
|
down: downHeartbeatRuns,
|
|
414
415
|
},
|
|
416
|
+
{
|
|
417
|
+
key: "migration_normalize_slack_external_content_v1",
|
|
418
|
+
version: 48,
|
|
419
|
+
description:
|
|
420
|
+
"Normalize legacy persisted Slack external_content wrappers back to raw message content",
|
|
421
|
+
down: downNormalizeSlackExternalContent,
|
|
422
|
+
},
|
|
415
423
|
];
|
|
416
424
|
|
|
417
425
|
export function getMaxMigrationVersion(): number {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
2
|
+
import { v4 as uuid } from "uuid";
|
|
3
|
+
|
|
4
|
+
import { getConfig } from "../config/loader.js";
|
|
5
|
+
import { getDb } from "./db-connection.js";
|
|
6
|
+
import { onboardingEvents } from "./schema.js";
|
|
7
|
+
|
|
8
|
+
export interface OnboardingEvent {
|
|
9
|
+
id: string;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
screen: string;
|
|
12
|
+
toolsJson: string | null;
|
|
13
|
+
tasksJson: string | null;
|
|
14
|
+
tone: string | null;
|
|
15
|
+
googleConnected: boolean | null;
|
|
16
|
+
googleScopesJson: string | null;
|
|
17
|
+
abVariant: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RecordOnboardingEventParams {
|
|
21
|
+
screen: string;
|
|
22
|
+
tools?: string[];
|
|
23
|
+
tasks?: string[];
|
|
24
|
+
tone?: string;
|
|
25
|
+
googleConnected?: boolean;
|
|
26
|
+
googleScopes?: string[];
|
|
27
|
+
abVariant?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Record an onboarding event (pre-chat selections and Google connect status).
|
|
32
|
+
* Returns null when usage data collection is disabled.
|
|
33
|
+
*/
|
|
34
|
+
export function recordOnboardingEvent(
|
|
35
|
+
params: RecordOnboardingEventParams,
|
|
36
|
+
): OnboardingEvent | null {
|
|
37
|
+
if (!getConfig().collectUsageData) return null;
|
|
38
|
+
const db = getDb();
|
|
39
|
+
const event: OnboardingEvent = {
|
|
40
|
+
id: uuid(),
|
|
41
|
+
createdAt: Date.now(),
|
|
42
|
+
screen: params.screen,
|
|
43
|
+
toolsJson: params.tools ? JSON.stringify(params.tools) : null,
|
|
44
|
+
tasksJson: params.tasks ? JSON.stringify(params.tasks) : null,
|
|
45
|
+
tone: params.tone ?? null,
|
|
46
|
+
googleConnected: params.googleConnected ?? null,
|
|
47
|
+
googleScopesJson: params.googleScopes
|
|
48
|
+
? JSON.stringify(params.googleScopes)
|
|
49
|
+
: null,
|
|
50
|
+
abVariant: params.abVariant ?? null,
|
|
51
|
+
};
|
|
52
|
+
db.insert(onboardingEvents)
|
|
53
|
+
.values({
|
|
54
|
+
id: event.id,
|
|
55
|
+
createdAt: event.createdAt,
|
|
56
|
+
screen: event.screen,
|
|
57
|
+
toolsJson: event.toolsJson,
|
|
58
|
+
tasksJson: event.tasksJson,
|
|
59
|
+
tone: event.tone,
|
|
60
|
+
googleConnected: event.googleConnected,
|
|
61
|
+
googleScopesJson: event.googleScopesJson,
|
|
62
|
+
abVariant: event.abVariant,
|
|
63
|
+
})
|
|
64
|
+
.run();
|
|
65
|
+
return event;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Query onboarding events that haven't been reported to telemetry yet.
|
|
70
|
+
* Uses a compound cursor (createdAt + id) for reliable watermarking.
|
|
71
|
+
*/
|
|
72
|
+
export function queryUnreportedOnboardingEvents(
|
|
73
|
+
afterCreatedAt: number,
|
|
74
|
+
afterId: string | undefined,
|
|
75
|
+
limit: number,
|
|
76
|
+
): OnboardingEvent[] {
|
|
77
|
+
const db = getDb();
|
|
78
|
+
const rows = db
|
|
79
|
+
.select({
|
|
80
|
+
id: onboardingEvents.id,
|
|
81
|
+
createdAt: onboardingEvents.createdAt,
|
|
82
|
+
screen: onboardingEvents.screen,
|
|
83
|
+
toolsJson: onboardingEvents.toolsJson,
|
|
84
|
+
tasksJson: onboardingEvents.tasksJson,
|
|
85
|
+
tone: onboardingEvents.tone,
|
|
86
|
+
googleConnected: onboardingEvents.googleConnected,
|
|
87
|
+
googleScopesJson: onboardingEvents.googleScopesJson,
|
|
88
|
+
abVariant: onboardingEvents.abVariant,
|
|
89
|
+
})
|
|
90
|
+
.from(onboardingEvents)
|
|
91
|
+
.where(
|
|
92
|
+
afterId
|
|
93
|
+
? or(
|
|
94
|
+
gt(onboardingEvents.createdAt, afterCreatedAt),
|
|
95
|
+
and(
|
|
96
|
+
eq(onboardingEvents.createdAt, afterCreatedAt),
|
|
97
|
+
gt(onboardingEvents.id, afterId),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
: gt(onboardingEvents.createdAt, afterCreatedAt),
|
|
101
|
+
)
|
|
102
|
+
.orderBy(asc(onboardingEvents.createdAt), asc(onboardingEvents.id))
|
|
103
|
+
.limit(limit)
|
|
104
|
+
.all();
|
|
105
|
+
return rows;
|
|
106
|
+
}
|
|
@@ -78,6 +78,7 @@ export const externalConversationBindings = sqliteTable(
|
|
|
78
78
|
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
79
79
|
sourceChannel: text("source_channel").notNull(),
|
|
80
80
|
externalChatId: text("external_chat_id").notNull(),
|
|
81
|
+
externalThreadId: text("external_thread_id"),
|
|
81
82
|
externalUserId: text("external_user_id"),
|
|
82
83
|
displayName: text("display_name"),
|
|
83
84
|
username: text("username"),
|
|
@@ -20,9 +20,7 @@ export const providerConnections = sqliteTable(
|
|
|
20
20
|
createdAt: integer("created_at").notNull(),
|
|
21
21
|
updatedAt: integer("updated_at").notNull(),
|
|
22
22
|
},
|
|
23
|
-
(table) => [
|
|
24
|
-
index("idx_provider_connections_provider").on(table.provider),
|
|
25
|
-
],
|
|
23
|
+
(table) => [index("idx_provider_connections_provider").on(table.provider)],
|
|
26
24
|
);
|
|
27
25
|
|
|
28
26
|
export type ProviderConnectionRow = typeof providerConnections.$inferSelect;
|
|
@@ -232,6 +232,18 @@ export const lifecycleEvents = sqliteTable("lifecycle_events", {
|
|
|
232
232
|
createdAt: integer("created_at").notNull(),
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
export const onboardingEvents = sqliteTable("onboarding_events", {
|
|
236
|
+
id: text("id").primaryKey(),
|
|
237
|
+
createdAt: integer("created_at").notNull(),
|
|
238
|
+
screen: text("screen").notNull(),
|
|
239
|
+
toolsJson: text("tools_json"),
|
|
240
|
+
tasksJson: text("tasks_json"),
|
|
241
|
+
tone: text("tone"),
|
|
242
|
+
googleConnected: integer("google_connected", { mode: "boolean" }),
|
|
243
|
+
googleScopesJson: text("google_scopes_json"),
|
|
244
|
+
abVariant: text("ab_variant"),
|
|
245
|
+
});
|
|
246
|
+
|
|
235
247
|
export const traceEvents = sqliteTable(
|
|
236
248
|
"trace_events",
|
|
237
249
|
{
|
|
@@ -1,16 +1,102 @@
|
|
|
1
1
|
import { and, asc, eq, gt, or, sql } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { getDb } from "./db-connection.js";
|
|
4
|
-
import { messages } from "./schema.js";
|
|
4
|
+
import { conversations, messages } from "./schema.js";
|
|
5
5
|
|
|
6
6
|
export interface TurnEvent {
|
|
7
7
|
id: string;
|
|
8
8
|
createdAt: number;
|
|
9
|
+
/**
|
|
10
|
+
* Parent conversation id. Lets downstream analytics group turns by
|
|
11
|
+
* conversation (e.g. avg turns per conversation).
|
|
12
|
+
*/
|
|
13
|
+
conversationId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Conversation type of the parent conversation. Used downstream to
|
|
16
|
+
* distinguish user-initiated turns (`"standard"`) from system-generated
|
|
17
|
+
* prompts in `"background"` / `"scheduled"` conversations so analytics
|
|
18
|
+
* (e.g. DAU) can exclude the latter.
|
|
19
|
+
*/
|
|
20
|
+
conversationType: string;
|
|
21
|
+
/**
|
|
22
|
+
* 1-indexed position of this user turn within the parent conversation,
|
|
23
|
+
* counting only real user turns (tool-result rows persisted with
|
|
24
|
+
* role="user" are excluded — same filter as the eligibility predicate
|
|
25
|
+
* below). The first user turn in a conversation is `1`.
|
|
26
|
+
*
|
|
27
|
+
* Computed via correlated subquery on the same filtered set used for
|
|
28
|
+
* eligibility; this scales with batch size (≤ BATCH_SIZE turns per
|
|
29
|
+
* flush) and uses the `idx_messages_conversation_id` index for the
|
|
30
|
+
* partition lookup.
|
|
31
|
+
*/
|
|
32
|
+
turnIndex: number;
|
|
33
|
+
/**
|
|
34
|
+
* Canonical `InterfaceId` enum value identifying the UI surface the user
|
|
35
|
+
* was interacting from at message-creation time (`"macos"`, `"ios"`,
|
|
36
|
+
* `"cli"`, `"web"`, `"chrome-extension"`, `"slack"`, `"telegram"`,
|
|
37
|
+
* `"whatsapp"`, `"email"`, `"phone"`). Sourced from
|
|
38
|
+
* `messages.metadata.userMessageInterface` (stamped on insert by every
|
|
39
|
+
* `persistUserMessage` path that flows through `TurnChannelContext`).
|
|
40
|
+
*
|
|
41
|
+
* Null when the metadata didn't carry the field — historical rows
|
|
42
|
+
* predating the threading, or system-initiated turns with no inbound
|
|
43
|
+
* client context. Downstream analytics should treat null as
|
|
44
|
+
* `"unknown"`.
|
|
45
|
+
*/
|
|
46
|
+
interfaceId: string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Canonical `ChannelId` enum value identifying the messaging fabric the
|
|
49
|
+
* user message arrived on (`"vellum"` for in-app messaging from
|
|
50
|
+
* macos/ios/web/cli; `"slack"`/`"telegram"`/`"whatsapp"`/`"email"`/
|
|
51
|
+
* `"phone"` for channel-based interfaces). Sourced from
|
|
52
|
+
* `messages.metadata.userMessageChannel`.
|
|
53
|
+
*
|
|
54
|
+
* The 7th `ChannelId` value (`"platform"`) is APNs-push outbound-only
|
|
55
|
+
* and should never appear on a user-message row.
|
|
56
|
+
*/
|
|
57
|
+
channelId: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Flexible client metadata stashed under `messages.metadata.client` by
|
|
60
|
+
* the HTTP header middleware. Carries optional `browserFamily`,
|
|
61
|
+
* `browserVersion`, `os`, `interfaceVersion` (and is extensible without
|
|
62
|
+
* a migration since it lives inside the JSON column). Null when no
|
|
63
|
+
* client headers were attached.
|
|
64
|
+
*
|
|
65
|
+
* Returned as raw JSON text — the reporter parses + re-shapes for the
|
|
66
|
+
* wire format.
|
|
67
|
+
*/
|
|
68
|
+
clientMetadata: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* SQL fragment that excludes tool-result rows persisted with role="user".
|
|
73
|
+
* Kept as a single source of truth so the eligibility predicate and the
|
|
74
|
+
* correlated `turn_index` count stay in lockstep — otherwise the index
|
|
75
|
+
* can drift from the visible turn stream and break "first turn" /
|
|
76
|
+
* "turns per conversation" math.
|
|
77
|
+
*
|
|
78
|
+
* `<alias>` is interpolated as the SQL identifier for the table whose
|
|
79
|
+
* `content` column should be filtered (e.g. `messages` for the outer
|
|
80
|
+
* query, `m2` for the correlated subquery).
|
|
81
|
+
*/
|
|
82
|
+
function realUserTurnContentFilter(alias: string): ReturnType<typeof sql> {
|
|
83
|
+
return sql.raw(
|
|
84
|
+
`${alias}.content NOT LIKE '%"type":"tool\\_result"%' ESCAPE '\\' ` +
|
|
85
|
+
`AND ${alias}.content NOT LIKE '%"type":"web\\_search\\_tool\\_result"%' ESCAPE '\\'`,
|
|
86
|
+
);
|
|
9
87
|
}
|
|
10
88
|
|
|
11
89
|
/**
|
|
12
90
|
* Query user messages (turns) that haven't been reported to telemetry yet.
|
|
13
91
|
* Uses a compound cursor (createdAt + id) for reliable watermarking.
|
|
92
|
+
*
|
|
93
|
+
* Joins to `conversations` so each turn carries its `conversationType`.
|
|
94
|
+
* The inner join is safe because `messages.conversationId` has a
|
|
95
|
+
* not-null FK to `conversations.id` (cascade on delete): every message
|
|
96
|
+
* row has a matching conversation row.
|
|
97
|
+
*
|
|
98
|
+
* `turnIndex` is computed via a correlated subquery counting the real
|
|
99
|
+
* user turns in the same conversation up to and including this row.
|
|
14
100
|
*/
|
|
15
101
|
export function queryUnreportedTurnEvents(
|
|
16
102
|
afterCreatedAt: number,
|
|
@@ -19,8 +105,47 @@ export function queryUnreportedTurnEvents(
|
|
|
19
105
|
): TurnEvent[] {
|
|
20
106
|
const db = getDb();
|
|
21
107
|
const rows = db
|
|
22
|
-
.select({
|
|
108
|
+
.select({
|
|
109
|
+
id: messages.id,
|
|
110
|
+
createdAt: messages.createdAt,
|
|
111
|
+
conversationId: messages.conversationId,
|
|
112
|
+
conversationType: conversations.conversationType,
|
|
113
|
+
// 1-indexed turn position within the parent conversation. Counts
|
|
114
|
+
// only real user turns (same filter applied to the outer query).
|
|
115
|
+
// `(created_at, id)` lex-comparison matches the watermark cursor
|
|
116
|
+
// ordering so ties on `created_at` are broken deterministically.
|
|
117
|
+
turnIndex: sql<number>`(
|
|
118
|
+
SELECT COUNT(*) FROM messages AS m2
|
|
119
|
+
WHERE m2.conversation_id = ${messages.conversationId}
|
|
120
|
+
AND m2.role = 'user'
|
|
121
|
+
AND ${realUserTurnContentFilter("m2")}
|
|
122
|
+
AND (m2.created_at < ${messages.createdAt}
|
|
123
|
+
OR (m2.created_at = ${messages.createdAt}
|
|
124
|
+
AND m2.id <= ${messages.id}))
|
|
125
|
+
)`.as("turn_index"),
|
|
126
|
+
// Client attribution: extract from `messages.metadata` JSON.
|
|
127
|
+
// `userMessageInterface` and `userMessageChannel` are stamped on
|
|
128
|
+
// insert by every `persistUserMessage` path that flows through
|
|
129
|
+
// `TurnChannelContext`. `client` is the flexible namespace for
|
|
130
|
+
// browser/os/version metadata attached by HTTP header middleware.
|
|
131
|
+
// `json_extract` returns SQL NULL when the JSON path is absent —
|
|
132
|
+
// exactly the null semantics we want on the wire.
|
|
133
|
+
interfaceId: sql<
|
|
134
|
+
string | null
|
|
135
|
+
>`json_extract(${messages.metadata}, '$.userMessageInterface')`.as(
|
|
136
|
+
"interface_id",
|
|
137
|
+
),
|
|
138
|
+
channelId: sql<
|
|
139
|
+
string | null
|
|
140
|
+
>`json_extract(${messages.metadata}, '$.userMessageChannel')`.as(
|
|
141
|
+
"channel_id",
|
|
142
|
+
),
|
|
143
|
+
clientMetadata: sql<
|
|
144
|
+
string | null
|
|
145
|
+
>`json_extract(${messages.metadata}, '$.client')`.as("client_metadata"),
|
|
146
|
+
})
|
|
23
147
|
.from(messages)
|
|
148
|
+
.innerJoin(conversations, eq(messages.conversationId, conversations.id))
|
|
24
149
|
.where(
|
|
25
150
|
and(
|
|
26
151
|
eq(messages.role, "user"),
|
|
@@ -52,9 +52,7 @@ mock.module("../../qdrant-client.js", () => ({
|
|
|
52
52
|
|
|
53
53
|
const state = {
|
|
54
54
|
embedCalls: [] as Array<{ inputs: unknown[] }>,
|
|
55
|
-
sparseCalls: [] as string[],
|
|
56
55
|
embedReturn: [[0.1, 0.2, 0.3]] as number[][],
|
|
57
|
-
sparseReturn: { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] },
|
|
58
56
|
/**
|
|
59
57
|
* Programmable Qdrant query response queues — one per channel. Each test
|
|
60
58
|
* stages whatever ordered hits it needs and lets `simBatch` /
|
|
@@ -87,10 +85,6 @@ mock.module("../../embedding-backend.js", () => ({
|
|
|
87
85
|
vectors: state.embedReturn,
|
|
88
86
|
};
|
|
89
87
|
},
|
|
90
|
-
generateSparseEmbedding: (text: string) => {
|
|
91
|
-
state.sparseCalls.push(text);
|
|
92
|
-
return state.sparseReturn;
|
|
93
|
-
},
|
|
94
88
|
}));
|
|
95
89
|
|
|
96
90
|
class MockQdrantClient {
|
|
@@ -176,9 +170,7 @@ const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
|
|
|
176
170
|
|
|
177
171
|
function resetState(): void {
|
|
178
172
|
state.embedCalls.length = 0;
|
|
179
|
-
state.sparseCalls.length = 0;
|
|
180
173
|
state.embedReturn = [[0.1, 0.2, 0.3]];
|
|
181
|
-
state.sparseReturn = { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] };
|
|
182
174
|
state.queryResponses.dense.length = 0;
|
|
183
175
|
state.queryResponses.sparse.length = 0;
|
|
184
176
|
state.queryCalls.length = 0;
|