@vellumai/assistant 0.8.1 → 0.8.3
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 +13 -19
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +17 -0
- package/docker-init-apt-root.sh +167 -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 +642 -5
- 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-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- 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 +147 -0
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- 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 +31 -65
- 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 +59 -1
- 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-media-retry.test.ts +19 -8
- 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 +102 -13
- 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__/date-context.test.ts +45 -0
- 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 +151 -55
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- 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 +507 -10
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- 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-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +15 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -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-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +58 -13
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +41 -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__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -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 +242 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- 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} +7 -2
- 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 +158 -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} +33 -31
- 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__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
- 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 +670 -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-087-memory-router-balanced-profile.test.ts +228 -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/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/agent/loop.ts +167 -18
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +122 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +960 -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 +388 -346
- package/src/cli/commands/plugins.ts +252 -0
- package/src/cli/commands/schedules.ts +683 -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__/search-plugins.test.ts +261 -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 +303 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +52 -2
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
- 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/phone-calls/SKILL.md +1 -1
- 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/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +41 -9
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +9 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +17 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +23 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1107 -0
- package/src/context/token-estimator.ts +34 -36
- 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 +33 -18
- 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-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +198 -11
- 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 +25 -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 -8
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/conversations.ts +1 -0
- 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 +153 -27
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +89 -91
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +85 -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/notifications.ts +21 -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 +11 -54
- package/src/daemon/pkb-reminder-builder.ts +5 -20
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +24 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +33 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +73 -189
- package/src/home/__tests__/feed-types.test.ts +80 -0
- package/src/home/feed-types.ts +36 -2
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/ipc/cli-client.ts +147 -45
- 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 +483 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- 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 +197 -11
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +12 -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 +150 -12
- package/src/memory/graph/conversation-graph-memory.ts +49 -21
- package/src/memory/graph/tools.ts +9 -40
- package/src/memory/indexer.ts +34 -29
- package/src/memory/invite-store.ts +53 -0
- 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 +24 -12
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- 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/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +9 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +3 -3
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +288 -11
- 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/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +81 -26
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +63 -8
- 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/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- 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/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +113 -45
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +21 -1
- package/src/notifications/home-feed-side-effect.ts +138 -5
- package/src/notifications/signal.ts +3 -5
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +19 -6
- 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 +74 -22
- 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 +187 -42
- 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 +40 -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 +10 -43
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -174
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +164 -0
- package/src/providers/__tests__/inference.test.ts +24 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +68 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +32 -6
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +159 -34
- package/src/providers/inference/resolve-auth.ts +14 -4
- package/src/providers/model-catalog.ts +249 -12
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +169 -8
- package/src/providers/openrouter/client.ts +49 -4
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
- 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 +38 -0
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +52 -15
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +103 -15
- package/src/runtime/auth/route-policy.ts +21 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +19 -47
- 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__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- 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 +126 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -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 +99 -35
- package/src/runtime/routes/conversation-routes.ts +97 -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 +8 -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 +199 -22
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- 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/llm-call-sites-routes.ts +11 -1
- 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 +98 -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 +17 -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/memory/register.ts +1 -9
- 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 +107 -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/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +10 -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/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -161,6 +161,38 @@ mock.module("../skill-store.js", () => ({
|
|
|
161
161
|
listSkillEntries: () => Array.from(skillState.entries.values()),
|
|
162
162
|
}));
|
|
163
163
|
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// CLI-command-store mock
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
//
|
|
168
|
+
// Mirrors the skill-store mock. CLI subcommand synthetic entries flow through
|
|
169
|
+
// the unified pipeline under the `cli-commands/<name>` slug prefix and render
|
|
170
|
+
// under `### CLI Commands You Can Use`. Tests stage `cliCommandState.entries`
|
|
171
|
+
// and rely on `stageTurn` plumbing to land slugs in the candidate set.
|
|
172
|
+
|
|
173
|
+
interface CliCommandEntryStub {
|
|
174
|
+
id: string;
|
|
175
|
+
description: string;
|
|
176
|
+
content: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const cliCommandState = {
|
|
180
|
+
entries: new Map<string, CliCommandEntryStub>(),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
mock.module("../cli-command-store.js", () => ({
|
|
184
|
+
getCliCommandCapability: (idOrSlug: string) => {
|
|
185
|
+
const id = idOrSlug.startsWith("cli-commands/")
|
|
186
|
+
? idOrSlug.slice("cli-commands/".length)
|
|
187
|
+
: idOrSlug;
|
|
188
|
+
return cliCommandState.entries.get(id) ?? null;
|
|
189
|
+
},
|
|
190
|
+
isCliCommandSlug: (slug: string) => slug.startsWith("cli-commands/"),
|
|
191
|
+
CLI_COMMAND_SLUG_PREFIX: "cli-commands/",
|
|
192
|
+
cliCommandSlugFor: (name: string) => `cli-commands/${name}`,
|
|
193
|
+
listCliCommandEntries: () => Array.from(cliCommandState.entries.values()),
|
|
194
|
+
}));
|
|
195
|
+
|
|
164
196
|
// ---------------------------------------------------------------------------
|
|
165
197
|
// Activation-log store mock
|
|
166
198
|
// ---------------------------------------------------------------------------
|
|
@@ -357,7 +389,7 @@ const { getSqliteFrom } = await import("../../db-connection.js");
|
|
|
357
389
|
const { migrateActivationState } =
|
|
358
390
|
await import("../../migrations/232-activation-state.js");
|
|
359
391
|
const schema = await import("../../schema.js");
|
|
360
|
-
const {
|
|
392
|
+
const { clearEverInjected, hydrate, save } =
|
|
361
393
|
await import("../activation-store.js");
|
|
362
394
|
const { injectMemoryV2Block } = await import("../injection.js");
|
|
363
395
|
const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
|
|
@@ -484,6 +516,7 @@ function resetState(): void {
|
|
|
484
516
|
state.queryResponses.dense.length = 0;
|
|
485
517
|
state.queryResponses.sparse.length = 0;
|
|
486
518
|
skillState.entries.clear();
|
|
519
|
+
cliCommandState.entries.clear();
|
|
487
520
|
telemetryState.recordCalls.length = 0;
|
|
488
521
|
telemetryState.recordShouldThrow = false;
|
|
489
522
|
pageStoreState.failingSlugs.clear();
|
|
@@ -503,6 +536,13 @@ function stageSkills(entries: SkillEntry[]): void {
|
|
|
503
536
|
}
|
|
504
537
|
}
|
|
505
538
|
|
|
539
|
+
/** Stage cli-command-store cache entries for the upcoming render. */
|
|
540
|
+
function stageCliCommands(entries: CliCommandEntryStub[]): void {
|
|
541
|
+
for (const entry of entries) {
|
|
542
|
+
cliCommandState.entries.set(entry.id, entry);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
506
546
|
let db: DrizzleDb;
|
|
507
547
|
beforeEach(() => {
|
|
508
548
|
db = createTestDb();
|
|
@@ -653,10 +693,10 @@ describe("injectMemoryV2Block", () => {
|
|
|
653
693
|
config: makeConfig(),
|
|
654
694
|
});
|
|
655
695
|
|
|
656
|
-
// Simulate compaction:
|
|
696
|
+
// Simulate compaction: clear the entire everInjected list.
|
|
657
697
|
const beforeEvict = await hydrate(db, "conv-1");
|
|
658
698
|
expect(beforeEvict).not.toBeNull();
|
|
659
|
-
const afterEvict =
|
|
699
|
+
const afterEvict = clearEverInjected(beforeEvict!);
|
|
660
700
|
expect(afterEvict.everInjected).toEqual([]);
|
|
661
701
|
await save(db, "conv-1", afterEvict);
|
|
662
702
|
|
|
@@ -879,17 +919,17 @@ describe("injectMemoryV2Block", () => {
|
|
|
879
919
|
// Unified pool — skills as `skills/<id>` slugs
|
|
880
920
|
// ---------------------------------------------------------------------------
|
|
881
921
|
|
|
882
|
-
test("renders a
|
|
922
|
+
test("renders a retrieved skills/<id> slug under Skills You Can Use", async () => {
|
|
883
923
|
// No concept-page candidates this turn — the only ANN hit is a skill
|
|
884
924
|
// slug. The render path branches on `skills/` prefix: it pulls the
|
|
885
925
|
// entry from the skill-store cache (mocked) and emits the bullet under
|
|
886
926
|
// the `### Skills You Can Use` subsection.
|
|
887
|
-
stageTurn([{ slug: "skills/
|
|
927
|
+
stageTurn([{ slug: "skills/retrieved-skill", denseScore: 0.9 }]);
|
|
888
928
|
stageSkills([
|
|
889
929
|
{
|
|
890
|
-
id: "
|
|
930
|
+
id: "retrieved-skill",
|
|
891
931
|
content:
|
|
892
|
-
'The "
|
|
932
|
+
'The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills.',
|
|
893
933
|
},
|
|
894
934
|
]);
|
|
895
935
|
|
|
@@ -904,16 +944,18 @@ describe("injectMemoryV2Block", () => {
|
|
|
904
944
|
config: makeConfig(),
|
|
905
945
|
});
|
|
906
946
|
|
|
907
|
-
expect(result.toInject).toEqual(["skills/
|
|
947
|
+
expect(result.toInject).toEqual(["skills/retrieved-skill"]);
|
|
908
948
|
expect(result.block).not.toBeNull();
|
|
909
949
|
expect(result.block).not.toContain("<memory>");
|
|
910
950
|
expect(result.block).not.toContain("</memory>");
|
|
911
951
|
expect(result.block).not.toContain("## What I Remember Right Now");
|
|
912
952
|
expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
'- The "
|
|
953
|
+
const headerIdx = result.block!.indexOf("### Skills You Can Use");
|
|
954
|
+
const skillIdx = result.block!.indexOf(
|
|
955
|
+
'- The "Retrieved Skill" skill (retrieved-skill) is available. Helps with retrieved skills. → use skill_load to activate',
|
|
916
956
|
);
|
|
957
|
+
expect(headerIdx).toBeGreaterThan(-1);
|
|
958
|
+
expect(skillIdx).toBeGreaterThan(headerIdx);
|
|
917
959
|
});
|
|
918
960
|
|
|
919
961
|
test("renders concept-page sections before the skills subsection in mixed blocks", async () => {
|
|
@@ -1058,6 +1100,153 @@ describe("injectMemoryV2Block", () => {
|
|
|
1058
1100
|
expect(result.block).toBeNull();
|
|
1059
1101
|
});
|
|
1060
1102
|
|
|
1103
|
+
// ---------------------------------------------------------------------------
|
|
1104
|
+
// CLI-command synthetic entries — same unified-pool plumbing as skills.
|
|
1105
|
+
// ---------------------------------------------------------------------------
|
|
1106
|
+
|
|
1107
|
+
test("renders a retrieved cli-commands/<name> slug under CLI Commands You Can Use", async () => {
|
|
1108
|
+
stageTurn([{ slug: "cli-commands/attachment", denseScore: 0.9 }]);
|
|
1109
|
+
stageCliCommands([
|
|
1110
|
+
{
|
|
1111
|
+
id: "attachment",
|
|
1112
|
+
description: "Manage file attachments for conversations",
|
|
1113
|
+
content: 'The "assistant attachment" CLI command is available...',
|
|
1114
|
+
},
|
|
1115
|
+
]);
|
|
1116
|
+
|
|
1117
|
+
const result = await injectMemoryV2Block({
|
|
1118
|
+
database: db,
|
|
1119
|
+
conversationId: "conv-1",
|
|
1120
|
+
currentTurn: 1,
|
|
1121
|
+
userMessage: "How do I register a video?",
|
|
1122
|
+
assistantMessage: "",
|
|
1123
|
+
nowText: "Now",
|
|
1124
|
+
messageId: "msg-1",
|
|
1125
|
+
config: makeConfig(),
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
expect(result.toInject).toEqual(["cli-commands/attachment"]);
|
|
1129
|
+
expect(result.block).not.toBeNull();
|
|
1130
|
+
const headerIdx = result.block!.indexOf("### CLI Commands You Can Use");
|
|
1131
|
+
const lineIdx = result.block!.indexOf(
|
|
1132
|
+
"- `assistant attachment`: Manage file attachments for conversations",
|
|
1133
|
+
);
|
|
1134
|
+
expect(headerIdx).toBeGreaterThan(-1);
|
|
1135
|
+
expect(lineIdx).toBeGreaterThan(headerIdx);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test("renders concepts, skills, then cli-commands in that order in mixed blocks", async () => {
|
|
1139
|
+
stageTurn([
|
|
1140
|
+
{ slug: "alice-vscode", denseScore: 0.95 },
|
|
1141
|
+
{ slug: "skills/example-skill-a", denseScore: 0.85 },
|
|
1142
|
+
{ slug: "cli-commands/config", denseScore: 0.75 },
|
|
1143
|
+
]);
|
|
1144
|
+
stageSkills([
|
|
1145
|
+
{
|
|
1146
|
+
id: "example-skill-a",
|
|
1147
|
+
content:
|
|
1148
|
+
'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
|
|
1149
|
+
},
|
|
1150
|
+
]);
|
|
1151
|
+
stageCliCommands([
|
|
1152
|
+
{
|
|
1153
|
+
id: "config",
|
|
1154
|
+
description: "Manage configuration",
|
|
1155
|
+
content: 'The "assistant config" CLI command is available...',
|
|
1156
|
+
},
|
|
1157
|
+
]);
|
|
1158
|
+
|
|
1159
|
+
const result = await injectMemoryV2Block({
|
|
1160
|
+
database: db,
|
|
1161
|
+
conversationId: "conv-1",
|
|
1162
|
+
currentTurn: 1,
|
|
1163
|
+
userMessage: "Help me",
|
|
1164
|
+
assistantMessage: "",
|
|
1165
|
+
nowText: "Now",
|
|
1166
|
+
messageId: "msg-1",
|
|
1167
|
+
config: makeConfig(),
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
expect(new Set(result.toInject)).toEqual(
|
|
1171
|
+
new Set([
|
|
1172
|
+
"alice-vscode",
|
|
1173
|
+
"skills/example-skill-a",
|
|
1174
|
+
"cli-commands/config",
|
|
1175
|
+
]),
|
|
1176
|
+
);
|
|
1177
|
+
const conceptIdx = result.block!.indexOf(
|
|
1178
|
+
"# memory/concepts/alice-vscode.md",
|
|
1179
|
+
);
|
|
1180
|
+
const skillsIdx = result.block!.indexOf("### Skills You Can Use");
|
|
1181
|
+
const cliIdx = result.block!.indexOf("### CLI Commands You Can Use");
|
|
1182
|
+
expect(conceptIdx).toBeGreaterThan(-1);
|
|
1183
|
+
expect(skillsIdx).toBeGreaterThan(conceptIdx);
|
|
1184
|
+
expect(cliIdx).toBeGreaterThan(skillsIdx);
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
test("cli-command slugs whose entry is missing from the cache are dropped silently", async () => {
|
|
1188
|
+
stageTurn([{ slug: "cli-commands/missing-command", denseScore: 0.9 }]);
|
|
1189
|
+
|
|
1190
|
+
const result = await injectMemoryV2Block({
|
|
1191
|
+
database: db,
|
|
1192
|
+
conversationId: "conv-1",
|
|
1193
|
+
currentTurn: 1,
|
|
1194
|
+
userMessage: "anything",
|
|
1195
|
+
assistantMessage: "",
|
|
1196
|
+
nowText: "Now",
|
|
1197
|
+
messageId: "msg-1",
|
|
1198
|
+
config: makeConfig(),
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
expect(result.toInject).toEqual([]);
|
|
1202
|
+
expect(result.block).toBeNull();
|
|
1203
|
+
|
|
1204
|
+
const persisted = await hydrate(db, "conv-1");
|
|
1205
|
+
expect(persisted!.everInjected).toEqual([]);
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
test("cli-commands participate in everInjected so they dedupe across turns", async () => {
|
|
1209
|
+
const entry = {
|
|
1210
|
+
id: "config",
|
|
1211
|
+
description: "Manage configuration",
|
|
1212
|
+
content: 'The "assistant config" CLI command is available...',
|
|
1213
|
+
};
|
|
1214
|
+
stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
|
|
1215
|
+
stageCliCommands([entry]);
|
|
1216
|
+
const result1 = await injectMemoryV2Block({
|
|
1217
|
+
database: db,
|
|
1218
|
+
conversationId: "conv-1",
|
|
1219
|
+
currentTurn: 1,
|
|
1220
|
+
userMessage: "config",
|
|
1221
|
+
assistantMessage: "",
|
|
1222
|
+
nowText: "Now",
|
|
1223
|
+
messageId: "msg-1",
|
|
1224
|
+
config: makeConfig(),
|
|
1225
|
+
});
|
|
1226
|
+
expect(result1.toInject).toEqual(["cli-commands/config"]);
|
|
1227
|
+
expect(result1.block).toContain("### CLI Commands You Can Use");
|
|
1228
|
+
|
|
1229
|
+
stageTurn([{ slug: "cli-commands/config", denseScore: 0.9 }]);
|
|
1230
|
+
stageCliCommands([entry]);
|
|
1231
|
+
const result2 = await injectMemoryV2Block({
|
|
1232
|
+
database: db,
|
|
1233
|
+
conversationId: "conv-1",
|
|
1234
|
+
currentTurn: 2,
|
|
1235
|
+
userMessage: "more config",
|
|
1236
|
+
assistantMessage: "ok",
|
|
1237
|
+
nowText: "Now",
|
|
1238
|
+
messageId: "msg-2",
|
|
1239
|
+
config: makeConfig(),
|
|
1240
|
+
});
|
|
1241
|
+
expect(result2.toInject).toEqual([]);
|
|
1242
|
+
expect(result2.block).toBeNull();
|
|
1243
|
+
|
|
1244
|
+
const persisted = await hydrate(db, "conv-1");
|
|
1245
|
+
expect(persisted!.everInjected).toEqual([
|
|
1246
|
+
{ slug: "cli-commands/config", turn: 1 },
|
|
1247
|
+
]);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1061
1250
|
test("context-load mode renders topNow even when every slug was previously injected", async () => {
|
|
1062
1251
|
// Turn 1 (per-turn): seed alice as injected.
|
|
1063
1252
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
@@ -1679,6 +1868,56 @@ describe("injectMemoryV2Block", () => {
|
|
|
1679
1868
|
expect(row.concepts).toEqual([]);
|
|
1680
1869
|
});
|
|
1681
1870
|
|
|
1871
|
+
test("flag-on: router-failure path swallows a save() error and returns block:null instead of throwing", async () => {
|
|
1872
|
+
// PR 30176 refactored router-failure handling to delegate to
|
|
1873
|
+
// `finalizeInjection`. That regressed the prior inline log-and-continue
|
|
1874
|
+
// semantics on the router-failure path: a transient SQLite write
|
|
1875
|
+
// throwing during the stub-state save now aborted the whole turn
|
|
1876
|
+
// because `finalizeInjection`'s try/catch re-threw caughtErr at the end.
|
|
1877
|
+
//
|
|
1878
|
+
// This test stages exactly that scenario — router returns
|
|
1879
|
+
// `failureReason: api_error` AND `save()` throws — and asserts the
|
|
1880
|
+
// turn completes with `{ block: null, toInject: [] }` rather than
|
|
1881
|
+
// propagating the SQLite error to `prepareMemory`.
|
|
1882
|
+
routerState.nextResult = {
|
|
1883
|
+
selectedSlugs: [],
|
|
1884
|
+
failureReason: "api_error",
|
|
1885
|
+
};
|
|
1886
|
+
activationStoreState.saveShouldThrow = true;
|
|
1887
|
+
|
|
1888
|
+
let threw: unknown = undefined;
|
|
1889
|
+
let result: Awaited<ReturnType<typeof injectMemoryV2Block>> | undefined;
|
|
1890
|
+
try {
|
|
1891
|
+
result = await injectMemoryV2Block({
|
|
1892
|
+
database: db,
|
|
1893
|
+
conversationId: "conv-router-fail-save-throws",
|
|
1894
|
+
currentTurn: 5,
|
|
1895
|
+
userMessage: "anything",
|
|
1896
|
+
assistantMessage: "ok",
|
|
1897
|
+
nowText: "Now",
|
|
1898
|
+
messageId: "msg-fail-save",
|
|
1899
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1900
|
+
});
|
|
1901
|
+
} catch (err) {
|
|
1902
|
+
threw = err;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
expect(threw).toBeUndefined();
|
|
1906
|
+
expect(result).toBeDefined();
|
|
1907
|
+
expect(result!.block).toBeNull();
|
|
1908
|
+
expect(result!.toInject).toEqual([]);
|
|
1909
|
+
|
|
1910
|
+
// Telemetry still flushes with `mode: "errored"` so the failure stays
|
|
1911
|
+
// observable — the same row the inline pre-refactor path emitted.
|
|
1912
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1913
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1914
|
+
mode: string;
|
|
1915
|
+
concepts: unknown[];
|
|
1916
|
+
};
|
|
1917
|
+
expect(row.mode).toBe("errored");
|
|
1918
|
+
expect(row.concepts).toEqual([]);
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1682
1921
|
test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
|
|
1683
1922
|
routerState.nextResult = {
|
|
1684
1923
|
selectedSlugs: [],
|
|
@@ -1904,5 +2143,43 @@ describe("injectMemoryV2Block", () => {
|
|
|
1904
2143
|
const row = telemetryState.recordCalls[0] as { mode: string };
|
|
1905
2144
|
expect(row.mode).toBe("per-turn");
|
|
1906
2145
|
});
|
|
2146
|
+
|
|
2147
|
+
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 () => {
|
|
2148
|
+
// Context-load is the full top-K bootstrap fired after compaction or
|
|
2149
|
+
// a fresh conversation reload. The earlier worry about the router's
|
|
2150
|
+
// `everInjected` dedupe filtering out post-compaction restorations
|
|
2151
|
+
// doesn't apply: `ConversationGraphMemory.onCompacted` calls
|
|
2152
|
+
// `evictCompactedTurnsV2` which empties the list before this code
|
|
2153
|
+
// runs. Router abstention here means no v2 pages this turn — that's
|
|
2154
|
+
// preferable to letting the activation graph pick something arbitrary.
|
|
2155
|
+
routerState.nextResult = {
|
|
2156
|
+
selectedSlugs: ["alice-vscode"],
|
|
2157
|
+
failureReason: null,
|
|
2158
|
+
};
|
|
2159
|
+
|
|
2160
|
+
const result = await injectMemoryV2Block({
|
|
2161
|
+
database: db,
|
|
2162
|
+
conversationId: "conv-context-load-router-on",
|
|
2163
|
+
currentTurn: 1,
|
|
2164
|
+
userMessage: "Tell me about Alice",
|
|
2165
|
+
assistantMessage: "",
|
|
2166
|
+
nowText: "Now",
|
|
2167
|
+
messageId: "msg-1",
|
|
2168
|
+
mode: "context-load",
|
|
2169
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
// Router was called on context-load too.
|
|
2173
|
+
expect(routerState.callCount).toBe(1);
|
|
2174
|
+
|
|
2175
|
+
// Router's picks were rendered.
|
|
2176
|
+
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
2177
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
2178
|
+
|
|
2179
|
+
// Telemetry row reflects the router mode, not the activation mode.
|
|
2180
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
2181
|
+
const row = telemetryState.recordCalls[0] as { mode: string };
|
|
2182
|
+
expect(row.mode).toBe("router");
|
|
2183
|
+
});
|
|
1907
2184
|
});
|
|
1908
2185
|
});
|
|
@@ -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: "",
|