@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
|
@@ -23,6 +23,7 @@ import type { AssistantConfig } from "../config/schema.js";
|
|
|
23
23
|
|
|
24
24
|
interface TestState {
|
|
25
25
|
seedCallCount: number;
|
|
26
|
+
seedCallOpts: Array<{ throwOnError?: boolean } | undefined>;
|
|
26
27
|
seedShouldReject: Error | null;
|
|
27
28
|
warnCalls: Array<{ obj: unknown; msg: unknown }>;
|
|
28
29
|
infoCalls: Array<{ obj: unknown; msg: unknown }>;
|
|
@@ -34,10 +35,14 @@ interface TestState {
|
|
|
34
35
|
listPagesResult: string[];
|
|
35
36
|
enqueueCalls: Array<{ type: string; payload: Record<string, unknown> }>;
|
|
36
37
|
clearSentinelCallCount: number;
|
|
38
|
+
// BM25 corpus-stats rebuild + reseed mocks.
|
|
39
|
+
corpusStatsBuildCount: number;
|
|
40
|
+
corpusStatsThrows: Error | null;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const state: TestState = {
|
|
40
44
|
seedCallCount: 0,
|
|
45
|
+
seedCallOpts: [],
|
|
41
46
|
seedShouldReject: null,
|
|
42
47
|
warnCalls: [],
|
|
43
48
|
infoCalls: [],
|
|
@@ -48,6 +53,8 @@ const state: TestState = {
|
|
|
48
53
|
listPagesResult: [],
|
|
49
54
|
enqueueCalls: [],
|
|
50
55
|
clearSentinelCallCount: 0,
|
|
56
|
+
corpusStatsBuildCount: 0,
|
|
57
|
+
corpusStatsThrows: null,
|
|
51
58
|
};
|
|
52
59
|
|
|
53
60
|
// ---------------------------------------------------------------------------
|
|
@@ -55,8 +62,11 @@ const state: TestState = {
|
|
|
55
62
|
// ---------------------------------------------------------------------------
|
|
56
63
|
|
|
57
64
|
mock.module("../memory/v2/skill-store.js", () => ({
|
|
58
|
-
seedV2SkillEntries: async (
|
|
65
|
+
seedV2SkillEntries: async (opts?: {
|
|
66
|
+
throwOnError?: boolean;
|
|
67
|
+
}): Promise<void> => {
|
|
59
68
|
state.seedCallCount += 1;
|
|
69
|
+
state.seedCallOpts.push(opts);
|
|
60
70
|
if (state.seedShouldReject) throw state.seedShouldReject;
|
|
61
71
|
},
|
|
62
72
|
}));
|
|
@@ -81,6 +91,13 @@ mock.module("../memory/v2/page-store.js", () => ({
|
|
|
81
91
|
state.listPagesResult.length > 0,
|
|
82
92
|
}));
|
|
83
93
|
|
|
94
|
+
mock.module("../memory/v2/sparse-bm25.js", () => ({
|
|
95
|
+
rebuildConceptPageCorpusStats: async (): Promise<void> => {
|
|
96
|
+
state.corpusStatsBuildCount += 1;
|
|
97
|
+
if (state.corpusStatsThrows) throw state.corpusStatsThrows;
|
|
98
|
+
},
|
|
99
|
+
}));
|
|
100
|
+
|
|
84
101
|
mock.module("../memory/jobs-store.js", () => ({
|
|
85
102
|
enqueueMemoryJob: (
|
|
86
103
|
type: string,
|
|
@@ -113,8 +130,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
113
130
|
}),
|
|
114
131
|
}));
|
|
115
132
|
|
|
116
|
-
const {
|
|
117
|
-
|
|
133
|
+
const {
|
|
134
|
+
maybeSeedMemoryV2Skills,
|
|
135
|
+
maybeRebuildMemoryV2Concepts,
|
|
136
|
+
rebuildBm25CorpusStatsAndReseedSkills,
|
|
137
|
+
} = await import("../daemon/memory-v2-startup.js");
|
|
118
138
|
|
|
119
139
|
// ---------------------------------------------------------------------------
|
|
120
140
|
// Test fixtures
|
|
@@ -149,6 +169,7 @@ async function flushMicrotasks(): Promise<void> {
|
|
|
149
169
|
|
|
150
170
|
function resetState(): void {
|
|
151
171
|
state.seedCallCount = 0;
|
|
172
|
+
state.seedCallOpts = [];
|
|
152
173
|
state.seedShouldReject = null;
|
|
153
174
|
state.warnCalls = [];
|
|
154
175
|
state.infoCalls = [];
|
|
@@ -159,6 +180,8 @@ function resetState(): void {
|
|
|
159
180
|
state.listPagesResult = [];
|
|
160
181
|
state.enqueueCalls = [];
|
|
161
182
|
state.clearSentinelCallCount = 0;
|
|
183
|
+
state.corpusStatsBuildCount = 0;
|
|
184
|
+
state.corpusStatsThrows = null;
|
|
162
185
|
}
|
|
163
186
|
|
|
164
187
|
describe("maybeSeedMemoryV2Skills (daemon startup gate)", () => {
|
|
@@ -274,3 +297,69 @@ describe("maybeRebuildMemoryV2Concepts (daemon startup gate)", () => {
|
|
|
274
297
|
);
|
|
275
298
|
});
|
|
276
299
|
});
|
|
300
|
+
|
|
301
|
+
describe("rebuildBm25CorpusStatsAndReseedSkills", () => {
|
|
302
|
+
beforeEach(resetState);
|
|
303
|
+
|
|
304
|
+
test("builds corpus stats then re-seeds skills when v2 is enabled", async () => {
|
|
305
|
+
await rebuildBm25CorpusStatsAndReseedSkills(makeConfig(true));
|
|
306
|
+
|
|
307
|
+
expect(state.corpusStatsBuildCount).toBe(1);
|
|
308
|
+
expect(state.seedCallCount).toBe(1);
|
|
309
|
+
// Must pass throwOnError: true so a swallowed internal seed failure
|
|
310
|
+
// cannot trip the unconditional success log in the caller.
|
|
311
|
+
expect(state.seedCallOpts[0]).toEqual({ throwOnError: true });
|
|
312
|
+
expect(state.warnCalls).toHaveLength(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("builds corpus stats but skips skill reseed when v2 is disabled", async () => {
|
|
316
|
+
await rebuildBm25CorpusStatsAndReseedSkills(makeConfig(false));
|
|
317
|
+
|
|
318
|
+
expect(state.corpusStatsBuildCount).toBe(1);
|
|
319
|
+
expect(state.seedCallCount).toBe(0);
|
|
320
|
+
expect(state.warnCalls).toHaveLength(0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("skips the reseed and logs a warning when the corpus-stats build throws", async () => {
|
|
324
|
+
state.corpusStatsThrows = new Error("listPages failed");
|
|
325
|
+
|
|
326
|
+
// Must not throw — fire-and-forget startup must not block.
|
|
327
|
+
let thrown: unknown = null;
|
|
328
|
+
try {
|
|
329
|
+
await rebuildBm25CorpusStatsAndReseedSkills(makeConfig(true));
|
|
330
|
+
} catch (err) {
|
|
331
|
+
thrown = err;
|
|
332
|
+
}
|
|
333
|
+
expect(thrown).toBeNull();
|
|
334
|
+
|
|
335
|
+
expect(state.corpusStatsBuildCount).toBe(1);
|
|
336
|
+
expect(state.seedCallCount).toBe(0);
|
|
337
|
+
const corpusWarn = state.warnCalls.find(
|
|
338
|
+
(w) =>
|
|
339
|
+
typeof w.msg === "string" &&
|
|
340
|
+
w.msg.includes("BM25 corpus-stats rebuild failed"),
|
|
341
|
+
);
|
|
342
|
+
expect(corpusWarn).toBeTruthy();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("swallows skill-reseed rejections after a successful corpus-stats build", async () => {
|
|
346
|
+
state.seedShouldReject = new Error("reseed failed");
|
|
347
|
+
|
|
348
|
+
let thrown: unknown = null;
|
|
349
|
+
try {
|
|
350
|
+
await rebuildBm25CorpusStatsAndReseedSkills(makeConfig(true));
|
|
351
|
+
} catch (err) {
|
|
352
|
+
thrown = err;
|
|
353
|
+
}
|
|
354
|
+
expect(thrown).toBeNull();
|
|
355
|
+
|
|
356
|
+
expect(state.corpusStatsBuildCount).toBe(1);
|
|
357
|
+
expect(state.seedCallCount).toBe(1);
|
|
358
|
+
const reseedWarn = state.warnCalls.find(
|
|
359
|
+
(w) =>
|
|
360
|
+
typeof w.msg === "string" &&
|
|
361
|
+
w.msg.includes("Failed to re-seed v2 skill entries"),
|
|
362
|
+
);
|
|
363
|
+
expect(reseedWarn).toBeTruthy();
|
|
364
|
+
});
|
|
365
|
+
});
|
|
@@ -25,6 +25,10 @@ mock.module("../config/loader.js", () => ({
|
|
|
25
25
|
provider: "test",
|
|
26
26
|
memory: { enabled: false },
|
|
27
27
|
rateLimit: { maxRequestsPerMinute: 0 },
|
|
28
|
+
slack: {
|
|
29
|
+
teamId: "T123",
|
|
30
|
+
teamUrl: "https://example.slack.com/",
|
|
31
|
+
},
|
|
28
32
|
}),
|
|
29
33
|
}));
|
|
30
34
|
|
|
@@ -32,6 +36,7 @@ import { createConversation } from "../memory/conversation-crud.js";
|
|
|
32
36
|
import { getDb } from "../memory/db-connection.js";
|
|
33
37
|
import { initializeDb } from "../memory/db-init.js";
|
|
34
38
|
import { messages } from "../memory/schema.js";
|
|
39
|
+
import { writeSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
35
40
|
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
36
41
|
import { BadRequestError } from "../runtime/routes/errors.js";
|
|
37
42
|
|
|
@@ -82,6 +87,13 @@ interface MessagePayload {
|
|
|
82
87
|
role: string;
|
|
83
88
|
content: string;
|
|
84
89
|
timestamp: string;
|
|
90
|
+
slackMessage?: {
|
|
91
|
+
channelId: string;
|
|
92
|
+
channelTs: string;
|
|
93
|
+
threadTs?: string;
|
|
94
|
+
messageLink?: { appUrl?: string; webUrl?: string };
|
|
95
|
+
threadLink?: { appUrl?: string; webUrl?: string };
|
|
96
|
+
};
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
interface ListResponse {
|
|
@@ -194,6 +206,49 @@ describe("handleListMessages page=latest", () => {
|
|
|
194
206
|
expect(body.oldestMessageId).toBeNull();
|
|
195
207
|
});
|
|
196
208
|
|
|
209
|
+
test("messages expose Slack message and thread links from slackMeta", () => {
|
|
210
|
+
const conv = createConversation();
|
|
211
|
+
const db = getDb();
|
|
212
|
+
db.insert(messages)
|
|
213
|
+
.values({
|
|
214
|
+
id: "msg-slack",
|
|
215
|
+
conversationId: conv.id,
|
|
216
|
+
role: "user",
|
|
217
|
+
content: JSON.stringify([{ type: "text", text: "Slack reply" }]),
|
|
218
|
+
metadata: JSON.stringify({
|
|
219
|
+
slackMeta: writeSlackMetadata({
|
|
220
|
+
source: "slack",
|
|
221
|
+
channelId: "C123ABCDEF",
|
|
222
|
+
channelTs: "1710000000.000200",
|
|
223
|
+
threadTs: "1710000000.000100",
|
|
224
|
+
eventKind: "message",
|
|
225
|
+
}),
|
|
226
|
+
}),
|
|
227
|
+
createdAt: 1,
|
|
228
|
+
})
|
|
229
|
+
.run();
|
|
230
|
+
|
|
231
|
+
const body = callList({ conversationId: conv.id, page: "latest" });
|
|
232
|
+
|
|
233
|
+
expect(body.messages[0].slackMessage).toEqual({
|
|
234
|
+
channelId: "C123ABCDEF",
|
|
235
|
+
channelTs: "1710000000.000200",
|
|
236
|
+
threadTs: "1710000000.000100",
|
|
237
|
+
messageLink: {
|
|
238
|
+
appUrl:
|
|
239
|
+
"slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000200",
|
|
240
|
+
webUrl:
|
|
241
|
+
"https://example.slack.com/archives/C123ABCDEF/p1710000000000200",
|
|
242
|
+
},
|
|
243
|
+
threadLink: {
|
|
244
|
+
appUrl:
|
|
245
|
+
"slack://channel?team=T123&id=C123ABCDEF&message=1710000000.000100",
|
|
246
|
+
webUrl:
|
|
247
|
+
"https://example.slack.com/archives/C123ABCDEF/p1710000000000100",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
197
252
|
test("page=latest on unresolved conversationKey returns null metadata contract", () => {
|
|
198
253
|
const body = callList({
|
|
199
254
|
conversationKey: "no-such-key",
|
|
@@ -157,7 +157,6 @@ describe("llmCall pipeline", () => {
|
|
|
157
157
|
manifest: {
|
|
158
158
|
name: "spy-llm",
|
|
159
159
|
version: "0.0.1",
|
|
160
|
-
requires: { pluginRuntime: "v1" },
|
|
161
160
|
},
|
|
162
161
|
middleware: {
|
|
163
162
|
llmCall: async (args, next, _ctx) => {
|
|
@@ -209,7 +208,6 @@ describe("llmCall pipeline", () => {
|
|
|
209
208
|
manifest: {
|
|
210
209
|
name: "spy-llm-after-default",
|
|
211
210
|
version: "0.0.1",
|
|
212
|
-
requires: { pluginRuntime: "v1" },
|
|
213
211
|
},
|
|
214
212
|
middleware: {
|
|
215
213
|
llmCall: async (args, next, _ctx) => {
|
|
@@ -255,7 +253,6 @@ describe("llmCall pipeline", () => {
|
|
|
255
253
|
manifest: {
|
|
256
254
|
name: "short-circuit-llm",
|
|
257
255
|
version: "0.0.1",
|
|
258
|
-
requires: { pluginRuntime: "v1" },
|
|
259
256
|
},
|
|
260
257
|
middleware: { llmCall: shortCircuit },
|
|
261
258
|
};
|
|
@@ -2,8 +2,8 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
|
-
import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
|
|
6
5
|
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
6
|
+
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
|
|
7
7
|
import { resolvePricing, resolvePricingForUsage } from "../util/pricing.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -29,6 +29,15 @@ function getRepoRoot(): string {
|
|
|
29
29
|
return join(process.cwd(), "..");
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const META_JSON_PATH = join(getRepoRoot(), "meta", "llm-provider-catalog.json");
|
|
33
|
+
const SWIFTPM_MIRROR_PATH = join(
|
|
34
|
+
getRepoRoot(),
|
|
35
|
+
"clients",
|
|
36
|
+
"shared",
|
|
37
|
+
"Resources",
|
|
38
|
+
"llm-provider-catalog.json",
|
|
39
|
+
);
|
|
40
|
+
|
|
32
41
|
interface ClientCatalogCredentialsGuide {
|
|
33
42
|
description: string;
|
|
34
43
|
url: string;
|
|
@@ -71,7 +80,7 @@ interface ClientCatalogEntry {
|
|
|
71
80
|
envVar?: string;
|
|
72
81
|
apiKeyPlaceholder?: string;
|
|
73
82
|
credentialsGuide?: ClientCatalogCredentialsGuide;
|
|
74
|
-
|
|
83
|
+
supportsPlatformAuth?: boolean;
|
|
75
84
|
defaultModel: string;
|
|
76
85
|
models: ClientCatalogModel[];
|
|
77
86
|
}
|
|
@@ -82,8 +91,7 @@ interface ClientCatalog {
|
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
function loadClientCatalog(): ClientCatalog {
|
|
85
|
-
const
|
|
86
|
-
const raw = readFileSync(catalogPath, "utf-8");
|
|
94
|
+
const raw = readFileSync(META_JSON_PATH, "utf-8");
|
|
87
95
|
return JSON.parse(raw);
|
|
88
96
|
}
|
|
89
97
|
|
|
@@ -124,8 +132,8 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
124
132
|
expect(clientEntry.setupHint).toBe(daemonEntry.setupHint);
|
|
125
133
|
expect(clientEntry.envVar).toBe(daemonEntry.envVar);
|
|
126
134
|
expect(clientEntry.apiKeyPlaceholder).toBe(daemonEntry.apiKeyPlaceholder);
|
|
127
|
-
expect(clientEntry.
|
|
128
|
-
daemonEntry.
|
|
135
|
+
expect(clientEntry.supportsPlatformAuth).toBe(
|
|
136
|
+
daemonEntry.supportsPlatformAuth,
|
|
129
137
|
);
|
|
130
138
|
expect(clientEntry.credentialsGuide).toEqual(
|
|
131
139
|
daemonEntry.credentialsGuide,
|
|
@@ -134,16 +142,16 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
134
142
|
}
|
|
135
143
|
});
|
|
136
144
|
|
|
137
|
-
test("
|
|
138
|
-
// The catalog field is derived from
|
|
145
|
+
test("supportsPlatformAuth mirrors PLATFORM_PROVIDER_META", () => {
|
|
146
|
+
// The catalog field is derived from PLATFORM_PROVIDER_META at build
|
|
139
147
|
// time. This test guards against future hand-edits to model-catalog.ts
|
|
140
|
-
// that would let the two drift. Adding a provider to
|
|
148
|
+
// that would let the two drift. Adding a provider to PLATFORM_PROVIDER_META
|
|
141
149
|
// must auto-propagate; flipping `managed: true` to `false` (or vice
|
|
142
150
|
// versa) must propagate too.
|
|
143
151
|
for (const entry of PROVIDER_CATALOG) {
|
|
144
152
|
const expectedSupportsManagedAuth =
|
|
145
|
-
|
|
146
|
-
expect(entry.
|
|
153
|
+
PLATFORM_PROVIDER_META[entry.id]?.managed === true;
|
|
154
|
+
expect(entry.supportsPlatformAuth).toBe(expectedSupportsManagedAuth);
|
|
147
155
|
}
|
|
148
156
|
});
|
|
149
157
|
|
|
@@ -208,6 +216,26 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
208
216
|
}
|
|
209
217
|
});
|
|
210
218
|
|
|
219
|
+
test("cache pricing rates are positive when defined", () => {
|
|
220
|
+
for (const entry of PROVIDER_CATALOG) {
|
|
221
|
+
for (const model of entry.models) {
|
|
222
|
+
if (!model.pricing) continue;
|
|
223
|
+
if (model.pricing.cacheReadPer1mTokens !== undefined) {
|
|
224
|
+
expect(
|
|
225
|
+
model.pricing.cacheReadPer1mTokens,
|
|
226
|
+
`${entry.id}/${model.id} cacheReadPer1mTokens must be positive`,
|
|
227
|
+
).toBeGreaterThan(0);
|
|
228
|
+
}
|
|
229
|
+
if (model.pricing.cacheWritePer1mTokens !== undefined) {
|
|
230
|
+
expect(
|
|
231
|
+
model.pricing.cacheWritePer1mTokens,
|
|
232
|
+
`${entry.id}/${model.id} cacheWritePer1mTokens must be positive`,
|
|
233
|
+
).toBeGreaterThan(0);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
211
239
|
test("every model default context is capped by its context window", () => {
|
|
212
240
|
const json = loadClientCatalog();
|
|
213
241
|
|
|
@@ -383,8 +411,7 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
383
411
|
`${provider.id}/${model.id} unpriced at tier ${tier.inputTokenThreshold}`,
|
|
384
412
|
).toBe("priced");
|
|
385
413
|
const resolvedRate =
|
|
386
|
-
(result.estimatedCostUsd ?? 0) *
|
|
387
|
-
(TOKENS_PER_MILLION / probeTokens);
|
|
414
|
+
(result.estimatedCostUsd ?? 0) * (TOKENS_PER_MILLION / probeTokens);
|
|
388
415
|
expect(
|
|
389
416
|
resolvedRate,
|
|
390
417
|
`${provider.id}/${model.id} input rate drift at tier ${tier.inputTokenThreshold}`,
|
|
@@ -407,4 +434,19 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
407
434
|
longContextMode: "native-model",
|
|
408
435
|
});
|
|
409
436
|
});
|
|
437
|
+
|
|
438
|
+
// -----------------------------------------------------------------------
|
|
439
|
+
// Mirror byte-equality
|
|
440
|
+
// -----------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
test("SwiftPM mirror is byte-identical to meta/ copy", () => {
|
|
443
|
+
// `sync-llm-catalog.ts` writes both files from the same serializer; this
|
|
444
|
+
// guard catches any case where one copy is regenerated without the other.
|
|
445
|
+
// Byte equality is required because SwiftPM bundles the resource verbatim
|
|
446
|
+
// into `VellumAssistantShared` while the meta/ JSON is consumed as a
|
|
447
|
+
// cross-package artifact (web codegen, etc.).
|
|
448
|
+
const metaBytes = readFileSync(META_JSON_PATH);
|
|
449
|
+
const swiftPmBytes = readFileSync(SWIFTPM_MIRROR_PATH);
|
|
450
|
+
expect(swiftPmBytes.equals(metaBytes)).toBe(true);
|
|
451
|
+
});
|
|
410
452
|
});
|
|
@@ -162,6 +162,40 @@ describe("ClickHouseLlmRequestLogSource", () => {
|
|
|
162
162
|
expect(rows).toEqual([]);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
+
test("getRequestLogsByMessageId binds message ids via parameterized placeholders", async () => {
|
|
166
|
+
// Regression for ATL-537. `getAssistantMessageIdsInTurn` returns the
|
|
167
|
+
// caller-supplied id straight through when the message lookup misses,
|
|
168
|
+
// so the value passed in here is what the IN clause receives. The id
|
|
169
|
+
// ends in a backslash on purpose: with the old inline-literal approach
|
|
170
|
+
// (quote-doubling only), ClickHouse would honor `\'` as an escaped
|
|
171
|
+
// quote and the string literal would break out of the IN clause.
|
|
172
|
+
// The fix binds each id as a typed `{id_N:String}` parameter, so the
|
|
173
|
+
// hostile content flows as data, never syntax.
|
|
174
|
+
const recorder: FakeFetchCall[] = [];
|
|
175
|
+
const malicious = "foo\\";
|
|
176
|
+
const src = makeSource({
|
|
177
|
+
body: "",
|
|
178
|
+
recorder,
|
|
179
|
+
resolveTurnMessageIds: () => [malicious, "msg-b"],
|
|
180
|
+
});
|
|
181
|
+
await src.getRequestLogsByMessageId(malicious);
|
|
182
|
+
expect(recorder).toHaveLength(1);
|
|
183
|
+
const call = recorder[0]!;
|
|
184
|
+
const parsed = new URL(call.url);
|
|
185
|
+
expect(parsed.searchParams.get("param_assistant_id")).toBe(
|
|
186
|
+
"asst-fixture-001",
|
|
187
|
+
);
|
|
188
|
+
expect(parsed.searchParams.get("param_id_0")).toBe(malicious);
|
|
189
|
+
expect(parsed.searchParams.get("param_id_1")).toBe("msg-b");
|
|
190
|
+
const body = String(call.init?.body ?? "");
|
|
191
|
+
expect(body).toContain("message_id IN ({id_0:String},{id_1:String})");
|
|
192
|
+
// No inline single-quoted literal should appear for the IN clause:
|
|
193
|
+
// if any caller-supplied id surfaces unbound in the SQL, that's the
|
|
194
|
+
// injection surface the regression test guards against.
|
|
195
|
+
expect(body).not.toContain(`'${malicious}'`);
|
|
196
|
+
expect(body).not.toContain(`'msg-b'`);
|
|
197
|
+
});
|
|
198
|
+
|
|
165
199
|
test("missing clickhouse:url credential surfaces a clear error", async () => {
|
|
166
200
|
const src = new ClickHouseLlmRequestLogSource(DEFAULT_CONFIG, {
|
|
167
201
|
resolveUrl: async () => null,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll,
|
|
1
|
+
import { afterAll, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
function makeLoggerStub(): Record<string, unknown> {
|
|
4
4
|
const stub: Record<string, unknown> = {};
|
|
@@ -22,19 +22,10 @@ mock.module("../util/logger.js", () => ({
|
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
24
|
// Configurable config stub. Each test sets `currentConfig` before calling
|
|
25
|
-
// the factory. The ClickHouse source is dynamic-imported, so its
|
|
26
|
-
// runs lazily; we don't need to mock conversation-crud here
|
|
27
|
-
// the factory
|
|
28
|
-
const LOCAL_DEFAULT_CONFIG = {
|
|
29
|
-
llmRequestLogs: {
|
|
30
|
-
readSource: "local",
|
|
31
|
-
clickhouse: {
|
|
32
|
-
database: "default",
|
|
33
|
-
table: "llm_request_logs",
|
|
34
|
-
user: "default",
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
};
|
|
25
|
+
// the factory. The ClickHouse source is dynamic-imported, so its
|
|
26
|
+
// constructor runs lazily; we don't need to mock conversation-crud here
|
|
27
|
+
// because none of the factory tests exercise the CH read path.
|
|
28
|
+
const LOCAL_DEFAULT_CONFIG = { llmRequestLogs: { readSource: "local" } };
|
|
38
29
|
let currentConfig: unknown = LOCAL_DEFAULT_CONFIG;
|
|
39
30
|
mock.module("../config/loader.js", () => ({
|
|
40
31
|
getConfig: () => currentConfig,
|
|
@@ -43,32 +34,18 @@ mock.module("../config/loader.js", () => ({
|
|
|
43
34
|
// Bun's `mock.module()` persists process-wide; reset to the safe local
|
|
44
35
|
// default after this file runs so other test files that touch this module
|
|
45
36
|
// (or transitively call `getConfig()` through the factory) don't pick up
|
|
46
|
-
// a stale `readSource=clickhouse` value.
|
|
47
|
-
// cache so the next caller resolves fresh against the reset config.
|
|
37
|
+
// a stale `readSource=clickhouse` value.
|
|
48
38
|
afterAll(() => {
|
|
49
39
|
currentConfig = LOCAL_DEFAULT_CONFIG;
|
|
50
|
-
invalidateLlmRequestLogSourceCache();
|
|
51
40
|
});
|
|
52
41
|
|
|
53
|
-
import {
|
|
54
|
-
getLlmRequestLogSource,
|
|
55
|
-
invalidateLlmRequestLogSourceCache,
|
|
56
|
-
} from "../memory/llm-request-log-source.js";
|
|
42
|
+
import { getLlmRequestLogSource } from "../memory/llm-request-log-source.js";
|
|
57
43
|
import { ClickHouseLlmRequestLogSource } from "../memory/llm-request-log-source-clickhouse.js";
|
|
58
44
|
import { LocalLlmRequestLogSource } from "../memory/llm-request-log-source-local.js";
|
|
59
45
|
|
|
60
46
|
describe("getLlmRequestLogSource factory", () => {
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
invalidateLlmRequestLogSourceCache();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
47
|
test("returns LocalLlmRequestLogSource when readSource is 'local'", async () => {
|
|
66
|
-
currentConfig = {
|
|
67
|
-
llmRequestLogs: {
|
|
68
|
-
readSource: "local",
|
|
69
|
-
clickhouse: { database: "default", table: "llm_request_logs", user: "default" },
|
|
70
|
-
},
|
|
71
|
-
};
|
|
48
|
+
currentConfig = { llmRequestLogs: { readSource: "local" } };
|
|
72
49
|
const src = await getLlmRequestLogSource();
|
|
73
50
|
expect(src).toBeInstanceOf(LocalLlmRequestLogSource);
|
|
74
51
|
});
|
|
@@ -83,42 +60,41 @@ describe("getLlmRequestLogSource factory", () => {
|
|
|
83
60
|
currentConfig = {
|
|
84
61
|
llmRequestLogs: {
|
|
85
62
|
readSource: "clickhouse",
|
|
86
|
-
clickhouse: {
|
|
63
|
+
clickhouse: {
|
|
64
|
+
database: "default",
|
|
65
|
+
table: "llm_request_logs",
|
|
66
|
+
user: "default",
|
|
67
|
+
},
|
|
87
68
|
},
|
|
88
69
|
};
|
|
89
70
|
const src = await getLlmRequestLogSource();
|
|
90
71
|
expect(src).toBeInstanceOf(ClickHouseLlmRequestLogSource);
|
|
91
72
|
});
|
|
92
73
|
|
|
93
|
-
test("
|
|
94
|
-
currentConfig = {
|
|
95
|
-
llmRequestLogs: {
|
|
96
|
-
readSource: "local",
|
|
97
|
-
clickhouse: { database: "default", table: "llm_request_logs", user: "default" },
|
|
98
|
-
},
|
|
99
|
-
};
|
|
74
|
+
test("instantiates a fresh source on every call (no module-level cache)", async () => {
|
|
75
|
+
currentConfig = { llmRequestLogs: { readSource: "local" } };
|
|
100
76
|
const first = await getLlmRequestLogSource();
|
|
101
77
|
const second = await getLlmRequestLogSource();
|
|
102
|
-
expect(second).toBe(first);
|
|
78
|
+
expect(second).not.toBe(first);
|
|
79
|
+
expect(second).toBeInstanceOf(LocalLlmRequestLogSource);
|
|
103
80
|
});
|
|
104
81
|
|
|
105
|
-
test("
|
|
106
|
-
currentConfig = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
const first = await getLlmRequestLogSource();
|
|
113
|
-
invalidateLlmRequestLogSourceCache();
|
|
82
|
+
test("picks up live config changes without an invalidation hook", async () => {
|
|
83
|
+
currentConfig = { llmRequestLogs: { readSource: "local" } };
|
|
84
|
+
const before = await getLlmRequestLogSource();
|
|
85
|
+
expect(before).toBeInstanceOf(LocalLlmRequestLogSource);
|
|
86
|
+
|
|
114
87
|
currentConfig = {
|
|
115
88
|
llmRequestLogs: {
|
|
116
89
|
readSource: "clickhouse",
|
|
117
|
-
clickhouse: {
|
|
90
|
+
clickhouse: {
|
|
91
|
+
database: "default",
|
|
92
|
+
table: "llm_request_logs",
|
|
93
|
+
user: "default",
|
|
94
|
+
},
|
|
118
95
|
},
|
|
119
96
|
};
|
|
120
|
-
const
|
|
121
|
-
expect(
|
|
122
|
-
expect(second).toBeInstanceOf(ClickHouseLlmRequestLogSource);
|
|
97
|
+
const after = await getLlmRequestLogSource();
|
|
98
|
+
expect(after).toBeInstanceOf(ClickHouseLlmRequestLogSource);
|
|
123
99
|
});
|
|
124
100
|
});
|