@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
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { and, count, desc, eq, sql } from "drizzle-orm";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
parseExternalContentEnvelope,
|
|
5
|
+
type UntrustedContentSource,
|
|
6
|
+
unwrapExternalContentForDisplay,
|
|
7
|
+
wrapUntrustedContent,
|
|
8
|
+
} from "../security/untrusted-content.js";
|
|
3
9
|
import { getLogger } from "../util/logger.js";
|
|
4
10
|
import type { ConversationRow } from "./conversation-crud.js";
|
|
5
11
|
import { parseConversation } from "./conversation-crud.js";
|
|
@@ -55,9 +61,14 @@ export function listConversations(
|
|
|
55
61
|
// SQLite file without running migrations in-process, so legacy private rows
|
|
56
62
|
// can briefly exist before migration cleanup. Hide them from foreground
|
|
57
63
|
// lists until the next migration pass deletes them.
|
|
64
|
+
//
|
|
65
|
+
// group_id is checked alongside conversationType so that conversations
|
|
66
|
+
// routed to system:background (e.g. heartbeat) via conversationMetadata
|
|
67
|
+
// but created with conversationType "standard" (vellum channel strategy)
|
|
68
|
+
// appear in the correct bucket.
|
|
58
69
|
const typeCond = backgroundOnly
|
|
59
|
-
? sql
|
|
60
|
-
: sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`;
|
|
70
|
+
? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
|
|
71
|
+
: sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
|
|
61
72
|
const where = includeArchived
|
|
62
73
|
? typeCond
|
|
63
74
|
: sql`${typeCond} AND ${conversations.archivedAt} IS NULL`;
|
|
@@ -151,10 +162,11 @@ export function listConversationsByTitlePrefix(
|
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
export function countConversations(backgroundOnly = false): number {
|
|
165
|
+
ensureGroupMigration();
|
|
154
166
|
const db = getDb();
|
|
155
167
|
const where = backgroundOnly
|
|
156
|
-
? sql
|
|
157
|
-
: sql`${conversations.conversationType} NOT IN ('background', 'scheduled')`;
|
|
168
|
+
? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
|
|
169
|
+
: sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
|
|
158
170
|
const [{ total }] = db
|
|
159
171
|
.select({ total: count() })
|
|
160
172
|
.from(conversations)
|
|
@@ -233,6 +245,7 @@ export function searchConversations(
|
|
|
233
245
|
): ConversationSearchResult[] {
|
|
234
246
|
if (!query.trim()) return [];
|
|
235
247
|
|
|
248
|
+
ensureGroupMigration();
|
|
236
249
|
const db = getDb();
|
|
237
250
|
const limit = opts?.limit ?? 20;
|
|
238
251
|
const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
|
|
@@ -262,7 +275,7 @@ export function searchConversations(
|
|
|
262
275
|
FROM messages_fts f
|
|
263
276
|
JOIN messages m ON m.id = f.message_id
|
|
264
277
|
JOIN conversations c ON c.id = m.conversation_id
|
|
265
|
-
WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
|
|
278
|
+
WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
|
|
266
279
|
LIMIT 1000
|
|
267
280
|
`,
|
|
268
281
|
ftsMatch,
|
|
@@ -287,7 +300,7 @@ export function searchConversations(
|
|
|
287
300
|
SELECT DISTINCT m.conversation_id
|
|
288
301
|
FROM messages m
|
|
289
302
|
JOIN conversations c ON c.id = m.conversation_id
|
|
290
|
-
WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
|
|
303
|
+
WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
|
|
291
304
|
LIMIT 1000
|
|
292
305
|
`,
|
|
293
306
|
likePattern,
|
|
@@ -302,6 +315,7 @@ export function searchConversations(
|
|
|
302
315
|
.where(
|
|
303
316
|
and(
|
|
304
317
|
sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`,
|
|
318
|
+
sql`COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`,
|
|
305
319
|
sql`${conversations.title} LIKE ${titlePattern} ESCAPE '\\'`,
|
|
306
320
|
sql`${conversations.archivedAt} IS NULL`,
|
|
307
321
|
),
|
|
@@ -403,29 +417,68 @@ export function searchConversations(
|
|
|
403
417
|
* text; we extract a readable snippet in either case.
|
|
404
418
|
*/
|
|
405
419
|
export function buildExcerpt(rawContent: string, query: string): string {
|
|
420
|
+
return buildExcerptWithExternalContentMode(rawContent, query, "display");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Build an excerpt for model-facing recall evidence. Unlike display excerpts,
|
|
425
|
+
* this keeps complete external_content envelopes around untrusted snippets so
|
|
426
|
+
* the model still sees clear third-party content boundaries.
|
|
427
|
+
*/
|
|
428
|
+
export function buildRecallEvidenceExcerpt(
|
|
429
|
+
rawContent: string,
|
|
430
|
+
query: string,
|
|
431
|
+
): string {
|
|
432
|
+
return buildExcerptWithExternalContentMode(rawContent, query, "preserve");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function buildExcerptWithExternalContentMode(
|
|
436
|
+
rawContent: string,
|
|
437
|
+
query: string,
|
|
438
|
+
externalContentMode: "display" | "preserve",
|
|
439
|
+
): string {
|
|
406
440
|
// Try to extract plain text from JSON content blocks first.
|
|
407
441
|
let text = rawContent;
|
|
408
442
|
try {
|
|
409
443
|
const parsed = JSON.parse(rawContent);
|
|
410
444
|
if (Array.isArray(parsed)) {
|
|
411
445
|
const parts: string[] = [];
|
|
446
|
+
let preservedExternalContent = false;
|
|
412
447
|
for (const block of parsed) {
|
|
413
448
|
if (typeof block === "object" && block != null) {
|
|
414
449
|
if (block.type === "text" && typeof block.text === "string") {
|
|
415
|
-
|
|
450
|
+
if (externalContentMode === "display") {
|
|
451
|
+
parts.push(unwrapExternalContentForDisplay(block.text));
|
|
452
|
+
} else {
|
|
453
|
+
const excerpt = buildRecallEvidenceText(block.text, query);
|
|
454
|
+
parts.push(excerpt.text);
|
|
455
|
+
preservedExternalContent ||= excerpt.preservedExternalContent;
|
|
456
|
+
}
|
|
416
457
|
} else if (
|
|
417
458
|
block.type === "tool_result" ||
|
|
418
459
|
block.type === "web_search_tool_result"
|
|
419
460
|
) {
|
|
420
461
|
const inner = Array.isArray(block.content) ? block.content : [];
|
|
421
462
|
for (const ib of inner) {
|
|
422
|
-
if (ib?.type === "text" && typeof ib.text === "string")
|
|
423
|
-
|
|
463
|
+
if (ib?.type === "text" && typeof ib.text === "string") {
|
|
464
|
+
if (externalContentMode === "display") {
|
|
465
|
+
parts.push(unwrapExternalContentForDisplay(ib.text));
|
|
466
|
+
} else {
|
|
467
|
+
const excerpt = buildRecallEvidenceText(ib.text, query);
|
|
468
|
+
parts.push(excerpt.text);
|
|
469
|
+
preservedExternalContent ||= excerpt.preservedExternalContent;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
424
472
|
}
|
|
425
473
|
}
|
|
426
474
|
}
|
|
427
475
|
}
|
|
428
|
-
if (parts.length > 0)
|
|
476
|
+
if (parts.length > 0) {
|
|
477
|
+
text = parts.join(" ");
|
|
478
|
+
if (externalContentMode === "preserve" && preservedExternalContent) {
|
|
479
|
+
return text;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
429
482
|
} else if (typeof parsed === "string") {
|
|
430
483
|
text = parsed;
|
|
431
484
|
}
|
|
@@ -433,6 +486,53 @@ export function buildExcerpt(rawContent: string, query: string): string {
|
|
|
433
486
|
// Not JSON — use as-is
|
|
434
487
|
}
|
|
435
488
|
|
|
489
|
+
if (externalContentMode === "display") {
|
|
490
|
+
text = unwrapExternalContentForDisplay(text);
|
|
491
|
+
} else {
|
|
492
|
+
const envelope = parseExternalContentEnvelope(text);
|
|
493
|
+
if (envelope) {
|
|
494
|
+
const innerExcerpt = buildExcerptFromText(envelope.content, query);
|
|
495
|
+
return wrapRecallEvidenceExcerpt(
|
|
496
|
+
innerExcerpt,
|
|
497
|
+
envelope.source,
|
|
498
|
+
envelope.origin,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return buildExcerptFromText(text, query);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function buildRecallEvidenceText(
|
|
507
|
+
text: string,
|
|
508
|
+
query: string,
|
|
509
|
+
): { text: string; preservedExternalContent: boolean } {
|
|
510
|
+
const envelope = parseExternalContentEnvelope(text);
|
|
511
|
+
if (!envelope) {
|
|
512
|
+
return { text, preservedExternalContent: false };
|
|
513
|
+
}
|
|
514
|
+
const innerExcerpt = buildExcerptFromText(envelope.content, query);
|
|
515
|
+
return {
|
|
516
|
+
text: wrapRecallEvidenceExcerpt(
|
|
517
|
+
innerExcerpt,
|
|
518
|
+
envelope.source,
|
|
519
|
+
envelope.origin,
|
|
520
|
+
),
|
|
521
|
+
preservedExternalContent: true,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function wrapRecallEvidenceExcerpt(
|
|
526
|
+
excerpt: string,
|
|
527
|
+
source: UntrustedContentSource,
|
|
528
|
+
origin?: string,
|
|
529
|
+
): string {
|
|
530
|
+
return origin
|
|
531
|
+
? wrapUntrustedContent(excerpt, { source, sourceDetail: origin })
|
|
532
|
+
: wrapUntrustedContent(excerpt, { source });
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function buildExcerptFromText(text: string, query: string): string {
|
|
436
536
|
const WINDOW = 100;
|
|
437
537
|
const lowerText = text.toLowerCase();
|
|
438
538
|
const lowerQuery = query.toLowerCase();
|
package/src/memory/db-init.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
createMessagesFts,
|
|
33
33
|
createNotificationTables,
|
|
34
34
|
createOAuthTables,
|
|
35
|
+
createOnboardingEventsTable,
|
|
35
36
|
createScopedApprovalGrantsTable,
|
|
36
37
|
createSequenceTables,
|
|
37
38
|
createTasksAndWorkItemsTables,
|
|
@@ -94,6 +95,7 @@ import {
|
|
|
94
95
|
migrateDropSetupSkillIdColumn,
|
|
95
96
|
migrateDropSimplifiedMemory,
|
|
96
97
|
migrateDropUsageCompositeIndexes,
|
|
98
|
+
migrateExternalConversationBindingThreadId,
|
|
97
99
|
migrateFkCascadeRebuilds,
|
|
98
100
|
migrateGuardianActionFollowup,
|
|
99
101
|
migrateGuardianActionSupersession,
|
|
@@ -122,6 +124,7 @@ import {
|
|
|
122
124
|
migrateMessagesConversationCreatedAtIndex,
|
|
123
125
|
migrateMessagesFtsBackfill,
|
|
124
126
|
migrateNormalizePhoneIdentities,
|
|
127
|
+
migrateNormalizeSlackExternalContent,
|
|
125
128
|
migrateNormalizeUserFileByPrincipal,
|
|
126
129
|
migrateNotificationDeliveryThreadDecision,
|
|
127
130
|
migrateOAuthAppsClientSecretPath,
|
|
@@ -424,6 +427,9 @@ export function initializeDb(): void {
|
|
|
424
427
|
migrateProviderConnectionStatusLabel,
|
|
425
428
|
migrateMemoryRetrospectiveState,
|
|
426
429
|
migrateBackfillProviderConnectionLabel,
|
|
430
|
+
migrateExternalConversationBindingThreadId,
|
|
431
|
+
createOnboardingEventsTable,
|
|
432
|
+
migrateNormalizeSlackExternalContent,
|
|
427
433
|
];
|
|
428
434
|
|
|
429
435
|
// Run each migration step, catching and logging individual failures so one
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* finding messages by source identifiers, and managing raw payload storage.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { and, eq, isNotNull } from "drizzle-orm";
|
|
8
|
+
import { and, eq, isNotNull, or } from "drizzle-orm";
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
10
|
|
|
11
|
+
import { readSlackMetadataFromMessageMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
11
12
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
12
|
-
import {
|
|
13
|
+
import { selectSlackMetaCandidateMetadata } from "./conversation-crud.js";
|
|
14
|
+
import {
|
|
15
|
+
getConversationByKey,
|
|
16
|
+
getOrCreateConversation,
|
|
17
|
+
setConversationKeyIfAbsent,
|
|
18
|
+
} from "./conversation-key-store.js";
|
|
13
19
|
import { getDb } from "./db-connection.js";
|
|
14
20
|
import { channelInboundEvents, conversations } from "./schema.js";
|
|
15
21
|
|
|
@@ -23,6 +29,144 @@ export interface InboundResult {
|
|
|
23
29
|
export interface RecordInboundOptions {
|
|
24
30
|
sourceMessageId?: string;
|
|
25
31
|
assistantId?: string;
|
|
32
|
+
sourceThreadId?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE = 50;
|
|
36
|
+
const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
|
|
37
|
+
|
|
38
|
+
function buildScopedConversationKeyForAssistant(
|
|
39
|
+
assistantId: string,
|
|
40
|
+
sourceChannel: string,
|
|
41
|
+
externalChatId: string,
|
|
42
|
+
sourceThreadId?: string | null,
|
|
43
|
+
): string {
|
|
44
|
+
const threadId = sourceThreadId?.trim();
|
|
45
|
+
if (sourceChannel === "slack" && threadId) {
|
|
46
|
+
return `asst:${assistantId}:${sourceChannel}:${externalChatId}:thread:${threadId}`;
|
|
47
|
+
}
|
|
48
|
+
return `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildScopedConversationKey(
|
|
52
|
+
sourceChannel: string,
|
|
53
|
+
externalChatId: string,
|
|
54
|
+
sourceThreadId?: string | null,
|
|
55
|
+
): string {
|
|
56
|
+
return buildScopedConversationKeyForAssistant(
|
|
57
|
+
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
58
|
+
sourceChannel,
|
|
59
|
+
externalChatId,
|
|
60
|
+
sourceThreadId,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function legacySlackConversationHasThreadEvidence(
|
|
65
|
+
conversationId: string,
|
|
66
|
+
externalChatId: string,
|
|
67
|
+
sourceThreadId: string,
|
|
68
|
+
): boolean {
|
|
69
|
+
const db = getDb();
|
|
70
|
+
const inboundEvidence = db
|
|
71
|
+
.select({ id: channelInboundEvents.id })
|
|
72
|
+
.from(channelInboundEvents)
|
|
73
|
+
.where(
|
|
74
|
+
and(
|
|
75
|
+
eq(channelInboundEvents.conversationId, conversationId),
|
|
76
|
+
eq(channelInboundEvents.sourceChannel, "slack"),
|
|
77
|
+
eq(channelInboundEvents.externalChatId, externalChatId),
|
|
78
|
+
or(
|
|
79
|
+
eq(channelInboundEvents.sourceMessageId, sourceThreadId),
|
|
80
|
+
eq(channelInboundEvents.externalMessageId, sourceThreadId),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
.get();
|
|
85
|
+
|
|
86
|
+
if (inboundEvidence) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let offset = 0;
|
|
91
|
+
while (offset < SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN) {
|
|
92
|
+
const remaining = SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN - offset;
|
|
93
|
+
const batchLimit = Math.min(
|
|
94
|
+
SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE,
|
|
95
|
+
remaining,
|
|
96
|
+
);
|
|
97
|
+
const metadataRows = selectSlackMetaCandidateMetadata(
|
|
98
|
+
conversationId,
|
|
99
|
+
batchLimit,
|
|
100
|
+
offset,
|
|
101
|
+
{ includeFlatLegacy: true },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (metadataRows.length === 0) return false;
|
|
105
|
+
for (const metadata of metadataRows) {
|
|
106
|
+
const slackMeta = readSlackMetadataFromMessageMetadata(metadata, {
|
|
107
|
+
allowFlatLegacy: true,
|
|
108
|
+
});
|
|
109
|
+
if (
|
|
110
|
+
slackMeta?.channelId === externalChatId &&
|
|
111
|
+
slackMeta.threadTs === sourceThreadId
|
|
112
|
+
) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (metadataRows.length < batchLimit) return false;
|
|
118
|
+
offset += metadataRows.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveInboundConversation(
|
|
125
|
+
assistantId: string,
|
|
126
|
+
sourceChannel: string,
|
|
127
|
+
externalChatId: string,
|
|
128
|
+
sourceThreadId?: string | null,
|
|
129
|
+
): { conversationId: string } {
|
|
130
|
+
const threadedKey = buildScopedConversationKeyForAssistant(
|
|
131
|
+
assistantId,
|
|
132
|
+
sourceChannel,
|
|
133
|
+
externalChatId,
|
|
134
|
+
sourceThreadId,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const threadId = sourceThreadId?.trim();
|
|
138
|
+
if (sourceChannel !== "slack" || !threadId) {
|
|
139
|
+
return getOrCreateConversation(threadedKey);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const threadedMapping = getConversationByKey(threadedKey);
|
|
143
|
+
if (threadedMapping) {
|
|
144
|
+
return { conversationId: threadedMapping.conversationId };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const legacyKey = buildScopedConversationKeyForAssistant(
|
|
148
|
+
assistantId,
|
|
149
|
+
sourceChannel,
|
|
150
|
+
externalChatId,
|
|
151
|
+
null,
|
|
152
|
+
);
|
|
153
|
+
const legacyMapping = getConversationByKey(legacyKey);
|
|
154
|
+
if (
|
|
155
|
+
legacyMapping &&
|
|
156
|
+
legacySlackConversationHasThreadEvidence(
|
|
157
|
+
legacyMapping.conversationId,
|
|
158
|
+
externalChatId,
|
|
159
|
+
threadId,
|
|
160
|
+
)
|
|
161
|
+
) {
|
|
162
|
+
setConversationKeyIfAbsent(threadedKey, legacyMapping.conversationId);
|
|
163
|
+
const aliasedMapping = getConversationByKey(threadedKey);
|
|
164
|
+
if (aliasedMapping) {
|
|
165
|
+
return { conversationId: aliasedMapping.conversationId };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return getOrCreateConversation(threadedKey);
|
|
26
170
|
}
|
|
27
171
|
|
|
28
172
|
/**
|
|
@@ -62,8 +206,12 @@ export function recordInbound(
|
|
|
62
206
|
}
|
|
63
207
|
|
|
64
208
|
const assistantId = options?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
65
|
-
const
|
|
66
|
-
|
|
209
|
+
const mapping = resolveInboundConversation(
|
|
210
|
+
assistantId,
|
|
211
|
+
sourceChannel,
|
|
212
|
+
externalChatId,
|
|
213
|
+
options?.sourceThreadId,
|
|
214
|
+
);
|
|
67
215
|
const now = Date.now();
|
|
68
216
|
const eventId = uuid();
|
|
69
217
|
|
|
@@ -176,4 +324,3 @@ export function clearPayload(eventId: string): void {
|
|
|
176
324
|
.where(eq(channelInboundEvents.id, eventId))
|
|
177
325
|
.run();
|
|
178
326
|
}
|
|
179
|
-
|
|
@@ -4,8 +4,8 @@ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags
|
|
|
4
4
|
import { getOllamaBaseUrlEnv } from "../config/env.js";
|
|
5
5
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
6
6
|
import type { AssistantConfig } from "../config/types.js";
|
|
7
|
-
import {
|
|
8
|
-
import { resolveManagedProxyContext } from "../providers/
|
|
7
|
+
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
|
|
8
|
+
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
|
|
9
9
|
import { getProviderKeyAsync } from "../security/secure-keys.js";
|
|
10
10
|
import { getLogger } from "../util/logger.js";
|
|
11
11
|
import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
|
|
@@ -311,7 +311,7 @@ export async function selectEmbeddingBackend(
|
|
|
311
311
|
) {
|
|
312
312
|
const proxyCtx = await resolveManagedProxyContext();
|
|
313
313
|
if (proxyCtx.enabled) {
|
|
314
|
-
const meta =
|
|
314
|
+
const meta = PLATFORM_PROVIDER_META["gemini"];
|
|
315
315
|
if (meta?.managed && meta.proxyPath) {
|
|
316
316
|
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
|
|
317
317
|
const managedModel = config.memory.embeddings.geminiModel;
|
|
@@ -685,7 +685,7 @@ async function selectFallbackBackends(
|
|
|
685
685
|
) {
|
|
686
686
|
// Try managed proxy Gemini as fallback when no direct key exists.
|
|
687
687
|
const proxyCtx = await resolveManagedProxyContext();
|
|
688
|
-
const meta =
|
|
688
|
+
const meta = PLATFORM_PROVIDER_META["gemini"];
|
|
689
689
|
if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
|
|
690
690
|
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
|
|
691
691
|
const managedModel = config.memory.embeddings.geminiModel;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* list APIs.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { and, eq, inArray } from "drizzle-orm";
|
|
10
|
+
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
11
11
|
|
|
12
12
|
import { getDb } from "./db-connection.js";
|
|
13
13
|
import { externalConversationBindings } from "./schema.js";
|
|
@@ -16,6 +16,7 @@ export interface ExternalConversationBinding {
|
|
|
16
16
|
conversationId: string;
|
|
17
17
|
sourceChannel: string;
|
|
18
18
|
externalChatId: string;
|
|
19
|
+
externalThreadId?: string | null;
|
|
19
20
|
externalUserId?: string | null;
|
|
20
21
|
displayName?: string | null;
|
|
21
22
|
username?: string | null;
|
|
@@ -29,11 +30,19 @@ export interface UpsertBindingInput {
|
|
|
29
30
|
conversationId: string;
|
|
30
31
|
sourceChannel: string;
|
|
31
32
|
externalChatId: string;
|
|
33
|
+
externalThreadId?: string | null;
|
|
32
34
|
externalUserId?: string | null;
|
|
33
35
|
displayName?: string | null;
|
|
34
36
|
username?: string | null;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function normalizeExternalThreadId(
|
|
40
|
+
externalThreadId?: string | null,
|
|
41
|
+
): string | null {
|
|
42
|
+
const trimmed = externalThreadId?.trim();
|
|
43
|
+
return trimmed ? trimmed : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* Insert or update an external conversation binding on conflict (conversationId).
|
|
39
48
|
* On conflict, updates channel metadata and timestamps.
|
|
@@ -41,12 +50,14 @@ export interface UpsertBindingInput {
|
|
|
41
50
|
export function upsertBinding(input: UpsertBindingInput): void {
|
|
42
51
|
const db = getDb();
|
|
43
52
|
const now = Date.now();
|
|
53
|
+
const externalThreadId = normalizeExternalThreadId(input.externalThreadId);
|
|
44
54
|
|
|
45
|
-
// If a stale binding exists for this
|
|
55
|
+
// If a stale binding exists for this channel/chat/thread tuple under a
|
|
46
56
|
// different conversationId, remove it first so the unique index is not violated.
|
|
47
|
-
const existing =
|
|
57
|
+
const existing = getBindingByChannelChatThread(
|
|
48
58
|
input.sourceChannel,
|
|
49
59
|
input.externalChatId,
|
|
60
|
+
externalThreadId,
|
|
50
61
|
);
|
|
51
62
|
if (existing && existing.conversationId !== input.conversationId) {
|
|
52
63
|
db.delete(externalConversationBindings)
|
|
@@ -64,6 +75,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
|
|
|
64
75
|
conversationId: input.conversationId,
|
|
65
76
|
sourceChannel: input.sourceChannel,
|
|
66
77
|
externalChatId: input.externalChatId,
|
|
78
|
+
externalThreadId,
|
|
67
79
|
externalUserId: input.externalUserId ?? null,
|
|
68
80
|
displayName: input.displayName ?? null,
|
|
69
81
|
username: input.username ?? null,
|
|
@@ -76,6 +88,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
|
|
|
76
88
|
set: {
|
|
77
89
|
sourceChannel: input.sourceChannel,
|
|
78
90
|
externalChatId: input.externalChatId,
|
|
91
|
+
externalThreadId,
|
|
79
92
|
externalUserId: input.externalUserId ?? null,
|
|
80
93
|
displayName: input.displayName ?? null,
|
|
81
94
|
username: input.username ?? null,
|
|
@@ -95,15 +108,18 @@ export function upsertOutboundBinding(input: {
|
|
|
95
108
|
conversationId: string;
|
|
96
109
|
sourceChannel: string;
|
|
97
110
|
externalChatId: string;
|
|
111
|
+
externalThreadId?: string | null;
|
|
98
112
|
}): void {
|
|
99
113
|
const db = getDb();
|
|
100
114
|
const now = Date.now();
|
|
115
|
+
const externalThreadId = normalizeExternalThreadId(input.externalThreadId);
|
|
101
116
|
|
|
102
|
-
// If a stale binding exists for this
|
|
117
|
+
// If a stale binding exists for this channel/chat/thread tuple under a
|
|
103
118
|
// different conversationId, remove it first so the unique index is not violated.
|
|
104
|
-
const existing =
|
|
119
|
+
const existing = getBindingByChannelChatThread(
|
|
105
120
|
input.sourceChannel,
|
|
106
121
|
input.externalChatId,
|
|
122
|
+
externalThreadId,
|
|
107
123
|
);
|
|
108
124
|
if (existing && existing.conversationId !== input.conversationId) {
|
|
109
125
|
db.delete(externalConversationBindings)
|
|
@@ -121,6 +137,7 @@ export function upsertOutboundBinding(input: {
|
|
|
121
137
|
conversationId: input.conversationId,
|
|
122
138
|
sourceChannel: input.sourceChannel,
|
|
123
139
|
externalChatId: input.externalChatId,
|
|
140
|
+
externalThreadId,
|
|
124
141
|
externalUserId: null,
|
|
125
142
|
displayName: null,
|
|
126
143
|
username: null,
|
|
@@ -133,6 +150,7 @@ export function upsertOutboundBinding(input: {
|
|
|
133
150
|
set: {
|
|
134
151
|
sourceChannel: input.sourceChannel,
|
|
135
152
|
externalChatId: input.externalChatId,
|
|
153
|
+
externalThreadId,
|
|
136
154
|
updatedAt: now,
|
|
137
155
|
lastOutboundAt: now,
|
|
138
156
|
},
|
|
@@ -161,8 +179,20 @@ export function getBindingByConversation(
|
|
|
161
179
|
export function getBindingByChannelChat(
|
|
162
180
|
sourceChannel: string,
|
|
163
181
|
externalChatId: string,
|
|
182
|
+
): ExternalConversationBinding | null {
|
|
183
|
+
return getBindingByChannelChatThread(sourceChannel, externalChatId, null);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Look up an external binding by channel + external chat ID + optional thread ID.
|
|
188
|
+
*/
|
|
189
|
+
export function getBindingByChannelChatThread(
|
|
190
|
+
sourceChannel: string,
|
|
191
|
+
externalChatId: string,
|
|
192
|
+
externalThreadId?: string | null,
|
|
164
193
|
): ExternalConversationBinding | null {
|
|
165
194
|
const db = getDb();
|
|
195
|
+
const normalizedThreadId = normalizeExternalThreadId(externalThreadId);
|
|
166
196
|
const row = db
|
|
167
197
|
.select()
|
|
168
198
|
.from(externalConversationBindings)
|
|
@@ -170,6 +200,12 @@ export function getBindingByChannelChat(
|
|
|
170
200
|
and(
|
|
171
201
|
eq(externalConversationBindings.sourceChannel, sourceChannel),
|
|
172
202
|
eq(externalConversationBindings.externalChatId, externalChatId),
|
|
203
|
+
normalizedThreadId
|
|
204
|
+
? eq(
|
|
205
|
+
externalConversationBindings.externalThreadId,
|
|
206
|
+
normalizedThreadId,
|
|
207
|
+
)
|
|
208
|
+
: isNull(externalConversationBindings.externalThreadId),
|
|
173
209
|
),
|
|
174
210
|
)
|
|
175
211
|
.get();
|
|
@@ -195,6 +231,31 @@ export function deleteBindingByChannelChat(
|
|
|
195
231
|
.run();
|
|
196
232
|
}
|
|
197
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Remove an external binding by channel + external chat ID + thread ID.
|
|
236
|
+
*/
|
|
237
|
+
export function deleteBindingByChannelChatThread(
|
|
238
|
+
sourceChannel: string,
|
|
239
|
+
externalChatId: string,
|
|
240
|
+
externalThreadId: string,
|
|
241
|
+
): void {
|
|
242
|
+
const db = getDb();
|
|
243
|
+
const normalizedThreadId = normalizeExternalThreadId(externalThreadId);
|
|
244
|
+
if (!normalizedThreadId) {
|
|
245
|
+
deleteBindingByChannelChat(sourceChannel, externalChatId);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
db.delete(externalConversationBindings)
|
|
249
|
+
.where(
|
|
250
|
+
and(
|
|
251
|
+
eq(externalConversationBindings.sourceChannel, sourceChannel),
|
|
252
|
+
eq(externalConversationBindings.externalChatId, externalChatId),
|
|
253
|
+
eq(externalConversationBindings.externalThreadId, normalizedThreadId),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
.run();
|
|
257
|
+
}
|
|
258
|
+
|
|
198
259
|
/**
|
|
199
260
|
* Get bindings for multiple conversation IDs at once.
|
|
200
261
|
* Returns a map of conversationId -> binding for efficient lookup.
|