@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
|
@@ -879,17 +879,17 @@ describe("injectMemoryV2Block", () => {
|
|
|
879
879
|
// Unified pool — skills as `skills/<id>` slugs
|
|
880
880
|
// ---------------------------------------------------------------------------
|
|
881
881
|
|
|
882
|
-
test("renders a
|
|
882
|
+
test("renders a retrieved skills/<id> slug under Skills You Can Use", async () => {
|
|
883
883
|
// No concept-page candidates this turn — the only ANN hit is a skill
|
|
884
884
|
// slug. The render path branches on `skills/` prefix: it pulls the
|
|
885
885
|
// entry from the skill-store cache (mocked) and emits the bullet under
|
|
886
886
|
// the `### Skills You Can Use` subsection.
|
|
887
|
-
stageTurn([{ slug: "skills/
|
|
887
|
+
stageTurn([{ slug: "skills/retrieved-skill", denseScore: 0.9 }]);
|
|
888
888
|
stageSkills([
|
|
889
889
|
{
|
|
890
|
-
id: "
|
|
890
|
+
id: "retrieved-skill",
|
|
891
891
|
content:
|
|
892
|
-
'The "
|
|
892
|
+
'The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills.',
|
|
893
893
|
},
|
|
894
894
|
]);
|
|
895
895
|
|
|
@@ -904,16 +904,18 @@ describe("injectMemoryV2Block", () => {
|
|
|
904
904
|
config: makeConfig(),
|
|
905
905
|
});
|
|
906
906
|
|
|
907
|
-
expect(result.toInject).toEqual(["skills/
|
|
907
|
+
expect(result.toInject).toEqual(["skills/retrieved-skill"]);
|
|
908
908
|
expect(result.block).not.toBeNull();
|
|
909
909
|
expect(result.block).not.toContain("<memory>");
|
|
910
910
|
expect(result.block).not.toContain("</memory>");
|
|
911
911
|
expect(result.block).not.toContain("## What I Remember Right Now");
|
|
912
912
|
expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
'- The "
|
|
913
|
+
const headerIdx = result.block!.indexOf("### Skills You Can Use");
|
|
914
|
+
const skillIdx = result.block!.indexOf(
|
|
915
|
+
'- The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills. → use skill_load to activate',
|
|
916
916
|
);
|
|
917
|
+
expect(headerIdx).toBeGreaterThan(-1);
|
|
918
|
+
expect(skillIdx).toBeGreaterThan(headerIdx);
|
|
917
919
|
});
|
|
918
920
|
|
|
919
921
|
test("renders concept-page sections before the skills subsection in mixed blocks", async () => {
|
|
@@ -1679,6 +1681,56 @@ describe("injectMemoryV2Block", () => {
|
|
|
1679
1681
|
expect(row.concepts).toEqual([]);
|
|
1680
1682
|
});
|
|
1681
1683
|
|
|
1684
|
+
test("flag-on: router-failure path swallows a save() error and returns block:null instead of throwing", async () => {
|
|
1685
|
+
// PR 30176 refactored router-failure handling to delegate to
|
|
1686
|
+
// `finalizeInjection`. That regressed the prior inline log-and-continue
|
|
1687
|
+
// semantics on the router-failure path: a transient SQLite write
|
|
1688
|
+
// throwing during the stub-state save now aborted the whole turn
|
|
1689
|
+
// because `finalizeInjection`'s try/catch re-threw caughtErr at the end.
|
|
1690
|
+
//
|
|
1691
|
+
// This test stages exactly that scenario — router returns
|
|
1692
|
+
// `failureReason: api_error` AND `save()` throws — and asserts the
|
|
1693
|
+
// turn completes with `{ block: null, toInject: [] }` rather than
|
|
1694
|
+
// propagating the SQLite error to `prepareMemory`.
|
|
1695
|
+
routerState.nextResult = {
|
|
1696
|
+
selectedSlugs: [],
|
|
1697
|
+
failureReason: "api_error",
|
|
1698
|
+
};
|
|
1699
|
+
activationStoreState.saveShouldThrow = true;
|
|
1700
|
+
|
|
1701
|
+
let threw: unknown = undefined;
|
|
1702
|
+
let result: Awaited<ReturnType<typeof injectMemoryV2Block>> | undefined;
|
|
1703
|
+
try {
|
|
1704
|
+
result = await injectMemoryV2Block({
|
|
1705
|
+
database: db,
|
|
1706
|
+
conversationId: "conv-router-fail-save-throws",
|
|
1707
|
+
currentTurn: 5,
|
|
1708
|
+
userMessage: "anything",
|
|
1709
|
+
assistantMessage: "ok",
|
|
1710
|
+
nowText: "Now",
|
|
1711
|
+
messageId: "msg-fail-save",
|
|
1712
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1713
|
+
});
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
threw = err;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
expect(threw).toBeUndefined();
|
|
1719
|
+
expect(result).toBeDefined();
|
|
1720
|
+
expect(result!.block).toBeNull();
|
|
1721
|
+
expect(result!.toInject).toEqual([]);
|
|
1722
|
+
|
|
1723
|
+
// Telemetry still flushes with `mode: "errored"` so the failure stays
|
|
1724
|
+
// observable — the same row the inline pre-refactor path emitted.
|
|
1725
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1726
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1727
|
+
mode: string;
|
|
1728
|
+
concepts: unknown[];
|
|
1729
|
+
};
|
|
1730
|
+
expect(row.mode).toBe("errored");
|
|
1731
|
+
expect(row.concepts).toEqual([]);
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1682
1734
|
test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
|
|
1683
1735
|
routerState.nextResult = {
|
|
1684
1736
|
selectedSlugs: [],
|
|
@@ -1904,5 +1956,43 @@ describe("injectMemoryV2Block", () => {
|
|
|
1904
1956
|
const row = telemetryState.recordCalls[0] as { mode: string };
|
|
1905
1957
|
expect(row.mode).toBe("per-turn");
|
|
1906
1958
|
});
|
|
1959
|
+
|
|
1960
|
+
test("flag-on + mode='context-load': router runs (everInjected was cleared by onCompacted so dedupe is a no-op; abstention is accepted as the trade-off)", async () => {
|
|
1961
|
+
// Context-load is the full top-K bootstrap fired after compaction or
|
|
1962
|
+
// a fresh conversation reload. The earlier worry about the router's
|
|
1963
|
+
// `everInjected` dedupe filtering out post-compaction restorations
|
|
1964
|
+
// doesn't apply: `ConversationGraphMemory.onCompacted` calls
|
|
1965
|
+
// `evictCompactedTurnsV2` which empties the list before this code
|
|
1966
|
+
// runs. Router abstention here means no v2 pages this turn — that's
|
|
1967
|
+
// preferable to letting the activation graph pick something arbitrary.
|
|
1968
|
+
routerState.nextResult = {
|
|
1969
|
+
selectedSlugs: ["alice-vscode"],
|
|
1970
|
+
failureReason: null,
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
const result = await injectMemoryV2Block({
|
|
1974
|
+
database: db,
|
|
1975
|
+
conversationId: "conv-context-load-router-on",
|
|
1976
|
+
currentTurn: 1,
|
|
1977
|
+
userMessage: "Tell me about Alice",
|
|
1978
|
+
assistantMessage: "",
|
|
1979
|
+
nowText: "Now",
|
|
1980
|
+
messageId: "msg-1",
|
|
1981
|
+
mode: "context-load",
|
|
1982
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
// Router was called on context-load too.
|
|
1986
|
+
expect(routerState.callCount).toBe(1);
|
|
1987
|
+
|
|
1988
|
+
// Router's picks were rendered.
|
|
1989
|
+
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
1990
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
1991
|
+
|
|
1992
|
+
// Telemetry row reflects the router mode, not the activation mode.
|
|
1993
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1994
|
+
const row = telemetryState.recordCalls[0] as { mode: string };
|
|
1995
|
+
expect(row.mode).toBe("router");
|
|
1996
|
+
});
|
|
1907
1997
|
});
|
|
1908
1998
|
});
|
|
@@ -754,6 +754,93 @@ describe("runMemoryV2Migration", () => {
|
|
|
754
754
|
expect(body).toContain("second body");
|
|
755
755
|
});
|
|
756
756
|
|
|
757
|
+
test("force=true cleanly strips the prior migration block from essentials.md", async () => {
|
|
758
|
+
insertNode(database, {
|
|
759
|
+
content: "Alice prefers VS Code.",
|
|
760
|
+
significance: 0.95,
|
|
761
|
+
});
|
|
762
|
+
await runMemoryV2Migration({
|
|
763
|
+
workspaceDir,
|
|
764
|
+
database,
|
|
765
|
+
provider: buildStubProvider("body"),
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const essentialsPath = join(workspaceDir, "memory", "essentials.md");
|
|
769
|
+
const afterFirst = readFileSync(essentialsPath, "utf-8");
|
|
770
|
+
expect(afterFirst).toContain("<!-- migration:v1-to-v2 -->");
|
|
771
|
+
expect(afterFirst).toContain("<!-- /migration:v1-to-v2 -->");
|
|
772
|
+
|
|
773
|
+
await runMemoryV2Migration({
|
|
774
|
+
workspaceDir,
|
|
775
|
+
database,
|
|
776
|
+
provider: buildStubProvider("body"),
|
|
777
|
+
force: true,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
const afterRerun = readFileSync(essentialsPath, "utf-8");
|
|
781
|
+
// Exactly one migration block — no leftover/duplicated markers.
|
|
782
|
+
expect(afterRerun.match(/<!-- migration:v1-to-v2 -->/g)?.length ?? 0).toBe(
|
|
783
|
+
1,
|
|
784
|
+
);
|
|
785
|
+
expect(
|
|
786
|
+
afterRerun.match(/<!-- \/migration:v1-to-v2 -->/g)?.length ?? 0,
|
|
787
|
+
).toBe(1);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("force=true preserves user-appended content after the migration block", async () => {
|
|
791
|
+
insertNode(database, {
|
|
792
|
+
content: "Alice prefers VS Code.",
|
|
793
|
+
significance: 0.95,
|
|
794
|
+
});
|
|
795
|
+
await runMemoryV2Migration({
|
|
796
|
+
workspaceDir,
|
|
797
|
+
database,
|
|
798
|
+
provider: buildStubProvider("body"),
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const essentialsPath = join(workspaceDir, "memory", "essentials.md");
|
|
802
|
+
const beforeAppend = readFileSync(essentialsPath, "utf-8");
|
|
803
|
+
const userAppended = "\nUser added this after the migration ran.\n";
|
|
804
|
+
writeFileSync(essentialsPath, beforeAppend + userAppended, "utf-8");
|
|
805
|
+
|
|
806
|
+
await runMemoryV2Migration({
|
|
807
|
+
workspaceDir,
|
|
808
|
+
database,
|
|
809
|
+
provider: buildStubProvider("body"),
|
|
810
|
+
force: true,
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const after = readFileSync(essentialsPath, "utf-8");
|
|
814
|
+
expect(after).toContain("User added this after the migration ran.");
|
|
815
|
+
// And exactly one migration envelope remains.
|
|
816
|
+
expect(after.match(/<!-- migration:v1-to-v2 -->/g)?.length ?? 0).toBe(1);
|
|
817
|
+
expect(after.match(/<!-- \/migration:v1-to-v2 -->/g)?.length ?? 0).toBe(1);
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("force=true on legacy (close-marker-less) file strips only up to the next blank line", async () => {
|
|
821
|
+
// Simulate a file written by the prior migration format: opening marker
|
|
822
|
+
// with no closing sentinel, with user-appended content separated by a
|
|
823
|
+
// blank line. The strip should preserve the user content.
|
|
824
|
+
insertNode(database, { content: "Alice prefers VS Code." });
|
|
825
|
+
const essentialsPath = join(workspaceDir, "memory", "essentials.md");
|
|
826
|
+
writeFileSync(
|
|
827
|
+
essentialsPath,
|
|
828
|
+
"<!-- migration:v1-to-v2 -->\nlegacy migrated line\n\nUser-appended legacy note.\n",
|
|
829
|
+
"utf-8",
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
await runMemoryV2Migration({
|
|
833
|
+
workspaceDir,
|
|
834
|
+
database,
|
|
835
|
+
provider: buildStubProvider("body"),
|
|
836
|
+
force: true,
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
const after = readFileSync(essentialsPath, "utf-8");
|
|
840
|
+
expect(after).toContain("User-appended legacy note.");
|
|
841
|
+
expect(after).not.toContain("legacy migrated line");
|
|
842
|
+
});
|
|
843
|
+
|
|
757
844
|
test("clearing the sentinel allows a non-force re-run", async () => {
|
|
758
845
|
insertNode(database, { content: "fact" });
|
|
759
846
|
const provider = buildStubProvider("body");
|
|
@@ -245,6 +245,89 @@ describe("getPageIndex", () => {
|
|
|
245
245
|
const idx = await getPageIndex(workspaceDir);
|
|
246
246
|
expect(idx.bySlug.get("alice")?.summary.length).toBe(200);
|
|
247
247
|
});
|
|
248
|
+
|
|
249
|
+
test("collapses embedded newlines in frontmatter.summary to single spaces", async () => {
|
|
250
|
+
await writePage(
|
|
251
|
+
workspaceDir,
|
|
252
|
+
makePage("alice", { summary: "First line.\nSecond line.\nThird line." }),
|
|
253
|
+
);
|
|
254
|
+
const idx = await getPageIndex(workspaceDir);
|
|
255
|
+
expect(idx.bySlug.get("alice")?.summary).toBe(
|
|
256
|
+
"First line. Second line. Third line.",
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("collapses embedded newlines and runs of whitespace in body fallback", async () => {
|
|
261
|
+
await writePage(
|
|
262
|
+
workspaceDir,
|
|
263
|
+
makePage("alice", {
|
|
264
|
+
body: " Body with\n\nmultiple\tlines\n and spaces. ",
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
const idx = await getPageIndex(workspaceDir);
|
|
268
|
+
expect(idx.bySlug.get("alice")?.summary).toBe(
|
|
269
|
+
"Body with multiple lines and spaces.",
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("normalizes skill-entry content with embedded newlines", async () => {
|
|
274
|
+
skillState.entries = [
|
|
275
|
+
{ id: "browser", content: "Drive a browser.\nSupports multiple tabs." },
|
|
276
|
+
];
|
|
277
|
+
const idx = await getPageIndex(workspaceDir);
|
|
278
|
+
expect(idx.bySlug.get("skills/browser")?.summary).toBe(
|
|
279
|
+
"Drive a browser. Supports multiple tabs.",
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("renders a single line per entry even when summaries contain newlines", async () => {
|
|
284
|
+
await writePage(
|
|
285
|
+
workspaceDir,
|
|
286
|
+
makePage("alice", { summary: "line one\nline two" }),
|
|
287
|
+
);
|
|
288
|
+
const idx = await getPageIndex(workspaceDir);
|
|
289
|
+
// Exactly one trailing newline — the entry itself must not split.
|
|
290
|
+
expect(idx.rendered.split("\n").filter(Boolean).length).toBe(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("drops a user concept page whose slug collides with a seeded skill entry", async () => {
|
|
294
|
+
await writePage(
|
|
295
|
+
workspaceDir,
|
|
296
|
+
makePage("skills/browser", {
|
|
297
|
+
summary: "User-authored page that shadows the skill.",
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
skillState.entries = [{ id: "browser", content: "Seeded skill content." }];
|
|
301
|
+
|
|
302
|
+
const idx = await getPageIndex(workspaceDir);
|
|
303
|
+
// Only the skill entry survives under skills/browser.
|
|
304
|
+
expect(idx.entries.filter((e) => e.slug === "skills/browser").length).toBe(
|
|
305
|
+
1,
|
|
306
|
+
);
|
|
307
|
+
expect(idx.bySlug.get("skills/browser")?.summary).toBe(
|
|
308
|
+
"Seeded skill content.",
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("collision dedupe leaves non-colliding pages and skills intact", async () => {
|
|
313
|
+
await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
|
|
314
|
+
await writePage(
|
|
315
|
+
workspaceDir,
|
|
316
|
+
makePage("skills/browser", { summary: "Shadow page." }),
|
|
317
|
+
);
|
|
318
|
+
skillState.entries = [
|
|
319
|
+
{ id: "browser", content: "Seeded browser." },
|
|
320
|
+
{ id: "calendar", content: "Seeded calendar." },
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const idx = await getPageIndex(workspaceDir);
|
|
324
|
+
expect(idx.entries.map((e) => e.slug)).toEqual([
|
|
325
|
+
"alice",
|
|
326
|
+
"skills/browser",
|
|
327
|
+
"skills/calendar",
|
|
328
|
+
]);
|
|
329
|
+
expect(idx.bySlug.get("skills/browser")?.summary).toBe("Seeded browser.");
|
|
330
|
+
});
|
|
248
331
|
});
|
|
249
332
|
|
|
250
333
|
// ---------------------------------------------------------------------------
|
|
@@ -123,6 +123,39 @@ describe("renderRouterPrompt — page index handling", () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
describe("renderRouterPrompt — replacement-pattern specials", () => {
|
|
127
|
+
// String.prototype.replaceAll interprets `$&`, `$'`, `` $` ``, `$$`, and
|
|
128
|
+
// `$n` in the replacement string as backreferences. LLM-generated page
|
|
129
|
+
// index content can contain literal `$` runs, so the substituter must
|
|
130
|
+
// pass values through unchanged.
|
|
131
|
+
const SPECIALS = "$& and $' and $` and $$ and $1";
|
|
132
|
+
|
|
133
|
+
test.each([
|
|
134
|
+
[
|
|
135
|
+
"pageIndexBlock",
|
|
136
|
+
{ assistantName: "Aria", userName: "Alice", pageIndexBlock: SPECIALS },
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
"assistantName",
|
|
140
|
+
{
|
|
141
|
+
assistantName: SPECIALS,
|
|
142
|
+
userName: "Alice",
|
|
143
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
[
|
|
147
|
+
"userName",
|
|
148
|
+
{
|
|
149
|
+
assistantName: "Aria",
|
|
150
|
+
userName: SPECIALS,
|
|
151
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
])("renders %s with $ specials verbatim", (_, opts) => {
|
|
155
|
+
expect(renderRouterPrompt(opts)).toContain(SPECIALS);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
126
159
|
describe("renderRouterPrompt — determinism & snapshot stability", () => {
|
|
127
160
|
test("returns the same string for the same inputs", () => {
|
|
128
161
|
const opts = {
|
|
@@ -196,7 +229,7 @@ describe("resolveRouterPrompt — override path", () => {
|
|
|
196
229
|
};
|
|
197
230
|
|
|
198
231
|
test("null overridePath returns the bundled prompt verbatim", () => {
|
|
199
|
-
expect(resolveRouterPrompt(null, STD_OPTS)).toEqual(
|
|
232
|
+
expect(resolveRouterPrompt(null, tmpDir, STD_OPTS)).toEqual(
|
|
200
233
|
renderRouterPrompt(STD_OPTS),
|
|
201
234
|
);
|
|
202
235
|
});
|
|
@@ -209,16 +242,35 @@ describe("resolveRouterPrompt — override path", () => {
|
|
|
209
242
|
"utf-8",
|
|
210
243
|
);
|
|
211
244
|
|
|
212
|
-
const out = resolveRouterPrompt(overridePath, STD_OPTS);
|
|
245
|
+
const out = resolveRouterPrompt(overridePath, tmpDir, STD_OPTS);
|
|
213
246
|
expect(out).toContain("Hi Aria, you are routing for Alice.");
|
|
214
247
|
expect(out).toContain(SAMPLE_INDEX);
|
|
215
248
|
expect(out).not.toContain("{{ASSISTANT_NAME}}");
|
|
216
249
|
expect(out).not.toContain("{{PAGE_INDEX}}");
|
|
217
250
|
});
|
|
218
251
|
|
|
252
|
+
test("relative override path is resolved under the passed workspaceDir, not the default workspace", () => {
|
|
253
|
+
// Write the override into the per-test temp dir, which acts as a
|
|
254
|
+
// non-default workspace. The configured path is purely relative so the
|
|
255
|
+
// loader must resolve it against the supplied workspaceDir — if it
|
|
256
|
+
// resolved against the process-wide default workspace instead, the file
|
|
257
|
+
// wouldn't be found and the bundled prompt would be returned.
|
|
258
|
+
const relativeName = "router-override.md";
|
|
259
|
+
writeFileSync(
|
|
260
|
+
join(tmpDir, relativeName),
|
|
261
|
+
"Routed via {{ASSISTANT_NAME}} for {{USER_NAME}} :: {{PAGE_INDEX}}",
|
|
262
|
+
"utf-8",
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const out = resolveRouterPrompt(relativeName, tmpDir, STD_OPTS);
|
|
266
|
+
expect(out).toContain("Routed via Aria for Alice");
|
|
267
|
+
expect(out).toContain(SAMPLE_INDEX);
|
|
268
|
+
expect(out).not.toEqual(renderRouterPrompt(STD_OPTS));
|
|
269
|
+
});
|
|
270
|
+
|
|
219
271
|
test("missing override file falls back to bundled prompt", () => {
|
|
220
272
|
const overridePath = join(tmpDir, "does-not-exist.md");
|
|
221
|
-
expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
|
|
273
|
+
expect(resolveRouterPrompt(overridePath, tmpDir, STD_OPTS)).toEqual(
|
|
222
274
|
renderRouterPrompt(STD_OPTS),
|
|
223
275
|
);
|
|
224
276
|
});
|
|
@@ -226,7 +278,7 @@ describe("resolveRouterPrompt — override path", () => {
|
|
|
226
278
|
test("empty override file falls back to bundled prompt", () => {
|
|
227
279
|
const overridePath = join(tmpDir, "empty.md");
|
|
228
280
|
writeFileSync(overridePath, " \n\t\n", "utf-8");
|
|
229
|
-
expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
|
|
281
|
+
expect(resolveRouterPrompt(overridePath, tmpDir, STD_OPTS)).toEqual(
|
|
230
282
|
renderRouterPrompt(STD_OPTS),
|
|
231
283
|
);
|
|
232
284
|
});
|
|
@@ -234,7 +286,7 @@ describe("resolveRouterPrompt — override path", () => {
|
|
|
234
286
|
test("override that is a directory falls back to bundled prompt", () => {
|
|
235
287
|
// Pass the temp directory itself as the override path — lstat sees a
|
|
236
288
|
// directory, not a regular file, so the loader bails to bundled.
|
|
237
|
-
expect(resolveRouterPrompt(tmpDir, STD_OPTS)).toEqual(
|
|
289
|
+
expect(resolveRouterPrompt(tmpDir, tmpDir, STD_OPTS)).toEqual(
|
|
238
290
|
renderRouterPrompt(STD_OPTS),
|
|
239
291
|
);
|
|
240
292
|
});
|
|
@@ -247,7 +299,7 @@ describe("resolveRouterPrompt — override path", () => {
|
|
|
247
299
|
"utf-8",
|
|
248
300
|
);
|
|
249
301
|
|
|
250
|
-
const out = resolveRouterPrompt(overridePath, {
|
|
302
|
+
const out = resolveRouterPrompt(overridePath, tmpDir, {
|
|
251
303
|
assistantName: null,
|
|
252
304
|
userName: null,
|
|
253
305
|
pageIndexBlock: "",
|
|
@@ -121,6 +121,9 @@ const state = {
|
|
|
121
121
|
// Throw queue for upsert: first call shifts and throws if non-null;
|
|
122
122
|
// subsequent calls succeed once the queue is exhausted.
|
|
123
123
|
upsertThrowQueue: [] as Array<Error | null>,
|
|
124
|
+
// Throw queue for createPayloadIndex: each entry maps to the next call,
|
|
125
|
+
// so tests can simulate index-creation failures (strict-mode, network).
|
|
126
|
+
createIndexThrowQueue: [] as Array<Error | null>,
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
class MockQdrantClient {
|
|
@@ -157,6 +160,10 @@ class MockQdrantClient {
|
|
|
157
160
|
params: { field_name: string; field_schema: string },
|
|
158
161
|
) {
|
|
159
162
|
state.createIndexCalls.push(params);
|
|
163
|
+
if (state.createIndexThrowQueue.length > 0) {
|
|
164
|
+
const next = state.createIndexThrowQueue.shift();
|
|
165
|
+
if (next) throw next;
|
|
166
|
+
}
|
|
160
167
|
return {};
|
|
161
168
|
}
|
|
162
169
|
async upsert(_name: string, params: { wait: boolean; points: MockPoint[] }) {
|
|
@@ -228,6 +235,7 @@ function resetState(): void {
|
|
|
228
235
|
state.countThrows = null;
|
|
229
236
|
state.countCalls = 0;
|
|
230
237
|
state.upsertThrowQueue.length = 0;
|
|
238
|
+
state.createIndexThrowQueue.length = 0;
|
|
231
239
|
_resetMemoryV2QdrantForTests();
|
|
232
240
|
// Drop any sentinel a prior test left behind so the no-drift default path
|
|
233
241
|
// doesn't accidentally report `migrated: true`.
|
|
@@ -278,9 +286,10 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
278
286
|
});
|
|
279
287
|
expect(params.on_disk_payload).toBe(true);
|
|
280
288
|
|
|
281
|
-
// Slug payload
|
|
289
|
+
// Slug + kind payload indexes are created up front.
|
|
282
290
|
expect(state.createIndexCalls).toEqual([
|
|
283
291
|
{ field_name: "slug", field_schema: "keyword" },
|
|
292
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
284
293
|
]);
|
|
285
294
|
});
|
|
286
295
|
|
|
@@ -295,9 +304,14 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
295
304
|
await ensureConceptPageCollection();
|
|
296
305
|
|
|
297
306
|
// Existence check fired exactly once thanks to the in-memory readiness
|
|
298
|
-
// cache; createCollection
|
|
307
|
+
// cache; createCollection never ran. Payload indexes are (idempotently)
|
|
308
|
+
// ensured on the existing-collection path to backfill long-lived installs
|
|
309
|
+
// that predate the `kind` index.
|
|
299
310
|
expect(state.createCollectionCalls).toBe(0);
|
|
300
|
-
expect(state.createIndexCalls).toEqual([
|
|
311
|
+
expect(state.createIndexCalls).toEqual([
|
|
312
|
+
{ field_name: "slug", field_schema: "keyword" },
|
|
313
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
314
|
+
]);
|
|
301
315
|
expect(state.collectionExistsCalls).toBe(1);
|
|
302
316
|
});
|
|
303
317
|
|
|
@@ -314,6 +328,7 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
314
328
|
expect(state.createCollectionCalls).toBe(1);
|
|
315
329
|
expect(state.createIndexCalls).toEqual([
|
|
316
330
|
{ field_name: "slug", field_schema: "keyword" },
|
|
331
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
317
332
|
]);
|
|
318
333
|
});
|
|
319
334
|
|
|
@@ -433,6 +448,54 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
433
448
|
expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
|
|
434
449
|
});
|
|
435
450
|
|
|
451
|
+
test("swallows 'already exists' on createPayloadIndex but propagates other failures without latching readiness", async () => {
|
|
452
|
+
// Existing collection that already has the full schema — the ensure
|
|
453
|
+
// path goes through `ensurePayloadIndexes` to backfill long-lived
|
|
454
|
+
// installs. The first index call hits an "already exists" race
|
|
455
|
+
// (benign; swallow); the second hits a strict-mode rejection (must
|
|
456
|
+
// propagate so readiness is not latched).
|
|
457
|
+
state.collectionExistsBeforeCreate = true;
|
|
458
|
+
state.createIndexThrowQueue.push(
|
|
459
|
+
Object.assign(
|
|
460
|
+
new Error("Wrong input: Payload field 'slug' already exists"),
|
|
461
|
+
{ status: 400 },
|
|
462
|
+
),
|
|
463
|
+
Object.assign(
|
|
464
|
+
new Error(
|
|
465
|
+
"Strict mode prohibits creating payload indexes on this deployment",
|
|
466
|
+
),
|
|
467
|
+
{ status: 400 },
|
|
468
|
+
),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
let caught: unknown = null;
|
|
472
|
+
try {
|
|
473
|
+
await ensureConceptPageCollection();
|
|
474
|
+
} catch (err) {
|
|
475
|
+
caught = err;
|
|
476
|
+
}
|
|
477
|
+
expect((caught as Error | null)?.message).toMatch(/strict mode/i);
|
|
478
|
+
|
|
479
|
+
// Both attempts ran; the strict-mode failure was not swallowed.
|
|
480
|
+
expect(state.createIndexCalls).toEqual([
|
|
481
|
+
{ field_name: "slug", field_schema: "keyword" },
|
|
482
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
// Readiness must NOT be latched after a non-benign failure — otherwise
|
|
486
|
+
// later slug/kind-filtered queries (e.g. skill backfill) would keep
|
|
487
|
+
// failing until a daemon restart. A follow-up ensure must retry.
|
|
488
|
+
const result = await ensureConceptPageCollection();
|
|
489
|
+
expect(result).toEqual({ migrated: false });
|
|
490
|
+
// Indexes attempted again on the retry (no throws queued this time).
|
|
491
|
+
expect(state.createIndexCalls).toEqual([
|
|
492
|
+
{ field_name: "slug", field_schema: "keyword" },
|
|
493
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
494
|
+
{ field_name: "slug", field_schema: "keyword" },
|
|
495
|
+
{ field_name: "kind", field_schema: "keyword" },
|
|
496
|
+
]);
|
|
497
|
+
});
|
|
498
|
+
|
|
436
499
|
test("concurrent ensure during a schema rebuild only deletes/creates once", async () => {
|
|
437
500
|
state.collectionExistsBeforeCreate = true;
|
|
438
501
|
state.getCollectionInfo = {
|
|
@@ -460,6 +460,21 @@ describe("runRouter — failure modes", () => {
|
|
|
460
460
|
expect(warnSeen).toBe(true);
|
|
461
461
|
});
|
|
462
462
|
|
|
463
|
+
test("duplicate-heavy IDs are deduped before the cap is applied", async () => {
|
|
464
|
+
// [1, 1, 2] with max=2 must yield two distinct slugs, not collapse to one
|
|
465
|
+
// after a pre-dedupe slice trims away the only other unique ID.
|
|
466
|
+
providerStub = makeProvider(toolUseResponse([1, 1, 2]));
|
|
467
|
+
|
|
468
|
+
const result = await runRouter({
|
|
469
|
+
workspaceDir,
|
|
470
|
+
...COMMON_PARAMS,
|
|
471
|
+
config: makeConfig({ maxPageIds: 2 }),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
expect(result.failureReason).toBeNull();
|
|
475
|
+
expect(result.selectedSlugs).toEqual(["alpha", "bravo"]);
|
|
476
|
+
});
|
|
477
|
+
|
|
463
478
|
test("more than max_page_ids → truncated with warn", async () => {
|
|
464
479
|
providerStub = makeProvider(toolUseResponse([1, 2, 3]));
|
|
465
480
|
|