@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
|
@@ -107,18 +107,20 @@ mock.module("@qdrant/js-client-rest", () => ({
|
|
|
107
107
|
QdrantClient: MockQdrantClient,
|
|
108
108
|
}));
|
|
109
109
|
|
|
110
|
+
const embedWithBackendMock = mock(async (_config, texts: string[]) => ({
|
|
111
|
+
provider: "local",
|
|
112
|
+
model: "test-model",
|
|
113
|
+
vectors: texts.map(() => [0.1, 0.2, 0.3]) as number[][],
|
|
114
|
+
}));
|
|
115
|
+
const generateSparseEmbeddingMock = mock((_text: string) => ({
|
|
116
|
+
indices: [1, 2, 3],
|
|
117
|
+
values: [0.5, 0.5, 0.5] as number[],
|
|
118
|
+
}));
|
|
110
119
|
const realEmbeddingBackend = await import("../../embedding-backend.js");
|
|
111
120
|
mock.module("../../embedding-backend.js", () => ({
|
|
112
121
|
...realEmbeddingBackend,
|
|
113
|
-
embedWithBackend:
|
|
114
|
-
|
|
115
|
-
model: "test-model",
|
|
116
|
-
vectors: [[0.1, 0.2, 0.3]] as number[][],
|
|
117
|
-
}),
|
|
118
|
-
generateSparseEmbedding: () => ({
|
|
119
|
-
indices: [1, 2, 3],
|
|
120
|
-
values: [0.5, 0.5, 0.5] as number[],
|
|
121
|
-
}),
|
|
122
|
+
embedWithBackend: embedWithBackendMock,
|
|
123
|
+
generateSparseEmbedding: generateSparseEmbeddingMock,
|
|
122
124
|
}));
|
|
123
125
|
|
|
124
126
|
const realQdrantClient = await import("../../qdrant-client.js");
|
|
@@ -293,6 +295,8 @@ beforeEach(() => {
|
|
|
293
295
|
qdrantState.queryResponses.sparse.length = 0;
|
|
294
296
|
loadContextMemoryMock.mockClear();
|
|
295
297
|
retrieveForTurnMock.mockClear();
|
|
298
|
+
embedWithBackendMock.mockClear();
|
|
299
|
+
generateSparseEmbeddingMock.mockClear();
|
|
296
300
|
_resetMemoryV2QdrantForTests();
|
|
297
301
|
});
|
|
298
302
|
|
|
@@ -390,6 +394,59 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
390
394
|
expect(firstBlock.text).toContain("# memory/concepts/alice-vscode.md");
|
|
391
395
|
});
|
|
392
396
|
|
|
397
|
+
test("per-turn dense embedding is computed from combined assistant+user text", async () => {
|
|
398
|
+
// Short referential follow-ups ("do that one") carry no semantic signal
|
|
399
|
+
// on their own — the dense PKB query embedding must mirror v1's
|
|
400
|
+
// `retrieveForTurn` and combine the prior assistant turn so hint search
|
|
401
|
+
// still resolves what "that one" refers to. The sparse vector matches
|
|
402
|
+
// v1 by using the user message alone so lexical signal isn't diluted.
|
|
403
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
404
|
+
|
|
405
|
+
const memory = makeMemory();
|
|
406
|
+
const config = makeConfig(true);
|
|
407
|
+
const assistantText =
|
|
408
|
+
"Alice prefers VS Code as her editor — she finds the extension ecosystem unmatched.";
|
|
409
|
+
const userText = "do that one";
|
|
410
|
+
const messages: Message[] = [
|
|
411
|
+
{
|
|
412
|
+
role: "user",
|
|
413
|
+
content: [
|
|
414
|
+
{ type: "text" as const, text: "what editors did we cover?" },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
role: "assistant",
|
|
419
|
+
content: [{ type: "text" as const, text: assistantText }],
|
|
420
|
+
},
|
|
421
|
+
{ role: "user", content: [{ type: "text" as const, text: userText }] },
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
await memory.prepareMemory(
|
|
425
|
+
messages,
|
|
426
|
+
config,
|
|
427
|
+
new AbortController().signal,
|
|
428
|
+
noopEvent,
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
// v1's `retrieveForTurn` joins assistantLast + userLast with "\n\n" and
|
|
432
|
+
// embeds the combined string as the dense query vector. Assert the v2
|
|
433
|
+
// path makes the exact same embed call somewhere during this turn.
|
|
434
|
+
const expectedCombined = `${assistantText}\n\n${userText}`;
|
|
435
|
+
const matchingCall = embedWithBackendMock.mock.calls.find((call) => {
|
|
436
|
+
const texts = call[1] as string[];
|
|
437
|
+
return texts.length === 1 && texts[0] === expectedCombined;
|
|
438
|
+
});
|
|
439
|
+
expect(matchingCall).toBeDefined();
|
|
440
|
+
|
|
441
|
+
// Sparse embedding for the per-turn query uses userLast only.
|
|
442
|
+
expect(generateSparseEmbeddingMock.mock.calls).toContainEqual([userText]);
|
|
443
|
+
expect(
|
|
444
|
+
generateSparseEmbeddingMock.mock.calls.some((call) =>
|
|
445
|
+
(call[0] as string).includes(assistantText),
|
|
446
|
+
),
|
|
447
|
+
).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
|
|
393
450
|
test("config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
|
|
394
451
|
// No `stageTurn` call — every channel returns `{ points: [] }` so the
|
|
395
452
|
// candidate set is empty and `injectMemoryV2Block` returns block=null.
|
|
@@ -438,8 +438,10 @@ export class ConversationGraphMemory {
|
|
|
438
438
|
// turns. v1's `loadContextMemory` produced these as a side effect of
|
|
439
439
|
// hybrid retrieval; the v2 path skips that retrieval, so embed
|
|
440
440
|
// explicitly here.
|
|
441
|
+
const userQueryText = rawUserText ?? userQuery ?? "";
|
|
441
442
|
const userQueryEmbed = await this.computeQueryVectors(
|
|
442
|
-
|
|
443
|
+
userQueryText,
|
|
444
|
+
userQueryText,
|
|
443
445
|
config,
|
|
444
446
|
signal,
|
|
445
447
|
);
|
|
@@ -598,8 +600,16 @@ export class ConversationGraphMemory {
|
|
|
598
600
|
if (v2.routed) {
|
|
599
601
|
// Surface a per-turn query embedding so PKB hint search still runs
|
|
600
602
|
// on v2 turns. v1's `retrieveForTurn` produced these as a side effect;
|
|
601
|
-
// the v2 path skips that retrieval, so embed explicitly here.
|
|
603
|
+
// the v2 path skips that retrieval, so embed explicitly here. Match
|
|
604
|
+
// v1's split: dense embeds the combined assistant+user text (short
|
|
605
|
+
// referential follow-ups like "do that one" need the assistant turn
|
|
606
|
+
// for semantic grounding), while sparse uses the user message alone
|
|
607
|
+
// to keep lexical signal focused on what the user actually said.
|
|
608
|
+
const denseQueryText = [assistantLast, userLast]
|
|
609
|
+
.filter((m) => m.length > 0)
|
|
610
|
+
.join("\n\n");
|
|
602
611
|
const perTurnEmbed = await this.computeQueryVectors(
|
|
612
|
+
denseQueryText,
|
|
603
613
|
userLast,
|
|
604
614
|
config,
|
|
605
615
|
signal,
|
|
@@ -697,24 +707,30 @@ export class ConversationGraphMemory {
|
|
|
697
707
|
* static fallback rather than blocking the turn.
|
|
698
708
|
*/
|
|
699
709
|
private async computeQueryVectors(
|
|
700
|
-
|
|
710
|
+
denseText: string,
|
|
711
|
+
sparseText: string,
|
|
701
712
|
config: AssistantConfig,
|
|
702
713
|
signal: AbortSignal,
|
|
703
714
|
): Promise<{ dense?: number[]; sparse?: QdrantSparseVector }> {
|
|
704
|
-
const
|
|
705
|
-
|
|
715
|
+
const trimmedDense = denseText.trim();
|
|
716
|
+
const trimmedSparse = sparseText.trim();
|
|
706
717
|
let dense: number[] | undefined;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
718
|
+
if (trimmedDense.length > 0) {
|
|
719
|
+
try {
|
|
720
|
+
const result = await embedWithRetry(config, [trimmedDense], { signal });
|
|
721
|
+
dense = result.vectors[0];
|
|
722
|
+
} catch (err) {
|
|
723
|
+
log.warn(
|
|
724
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
725
|
+
"Failed to embed query for PKB hints on v2 path",
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
let sparse: QdrantSparseVector | undefined;
|
|
730
|
+
if (trimmedSparse.length > 0) {
|
|
731
|
+
const sparseRaw = generateSparseEmbedding(trimmedSparse);
|
|
732
|
+
sparse = sparseRaw.indices.length > 0 ? sparseRaw : undefined;
|
|
715
733
|
}
|
|
716
|
-
const sparseRaw = generateSparseEmbedding(trimmed);
|
|
717
|
-
const sparse = sparseRaw.indices.length > 0 ? sparseRaw : undefined;
|
|
718
734
|
return { dense, sparse };
|
|
719
735
|
}
|
|
720
736
|
|
|
@@ -20,7 +20,7 @@ const RECALL_DEPTHS = ["fast", "standard", "deep"] as const;
|
|
|
20
20
|
export const graphRecallDefinition: ToolDefinition = {
|
|
21
21
|
name: "recall",
|
|
22
22
|
description:
|
|
23
|
-
'Search local information the moment you feel uncertain. Use recall for memory, past conversations, and workspace files — before you guess, before you ask, before you hedge. Auto-injection is incomplete by design; it surfaces patterns, not the specifics you need to answer well. If you catch yourself reaching for "I think", "I believe", "if I remember", "didn\'t we", "last time" — that\'s the signal. Recall. If
|
|
23
|
+
'Search local information the moment you feel uncertain. Use recall for memory, past conversations, and workspace files — before you guess, before you ask, before you hedge. Auto-injection is incomplete by design; it surfaces patterns, not the specifics you need to answer well. If you catch yourself reaching for "I think", "I believe", "if I remember", "didn\'t we", "last time" — that\'s the signal. Recall. If a turn references someone, a place, a decision, a document, or prior work you should be able to find locally — recall. Call it multiple times per conversation if the turn warrants it. Be specific in your query for best results.',
|
|
24
24
|
input_schema: {
|
|
25
25
|
type: "object",
|
|
26
26
|
properties: {
|
|
@@ -72,7 +72,7 @@ const REMEMBER_DESCRIPTION_DEFAULT =
|
|
|
72
72
|
* something feels worth marking, not because the volume is required.
|
|
73
73
|
*/
|
|
74
74
|
const REMEMBER_DESCRIPTION_RELAXED =
|
|
75
|
-
"Remember anything concrete
|
|
75
|
+
"Remember anything concrete shared in conversation: corrections, plans, decisions, felt moments, names, dates, commitments, preferences. Corrections are the highest priority — call `remember` the same turn the correction lands. You don't have to call this on every turn; a retrospective pass reviews the conversation after each message-count / time interval and saves what you didn't capture. Use judgment: pause and remember when something feels worth marking, not because the volume is required.";
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Return the description that should appear in the `remember` tool
|
|
@@ -115,7 +115,7 @@ export const graphRememberDefinition: ToolDefinition = {
|
|
|
115
115
|
finish_turn: {
|
|
116
116
|
type: "boolean",
|
|
117
117
|
description:
|
|
118
|
-
"When you have nothing else to say and want to
|
|
118
|
+
"When you have nothing else to say and want to yield the turn you MUST set this to true. When true, your turn ends after this tool call. It's critical that you do this in order to avoid unnecessary LLM calls.",
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
121
|
required: ["content"],
|
package/src/memory/indexer.ts
CHANGED
|
@@ -169,32 +169,30 @@ export async function indexMessageNow(
|
|
|
169
169
|
const batchSize = config.extraction.batchSize ?? 10;
|
|
170
170
|
const idleTimeoutMs = config.extraction.idleTimeoutMs ?? 300_000;
|
|
171
171
|
|
|
172
|
+
// Reading config here is best-effort: when it fails we treat v2 as
|
|
173
|
+
// inactive (failing-open to v1) so a config error never silently
|
|
174
|
+
// drops the extraction or summarization paths.
|
|
175
|
+
let triggerConfig: ReturnType<typeof getConfig> | null = null;
|
|
176
|
+
try {
|
|
177
|
+
triggerConfig = getConfig();
|
|
178
|
+
} catch (err) {
|
|
179
|
+
log.debug(
|
|
180
|
+
{ err, conversationId: input.conversationId },
|
|
181
|
+
"Skipping feature-gated extraction triggers: failed to load config",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const v2Config =
|
|
186
|
+
triggerConfig != null && triggerConfig.memory.v2.enabled
|
|
187
|
+
? triggerConfig
|
|
188
|
+
: null;
|
|
189
|
+
|
|
172
190
|
// Recursion guard: skip graph extraction + auto-analysis enqueues
|
|
173
191
|
// when the source conversation is itself an auto-analysis
|
|
174
192
|
// conversation. The analysis agent writes memory directly via tools,
|
|
175
193
|
// so extracting from its reflective musings would double-count and
|
|
176
194
|
// analyzing its own output would loop indefinitely.
|
|
177
|
-
// Summaries still run — they feed the graph retrieval pipeline and
|
|
178
|
-
// are not recursion-prone.
|
|
179
195
|
if (!isAutoAnalysisSource) {
|
|
180
|
-
// Reading config here is best-effort: when it fails we treat v2 as
|
|
181
|
-
// inactive (failing-open to v1) so a config error never silently
|
|
182
|
-
// drops both extraction paths.
|
|
183
|
-
let triggerConfig: ReturnType<typeof getConfig> | null = null;
|
|
184
|
-
try {
|
|
185
|
-
triggerConfig = getConfig();
|
|
186
|
-
} catch (err) {
|
|
187
|
-
log.debug(
|
|
188
|
-
{ err, conversationId: input.conversationId },
|
|
189
|
-
"Skipping feature-gated extraction triggers: failed to load config",
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const v2Config =
|
|
194
|
-
triggerConfig != null && triggerConfig.memory.v2.enabled
|
|
195
|
-
? triggerConfig
|
|
196
|
-
: null;
|
|
197
|
-
|
|
198
196
|
// ── Graph extraction (v1) ───────────────────────────────────────
|
|
199
197
|
// Suppressed when v2 is active — v2 reads memory from buffer.md
|
|
200
198
|
// and concept pages, so the v1 graph would be stale data nobody
|
|
@@ -302,15 +300,22 @@ export async function indexMessageNow(
|
|
|
302
300
|
}
|
|
303
301
|
}
|
|
304
302
|
|
|
305
|
-
// ── Conversation summarization (
|
|
306
|
-
// Summaries feed the graph retrieval pipeline
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
303
|
+
// ── Conversation summarization (v1) ───────────────────────────────
|
|
304
|
+
// Summaries feed the v1 graph retrieval pipeline (fetchRecentSummaries,
|
|
305
|
+
// semantic search). Suppressed when v2 is active — v2 readers (concept
|
|
306
|
+
// pages, activation pipeline) do not consume `memorySummaries`, so the
|
|
307
|
+
// summarization LLM call would produce rows nothing reads. Stale rows
|
|
308
|
+
// from before v2 was enabled are short-circuited at dispatch in
|
|
309
|
+
// jobs-worker.ts. Debounced on the same idle timeout — no threshold
|
|
310
|
+
// trigger needed since summaries compress the whole conversation, not
|
|
311
|
+
// incremental batches.
|
|
312
|
+
if (v2Config == null) {
|
|
313
|
+
upsertDebouncedJob(
|
|
314
|
+
"build_conversation_summary",
|
|
315
|
+
{ conversationId: input.conversationId },
|
|
316
|
+
Date.now() + idleTimeoutMs,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
if (skippedShortSegments > 0) {
|
|
@@ -151,6 +151,8 @@ type MemoryJob = ReturnType<MemoryJobMod["claimMemoryJobs"]>[number];
|
|
|
151
151
|
const { embedConceptPageJob, enqueueEmbedConceptPageJob } =
|
|
152
152
|
await import("../embed-concept-page.js");
|
|
153
153
|
const { writePage } = await import("../../v2/page-store.js");
|
|
154
|
+
const { _resetQdrantBreaker, isQdrantBreakerOpen, withQdrantBreaker } =
|
|
155
|
+
await import("../../qdrant-circuit-breaker.js");
|
|
154
156
|
|
|
155
157
|
// Use a tiny vectorSize so the cache-dim check matches our stub vector.
|
|
156
158
|
const TEST_CONFIG = {
|
|
@@ -186,6 +188,7 @@ beforeEach(() => {
|
|
|
186
188
|
embedWithBackendCalls.length = 0;
|
|
187
189
|
upsertCalls.length = 0;
|
|
188
190
|
deleteCalls.length = 0;
|
|
191
|
+
_resetQdrantBreaker();
|
|
189
192
|
});
|
|
190
193
|
|
|
191
194
|
afterEach(() => {
|
|
@@ -436,6 +439,76 @@ describe("embedConceptPageJob — defensive", () => {
|
|
|
436
439
|
});
|
|
437
440
|
});
|
|
438
441
|
|
|
442
|
+
describe("embedConceptPageJob — Qdrant breaker integration", () => {
|
|
443
|
+
test("half-open probe success closes the breaker so embed catch-up unthrottles", async () => {
|
|
444
|
+
// Trip the breaker by recording 5 consecutive Qdrant failures. Without
|
|
445
|
+
// this fix, `embed_concept_page` bypassed the breaker entirely — winning
|
|
446
|
+
// the half-open probe slot did not transition state back to closed and
|
|
447
|
+
// the embed lane stayed throttled at one job per tick indefinitely.
|
|
448
|
+
for (let i = 0; i < 5; i++) {
|
|
449
|
+
try {
|
|
450
|
+
await withQdrantBreaker(async () => {
|
|
451
|
+
throw new Error("simulated qdrant failure");
|
|
452
|
+
});
|
|
453
|
+
} catch {
|
|
454
|
+
// expected
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
expect(isQdrantBreakerOpen()).toBe(true);
|
|
458
|
+
|
|
459
|
+
// Advance time past the 30s cooldown so the next breaker call transitions
|
|
460
|
+
// open → half-open and allows the probe through.
|
|
461
|
+
const originalNow = Date.now;
|
|
462
|
+
Date.now = () => originalNow() + 60_000;
|
|
463
|
+
try {
|
|
464
|
+
await writePage(tmpWorkspace, {
|
|
465
|
+
slug: "probe-success",
|
|
466
|
+
frontmatter: { edges: [], ref_files: [], ref_urls: [] },
|
|
467
|
+
body: "Probe body.\n",
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await embedConceptPageJob(
|
|
471
|
+
makeJob({ slug: "probe-success" }),
|
|
472
|
+
TEST_CONFIG,
|
|
473
|
+
);
|
|
474
|
+
} finally {
|
|
475
|
+
Date.now = originalNow;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
expect(upsertCalls).toHaveLength(1);
|
|
479
|
+
// Probe succeeded → breaker should now be closed (not open, not
|
|
480
|
+
// half-open), restoring full embed-lane concurrency.
|
|
481
|
+
expect(isQdrantBreakerOpen()).toBe(false);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("half-open probe success on the delete path also closes the breaker", async () => {
|
|
485
|
+
// Same flow as above but exercising the missing-page branch — both v2
|
|
486
|
+
// Qdrant calls (`upsert` and `delete`) must close the breaker on success.
|
|
487
|
+
for (let i = 0; i < 5; i++) {
|
|
488
|
+
try {
|
|
489
|
+
await withQdrantBreaker(async () => {
|
|
490
|
+
throw new Error("simulated qdrant failure");
|
|
491
|
+
});
|
|
492
|
+
} catch {
|
|
493
|
+
// expected
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
expect(isQdrantBreakerOpen()).toBe(true);
|
|
497
|
+
|
|
498
|
+
const originalNow = Date.now;
|
|
499
|
+
Date.now = () => originalNow() + 60_000;
|
|
500
|
+
try {
|
|
501
|
+
// No `writePage` — the handler takes the delete branch.
|
|
502
|
+
await embedConceptPageJob(makeJob({ slug: "missing-slug" }), TEST_CONFIG);
|
|
503
|
+
} finally {
|
|
504
|
+
Date.now = originalNow;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
expect(deleteCalls).toEqual(["missing-slug"]);
|
|
508
|
+
expect(isQdrantBreakerOpen()).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
439
512
|
describe("enqueueEmbedConceptPageJob", () => {
|
|
440
513
|
test("enqueues a pending embed_concept_page job with the slug payload", () => {
|
|
441
514
|
const id = enqueueEmbedConceptPageJob({ slug: "alice-prefers-vs-code" });
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { embeddingInputContentHash } from "../embedding-types.js";
|
|
35
35
|
import { asString, blobToVector, vectorToBlob } from "../job-utils.js";
|
|
36
36
|
import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
|
|
37
|
+
import { withQdrantBreaker } from "../qdrant-circuit-breaker.js";
|
|
37
38
|
import { memoryEmbeddings } from "../schema.js";
|
|
38
39
|
import { readPage } from "../v2/page-store.js";
|
|
39
40
|
import {
|
|
@@ -81,7 +82,10 @@ export async function embedConceptPageJob(
|
|
|
81
82
|
if (!page) {
|
|
82
83
|
// Page was deleted out from under us — clean up the prior embedding so
|
|
83
84
|
// retrieval no longer surfaces a slug whose disk-side prose is gone.
|
|
84
|
-
|
|
85
|
+
// Route through the Qdrant breaker so success on the half-open probe
|
|
86
|
+
// slot transitions the breaker back to closed and unthrottles embed
|
|
87
|
+
// catch-up.
|
|
88
|
+
await withQdrantBreaker(() => deleteConceptPageEmbedding(slug));
|
|
85
89
|
return;
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -280,16 +284,21 @@ export async function embedConceptPageJob(
|
|
|
280
284
|
? await applyCorrectionIfCalibrated(summaryDense, writeProvider, writeModel)
|
|
281
285
|
: undefined;
|
|
282
286
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
287
|
+
// Route through the Qdrant breaker so a probe-slot success transitions the
|
|
288
|
+
// breaker back to closed; without this wrapper the embed lane stays
|
|
289
|
+
// throttled at one job per tick indefinitely after a half-open success.
|
|
290
|
+
await withQdrantBreaker(() =>
|
|
291
|
+
upsertConceptPageEmbedding({
|
|
292
|
+
slug,
|
|
293
|
+
dense: correctedDense,
|
|
294
|
+
sparse,
|
|
295
|
+
summary:
|
|
296
|
+
correctedSummaryDense && summarySparse
|
|
297
|
+
? { dense: correctedSummaryDense, sparse: summarySparse }
|
|
298
|
+
: undefined,
|
|
299
|
+
updatedAt: now,
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
293
302
|
}
|
|
294
303
|
|
|
295
304
|
/** SQLite cache row shape returned by `readEmbeddingCache`. */
|
|
@@ -95,7 +95,6 @@ const V1_QDRANT_JOB_TYPES = new Set<MemoryJobType>([
|
|
|
95
95
|
"embed_attachment",
|
|
96
96
|
"embed_graph_node",
|
|
97
97
|
"embed_pkb_file",
|
|
98
|
-
"graph_trigger_embed",
|
|
99
98
|
"rebuild_index",
|
|
100
99
|
"delete_qdrant_vectors",
|
|
101
100
|
]);
|
|
@@ -525,6 +524,12 @@ async function processJob(
|
|
|
525
524
|
pruneOldTraceEventsJob(job, config);
|
|
526
525
|
return;
|
|
527
526
|
case "build_conversation_summary":
|
|
527
|
+
// Stale rows enqueued before v2 was enabled must not consume the
|
|
528
|
+
// `conversationSummarization` LLM budget — v2 readers do not consume
|
|
529
|
+
// `memorySummaries`, mirroring the `graph_extract` gate below.
|
|
530
|
+
if (config.memory.v2.enabled) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
528
533
|
await buildConversationSummaryJob(job, config);
|
|
529
534
|
return;
|
|
530
535
|
case "backfill":
|
|
@@ -172,14 +172,21 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
|
|
|
172
172
|
private async selectByMessageIds(ids: string[]): Promise<LogRow[]> {
|
|
173
173
|
if (ids.length === 0) return [];
|
|
174
174
|
const aid = await this.assistantId();
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
175
|
+
// Bind each id as its own {id_N:String} placeholder. The IDs ultimately
|
|
176
|
+
// come from a caller-supplied path parameter — `getAssistantMessageIdsInTurn`
|
|
177
|
+
// passes the input straight through when the message lookup misses — so
|
|
178
|
+
// inline literal building (even with quote-doubling) is unsafe: ClickHouse
|
|
179
|
+
// honors `\'` as an escaped quote inside string literals, letting a
|
|
180
|
+
// backslash-suffixed id break out of the IN clause and bypass the
|
|
181
|
+
// `assistant_id` scope filter. Type-bound parameters carry value, not
|
|
182
|
+
// syntax, regardless of content.
|
|
183
|
+
const params: Record<string, string> = { assistant_id: aid };
|
|
184
|
+
const placeholders: string[] = [];
|
|
185
|
+
for (let i = 0; i < ids.length; i++) {
|
|
186
|
+
const key = `id_${i}`;
|
|
187
|
+
params[key] = ids[i]!;
|
|
188
|
+
placeholders.push(`{${key}:String}`);
|
|
189
|
+
}
|
|
183
190
|
const sql = `SELECT
|
|
184
191
|
id,
|
|
185
192
|
conversation_id,
|
|
@@ -190,11 +197,11 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
|
|
|
190
197
|
toUnixTimestamp64Milli(created_at) AS created_at
|
|
191
198
|
FROM ${this.tableRef()}
|
|
192
199
|
WHERE assistant_id = {assistant_id:String}
|
|
193
|
-
AND message_id IN ${
|
|
200
|
+
AND message_id IN (${placeholders.join(",")})
|
|
194
201
|
ORDER BY created_at ASC, id ASC
|
|
195
202
|
LIMIT 1 BY id
|
|
196
203
|
FORMAT JSONEachRow`;
|
|
197
|
-
const rows = await this.exec(sql,
|
|
204
|
+
const rows = await this.exec(sql, params);
|
|
198
205
|
return rows.map((r) => this.toLogRow(r));
|
|
199
206
|
}
|
|
200
207
|
|
|
@@ -12,16 +12,13 @@
|
|
|
12
12
|
* only sees rows the mirror cron has flushed). See
|
|
13
13
|
* `llm-request-log-source-clickhouse.ts`.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Implementations are cheap to instantiate, so there's no module-level
|
|
16
|
+
* cache — each call resolves config fresh and constructs a new instance.
|
|
17
|
+
* Config edits take effect on the next request without an invalidation hook.
|
|
18
18
|
*/
|
|
19
19
|
import { getConfig } from "../config/loader.js";
|
|
20
|
-
import { getLogger } from "../util/logger.js";
|
|
21
20
|
import type { LogRow } from "./llm-request-log-store.js";
|
|
22
21
|
|
|
23
|
-
const log = getLogger("llm-request-log-source");
|
|
24
|
-
|
|
25
22
|
export interface LlmRequestLogSource {
|
|
26
23
|
/** Fetch a single log row by its primary key. Returns null if not found. */
|
|
27
24
|
getRequestLogById(logId: string): Promise<LogRow | null>;
|
|
@@ -36,62 +33,32 @@ export interface LlmRequestLogSource {
|
|
|
36
33
|
getRequestLogsByMessageId(messageId: string): Promise<LogRow[]>;
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
let cached: LlmRequestLogSource | null = null;
|
|
40
|
-
let cachedKind: "local" | "clickhouse" | null = null;
|
|
41
|
-
|
|
42
36
|
/**
|
|
43
|
-
* Return the
|
|
37
|
+
* Return the configured LLM request log source.
|
|
44
38
|
*
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* everything that transitively imports it, including `config-watcher`)
|
|
51
|
-
* free of `llm-request-log-store → conversation-crud → indexer → embedding-backend`,
|
|
39
|
+
* The factory is async because both implementations are loaded via
|
|
40
|
+
* dynamic `import()` on first use. This is deliberate: it keeps the
|
|
41
|
+
* static module graph for `llm-request-log-source.ts` (and for
|
|
42
|
+
* everything that transitively imports it) free of
|
|
43
|
+
* `llm-request-log-store → conversation-crud → indexer → embedding-backend`,
|
|
52
44
|
* which would otherwise force test files that stub `embedding-backend.js`
|
|
53
|
-
* to also stub every export `indexer.ts` reaches for.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
45
|
+
* to also stub every export `indexer.ts` reaches for.
|
|
46
|
+
*
|
|
47
|
+
* Callers must `await` both the factory and the source methods.
|
|
56
48
|
*/
|
|
57
49
|
export async function getLlmRequestLogSource(): Promise<LlmRequestLogSource> {
|
|
58
|
-
if (cached) return cached;
|
|
59
|
-
|
|
60
50
|
const config = getConfig();
|
|
61
|
-
const
|
|
51
|
+
const cfg = config.llmRequestLogs ?? { readSource: "local" as const };
|
|
62
52
|
|
|
63
|
-
if (
|
|
53
|
+
if (cfg.readSource === "clickhouse") {
|
|
64
54
|
const { ClickHouseLlmRequestLogSource } = await import(
|
|
65
55
|
"./llm-request-log-source-clickhouse.js"
|
|
66
56
|
);
|
|
67
|
-
|
|
68
|
-
cachedKind = "clickhouse";
|
|
69
|
-
log.info(
|
|
70
|
-
{ table: config.llmRequestLogs.clickhouse.table },
|
|
71
|
-
"Using ClickHouse for LLM request log reads",
|
|
72
|
-
);
|
|
73
|
-
} else {
|
|
74
|
-
const { LocalLlmRequestLogSource } = await import(
|
|
75
|
-
"./llm-request-log-source-local.js"
|
|
76
|
-
);
|
|
77
|
-
cached = new LocalLlmRequestLogSource();
|
|
78
|
-
cachedKind = "local";
|
|
57
|
+
return new ClickHouseLlmRequestLogSource(cfg.clickhouse);
|
|
79
58
|
}
|
|
80
59
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* Drop the cached source so the next `getLlmRequestLogSource()` call
|
|
86
|
-
* resolves fresh from config. Called on workspace config reload.
|
|
87
|
-
*/
|
|
88
|
-
export function invalidateLlmRequestLogSourceCache(): void {
|
|
89
|
-
if (cached !== null) {
|
|
90
|
-
log.debug(
|
|
91
|
-
{ previousKind: cachedKind },
|
|
92
|
-
"Invalidating LLM request log source cache",
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
cached = null;
|
|
96
|
-
cachedKind = null;
|
|
60
|
+
const { LocalLlmRequestLogSource } = await import(
|
|
61
|
+
"./llm-request-log-source-local.js"
|
|
62
|
+
);
|
|
63
|
+
return new LocalLlmRequestLogSource();
|
|
97
64
|
}
|