@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae
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 +6 -6
- package/bun.lock +2 -2
- package/docs/activation-funnel-telemetry.md +310 -0
- package/examples/plugins/echo/README.md +61 -60
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +263 -121
- package/package.json +2 -2
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +23 -8
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +6 -5
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +146 -0
- package/src/__tests__/compaction-events.test.ts +7 -7
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +265 -125
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
- package/src/__tests__/conversation-agent-loop.test.ts +342 -623
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
- package/src/__tests__/conversation-process-callsite.test.ts +15 -15
- package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
- package/src/__tests__/conversation-queue.test.ts +10 -10
- package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +7 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
- package/src/__tests__/conversation-speed-override.test.ts +11 -10
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
- package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +17 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/history-repair-hook.test.ts +5 -1
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +91 -81
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/published-app-updater.test.ts +138 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +5 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +188 -28
- package/src/acp/resolve-agent.ts +119 -42
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +403 -251
- package/src/api/responses/conversation-message.ts +22 -2
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/approvals/guardian-request-resolvers.ts +27 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
- package/src/cli/lib/install-from-github.ts +797 -60
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +416 -0
- package/src/cli/lib/plugin-marketplace.ts +215 -0
- package/src/cli/lib/search-plugins.ts +201 -29
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +9 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +80 -41
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -3
- package/src/config/feature-flag-registry.json +10 -16
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -21
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +17 -3
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/context/strip-injections.ts +8 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
- package/src/daemon/conversation-agent-loop.ts +308 -1297
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +22 -141
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +361 -393
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +165 -11
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +444 -62
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +36 -46
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +6 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -63
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +9 -2
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +40 -11
- package/src/plugin-api/types.ts +108 -0
- package/src/plugins/defaults/compaction/compact.ts +106 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +156 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/external-api.ts +12 -2
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/acp-routes.test.ts +185 -32
- package/src/runtime/routes/acp-routes.ts +328 -30
- package/src/runtime/routes/app-management-routes.ts +3 -0
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +30 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +65 -305
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/services/published-app-updater.ts +30 -8
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +9 -19
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +173 -202
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +32 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/daemon/wake-target-adapter.ts +0 -253
- package/src/memory/v3/__tests__/assign.test.ts +0 -242
- package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
- package/src/memory/v3/__tests__/core.test.ts +0 -39
- package/src/memory/v3/__tests__/health.test.ts +0 -219
- package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
- package/src/memory/v3/__tests__/needle.test.ts +0 -107
- package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
- package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
- package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
- package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
- package/src/memory/v3/__tests__/router.test.ts +0 -332
- package/src/memory/v3/__tests__/selector.test.ts +0 -470
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
- package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
- package/src/memory/v3/__tests__/tree.test.ts +0 -192
- package/src/memory/v3/assign.ts +0 -268
- package/src/memory/v3/capabilities.ts +0 -124
- package/src/memory/v3/core.ts +0 -26
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +0 -314
- package/src/memory/v3/needle.ts +0 -115
- package/src/memory/v3/orchestrate.ts +0 -126
- package/src/memory/v3/page-content.ts +0 -34
- package/src/memory/v3/provider-blocks.ts +0 -26
- package/src/memory/v3/reconcile.ts +0 -523
- package/src/memory/v3/router.ts +0 -190
- package/src/memory/v3/selection-log-store.ts +0 -84
- package/src/memory/v3/selector.ts +0 -226
- package/src/memory/v3/shadow-plugin.ts +0 -411
- package/src/memory/v3/snapshot.ts +0 -209
- package/src/memory/v3/tree.ts +0 -174
- package/src/memory/v3/types.ts +0 -59
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- package/src/util/map-limit.ts +0 -27
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-side store for the inspector's Memory V3 section. Reads the persisted
|
|
3
|
+
* `memory_v3_selections` rows for a turn and re-renders the `<memory>` block
|
|
4
|
+
* the v3 working set selected, so the inspector can show what v3 chose (and,
|
|
5
|
+
* in live mode, what it actually injected) without re-running orchestration —
|
|
6
|
+
* which would be wrong anyway, since the working set is stateful (carry-forward
|
|
7
|
+
* across turns) and can't be reproduced after the fact.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MemoryV3SelectionLog } from "../../../api/responses/memory-v3-selection-log.js";
|
|
11
|
+
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
12
|
+
import { getConfig } from "../../../config/loader.js";
|
|
13
|
+
import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
|
|
14
|
+
import { renderV3SectionContent } from "./page-content.js";
|
|
15
|
+
import { renderMemoryBlock } from "./render-injection.js";
|
|
16
|
+
import {
|
|
17
|
+
type Section,
|
|
18
|
+
SELECTION_SOURCES,
|
|
19
|
+
type SelectionSource,
|
|
20
|
+
type Slug,
|
|
21
|
+
} from "./types.js";
|
|
22
|
+
|
|
23
|
+
const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
|
|
24
|
+
const MEMORY_V3_LIVE = "memory-v3-live" as const;
|
|
25
|
+
|
|
26
|
+
interface SelectionRow {
|
|
27
|
+
slug: string;
|
|
28
|
+
source: string;
|
|
29
|
+
pinned: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
|
|
33
|
+
return getSqliteFrom(getDb())
|
|
34
|
+
.query(
|
|
35
|
+
/*sql*/ `
|
|
36
|
+
SELECT slug, source, pinned FROM memory_v3_selections
|
|
37
|
+
WHERE conversation_id = ? AND turn = ?
|
|
38
|
+
ORDER BY rowid
|
|
39
|
+
`,
|
|
40
|
+
)
|
|
41
|
+
.all(conversationId, turn) as SelectionRow[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build the inspector's v3 selection log for one turn of a conversation.
|
|
46
|
+
*
|
|
47
|
+
* `turn` is the inspected message's turn (the v2-activation log's turn). The
|
|
48
|
+
* selection is returned ONLY for that exact turn; when there are no v3 rows for
|
|
49
|
+
* it — including when `turn` is null (the message has no v2-activation turn) —
|
|
50
|
+
* this returns `null` rather than falling back to a different turn. A fallback
|
|
51
|
+
* would attribute another turn's selection (e.g. a later turn's pages and
|
|
52
|
+
* rendered block) to the inspected message, which corrupts shadow validation.
|
|
53
|
+
*
|
|
54
|
+
* Caveat: v3 rows are keyed by the orchestrator turn counter (`ctx.turnCount`)
|
|
55
|
+
* while `turn` here is the v2 memory-tracker turn. They coincide for normal
|
|
56
|
+
* turns, but if they diverge this simply yields `null` (no section) rather than
|
|
57
|
+
* wrong data. Tying v3 rows to a message id for exact per-message attribution
|
|
58
|
+
* regardless of counter drift is a documented follow-up.
|
|
59
|
+
*
|
|
60
|
+
* Selection rows are stored in `finalInjection` order (this turn's L2
|
|
61
|
+
* selections, then carry-forward), so rendering them in row order reproduces
|
|
62
|
+
* the block v3 would inject.
|
|
63
|
+
*/
|
|
64
|
+
export async function getMemoryV3SelectionForInspector(
|
|
65
|
+
conversationId: string,
|
|
66
|
+
turn: number | null | undefined,
|
|
67
|
+
): Promise<MemoryV3SelectionLog | null> {
|
|
68
|
+
if (turn == null) return null;
|
|
69
|
+
|
|
70
|
+
const rows = rowsForTurn(conversationId, turn);
|
|
71
|
+
if (rows.length === 0) return null;
|
|
72
|
+
|
|
73
|
+
const config = getConfig();
|
|
74
|
+
const selections = rows.map((r) => ({
|
|
75
|
+
slug: r.slug,
|
|
76
|
+
source: r.source,
|
|
77
|
+
pinned: r.pinned === 1,
|
|
78
|
+
}));
|
|
79
|
+
const slugs: Slug[] = selections.map((s) => s.slug);
|
|
80
|
+
// NOTE: `injectedText` is APPROXIMATE — it renders the full/lead page for
|
|
81
|
+
// every slug, not the exact matched section live injection used. The inspector
|
|
82
|
+
// re-renders from persisted rows without re-running orchestration, and the
|
|
83
|
+
// section identity is not persisted (`memory_v3_selections` stores
|
|
84
|
+
// slug/source/pinned, not the section ordinal), so the section map is empty
|
|
85
|
+
// and `renderV3SectionContent` falls back to the full page. Persisting the
|
|
86
|
+
// injected section (a schema migration) is deliberately deferred: the A/B's
|
|
87
|
+
// primary signal is slug-level recall via `summarizeSelections`, which is
|
|
88
|
+
// exact — only this debug-oriented injected text is an approximation.
|
|
89
|
+
const injectedText = await renderMemoryBlock(
|
|
90
|
+
slugs,
|
|
91
|
+
new Map<Slug, Section>(),
|
|
92
|
+
renderV3SectionContent,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
turn,
|
|
97
|
+
live: isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config),
|
|
98
|
+
shadow: isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config),
|
|
99
|
+
selections,
|
|
100
|
+
injectedText,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Offline A/B aggregate over a conversation's logged v3 selections. Reads every
|
|
106
|
+
* `memory_v3_selections` row for the conversation (all turns) and rolls them up
|
|
107
|
+
* for shadow-vs-v2 inspection without re-rendering any blocks:
|
|
108
|
+
*
|
|
109
|
+
* - `bySource`: count of selection rows per lane source (`needle` / `dense` /
|
|
110
|
+
* `edge` / `carry-forward`). Every known source is present (zero when
|
|
111
|
+
* unused) so callers can diff two runs without null-guarding; an unknown
|
|
112
|
+
* historical/free-text source is ignored (the column is permissive).
|
|
113
|
+
* - `turns`: number of distinct turns that logged at least one selection.
|
|
114
|
+
* - `distinctSlugs`: number of distinct page slugs selected across all turns —
|
|
115
|
+
* the conversation's working-set footprint.
|
|
116
|
+
*
|
|
117
|
+
* This is read-only telemetry for comparing a shadow run's lane mix against
|
|
118
|
+
* v2's logged selections offline; it never re-runs orchestration.
|
|
119
|
+
*/
|
|
120
|
+
export interface SelectionSummary {
|
|
121
|
+
bySource: Record<SelectionSource, number>;
|
|
122
|
+
turns: number;
|
|
123
|
+
distinctSlugs: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isSelectionSource(source: string): source is SelectionSource {
|
|
127
|
+
return (SELECTION_SOURCES as readonly string[]).includes(source);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function summarizeSelections(conversationId: string): SelectionSummary {
|
|
131
|
+
const rows = getSqliteFrom(getDb())
|
|
132
|
+
.query(
|
|
133
|
+
/*sql*/ `
|
|
134
|
+
SELECT turn, slug, source FROM memory_v3_selections
|
|
135
|
+
WHERE conversation_id = ?
|
|
136
|
+
`,
|
|
137
|
+
)
|
|
138
|
+
.all(conversationId) as Array<{
|
|
139
|
+
turn: number;
|
|
140
|
+
slug: string;
|
|
141
|
+
source: string;
|
|
142
|
+
}>;
|
|
143
|
+
|
|
144
|
+
const bySource = Object.fromEntries(
|
|
145
|
+
SELECTION_SOURCES.map((source) => [source, 0]),
|
|
146
|
+
) as Record<SelectionSource, number>;
|
|
147
|
+
const turns = new Set<number>();
|
|
148
|
+
const slugs = new Set<string>();
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
if (isSelectionSource(row.source)) bySource[row.source] += 1;
|
|
151
|
+
turns.add(row.turn);
|
|
152
|
+
slugs.add(row.slug);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { bySource, turns: turns.size, distinctSlugs: slugs.size };
|
|
156
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — flag-gated shadow/live orchestration engine.
|
|
3
|
+
*
|
|
4
|
+
* Runs the v3 orchestrator each turn and records its selection set to
|
|
5
|
+
* `memory_v3_selections`. Two flags gate behavior:
|
|
6
|
+
*
|
|
7
|
+
* - `memory-v3-shadow` (live OFF): observation-only. {@link observeTurn}
|
|
8
|
+
* orchestrates and logs the selection set; no injection is produced, so v2
|
|
9
|
+
* injection is bit-for-bit identical — the only difference is the
|
|
10
|
+
* side-effect telemetry write.
|
|
11
|
+
* - `memory-v3-live`: live injection. The injector (`memoryV3Injector` in
|
|
12
|
+
* `./injector.ts`) additionally renders the working-set selection into a
|
|
13
|
+
* `<memory>` block and returns it at v2's dynamic-memory placement
|
|
14
|
+
* (`after-memory-prefix`). Selections are still logged.
|
|
15
|
+
* - both OFF: orchestration is skipped entirely.
|
|
16
|
+
*
|
|
17
|
+
* On each turn (either flag on):
|
|
18
|
+
* 1. Lazy-init the v3 lanes ONCE across the whole process (section index,
|
|
19
|
+
* section-grain BM25 needle, dense lane config, link-graph edge graph,
|
|
20
|
+
* carry-forward working set), memoizing the init promise so concurrent
|
|
21
|
+
* first turns share a single build.
|
|
22
|
+
* 2. Build a {@link MemoryRoutingTurn} from the conversation's recent messages.
|
|
23
|
+
* 3. Run {@link orchestrate} and record its selection set to
|
|
24
|
+
* `memory_v3_selections` with a best-effort lane attribution.
|
|
25
|
+
*
|
|
26
|
+
* {@link observeTurn} wraps everything after the flag read in try/catch — any
|
|
27
|
+
* failure is logged and swallowed so it can never affect the live turn. The
|
|
28
|
+
* injector treats a `null`/empty result as "no v3 injection", so v2 memory
|
|
29
|
+
* remains the fallback rather than dropping all memory.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
33
|
+
|
|
34
|
+
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
35
|
+
import { getConfig } from "../../../config/loader.js";
|
|
36
|
+
import type { AssistantConfig } from "../../../config/schema.js";
|
|
37
|
+
import { getMessages } from "../../../memory/conversation-crud.js";
|
|
38
|
+
import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
|
|
39
|
+
import { stringifyMessageContent } from "../../../memory/message-content.js";
|
|
40
|
+
import { getPageIndex } from "../../../memory/v2/page-index.js";
|
|
41
|
+
import { readPage, renderPageContent } from "../../../memory/v2/page-store.js";
|
|
42
|
+
import { getLogger } from "../../../util/logger.js";
|
|
43
|
+
import {
|
|
44
|
+
getWorkspaceDir,
|
|
45
|
+
getWorkspacePromptPath,
|
|
46
|
+
} from "../../../util/platform.js";
|
|
47
|
+
import { stripCommentLines } from "../../../util/strip-comment-lines.js";
|
|
48
|
+
import { capabilityOrDiskBody } from "./capabilities.js";
|
|
49
|
+
import type { EdgeGraph } from "./edge.js";
|
|
50
|
+
import { buildEdgeGraph } from "./edge.js";
|
|
51
|
+
import type { OrchestrateResult } from "./orchestrate.js";
|
|
52
|
+
import { orchestrate } from "./orchestrate.js";
|
|
53
|
+
import { ensureSectionCollection } from "./section-dense-store.js";
|
|
54
|
+
import type { SectionNeedle } from "./section-needle.js";
|
|
55
|
+
import { buildSectionNeedle } from "./section-needle.js";
|
|
56
|
+
import { buildSectionIndex } from "./sections.js";
|
|
57
|
+
import {
|
|
58
|
+
type MemoryRoutingTurn,
|
|
59
|
+
type SectionIndex,
|
|
60
|
+
type SelectionSource,
|
|
61
|
+
type Slug,
|
|
62
|
+
} from "./types.js";
|
|
63
|
+
import { WorkingSet } from "./working-set.js";
|
|
64
|
+
|
|
65
|
+
export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
|
|
66
|
+
export const MEMORY_V3_LIVE = "memory-v3-live" as const;
|
|
67
|
+
|
|
68
|
+
const log = getLogger("memory-v3-shadow");
|
|
69
|
+
|
|
70
|
+
/** How many recent messages to fold into the shadow `recentContext` string. */
|
|
71
|
+
const RECENT_CONTEXT_MESSAGES = 6;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The lazily-built, process-lifetime v3 lanes. The working set is stateful
|
|
75
|
+
* (carry-forward across turns), so it is intentionally shared across every
|
|
76
|
+
* shadow turn rather than rebuilt per turn — that is the whole point of the
|
|
77
|
+
* carry-forward lane.
|
|
78
|
+
*/
|
|
79
|
+
export interface ShadowLanes {
|
|
80
|
+
sectionIndex: SectionIndex;
|
|
81
|
+
needle: SectionNeedle;
|
|
82
|
+
/** Config the dense lane needs to embed the query + search the section
|
|
83
|
+
* collection. */
|
|
84
|
+
denseConfig: AssistantConfig;
|
|
85
|
+
edgeGraph: EdgeGraph;
|
|
86
|
+
workingSet: WorkingSet;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Memoized init promise. Caching the PROMISE (not the resolved value) means
|
|
91
|
+
* concurrent first turns all await the same build instead of racing several
|
|
92
|
+
* section-index / needle / edge-graph passes.
|
|
93
|
+
*/
|
|
94
|
+
let lanesPromise: Promise<ShadowLanes> | null = null;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Drop the memoized lanes so the NEXT `getLanes` rebuilds them from scratch
|
|
98
|
+
* (fresh section index + fresh needle + fresh edge graph). The rebuild is lazy
|
|
99
|
+
* — this only clears the cache, so the cost is paid by the next caller, and
|
|
100
|
+
* concurrent first-callers after the invalidation still share a single build via
|
|
101
|
+
* the re-memoized promise. Call this whenever the underlying pages change on
|
|
102
|
+
* disk.
|
|
103
|
+
*/
|
|
104
|
+
export function invalidateLanes(): void {
|
|
105
|
+
lanesPromise = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Test-only alias for {@link invalidateLanes}. */
|
|
109
|
+
export function resetShadowLanesForTests(): void {
|
|
110
|
+
invalidateLanes();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
|
|
114
|
+
const pageIndex = await getPageIndex(getWorkspaceDir());
|
|
115
|
+
const slugs = pageIndex.entries.map((entry) => entry.slug);
|
|
116
|
+
|
|
117
|
+
// Read each page ONCE and feed BOTH forms downstream: the frontmatter-stripped
|
|
118
|
+
// body to the section index (lexical/dense matching), and the raw page
|
|
119
|
+
// (frontmatter + body) to the edge graph (so the `links:` frontmatter is
|
|
120
|
+
// available). A per-slug cache holds the parsed page so the second consumer
|
|
121
|
+
// reuses the first read.
|
|
122
|
+
const pageCache = new Map<Slug, { body: string; raw: string } | null>();
|
|
123
|
+
async function loadPage(
|
|
124
|
+
slug: Slug,
|
|
125
|
+
): Promise<{ body: string; raw: string } | null> {
|
|
126
|
+
if (pageCache.has(slug)) return pageCache.get(slug)!;
|
|
127
|
+
let loaded: { body: string; raw: string } | null = null;
|
|
128
|
+
try {
|
|
129
|
+
const page = await readPage(getWorkspaceDir(), slug);
|
|
130
|
+
if (page) loaded = { body: page.body, raw: renderPageContent(page) };
|
|
131
|
+
} catch {
|
|
132
|
+
loaded = null;
|
|
133
|
+
}
|
|
134
|
+
pageCache.set(slug, loaded);
|
|
135
|
+
return loaded;
|
|
136
|
+
}
|
|
137
|
+
// Synthetic capability slugs (skills / CLI commands) have no on-disk page, so
|
|
138
|
+
// they contribute their rendered capability content to the section index —
|
|
139
|
+
// exactly the content `page-content.ts` injects for them. This puts them in
|
|
140
|
+
// the section index, so the needle lane (and, once a backfill embeds them, the
|
|
141
|
+
// dense lane) ranks them by relevance like any other page, instead of being
|
|
142
|
+
// blindly added to the select pool every turn. Real pages read their body
|
|
143
|
+
// through the cached `loadPage`.
|
|
144
|
+
const pageBody = async (slug: Slug): Promise<string> =>
|
|
145
|
+
capabilityOrDiskBody(slug, async (s) => (await loadPage(s))?.body ?? "");
|
|
146
|
+
const pageRaw = async (slug: Slug): Promise<string> => {
|
|
147
|
+
const loaded = await loadPage(slug);
|
|
148
|
+
if (!loaded) throw new Error(`page not found: ${slug}`);
|
|
149
|
+
return loaded.raw;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const sectionIndex = await buildSectionIndex(slugs, pageBody);
|
|
153
|
+
const needle = buildSectionNeedle(sectionIndex);
|
|
154
|
+
const edgeGraph = await buildEdgeGraph(pageIndex.entries, pageRaw, {
|
|
155
|
+
hubDegree: config.memory.v3.edge.hubDegree,
|
|
156
|
+
});
|
|
157
|
+
// Ensuring the dense collection is best-effort: the needle + edge lanes and
|
|
158
|
+
// carry-forward are in-memory and independent of Qdrant, so a Qdrant outage
|
|
159
|
+
// must NOT reject lane init (which would return `null` from observeTurn and
|
|
160
|
+
// disable ALL of v3, plus poison the memoized lanes until invalidation). On
|
|
161
|
+
// failure we log and continue with the dense lane degraded — denseLane already
|
|
162
|
+
// returns no hits on a Qdrant error, and maintain/backfill re-ensure the
|
|
163
|
+
// collection once Qdrant recovers.
|
|
164
|
+
try {
|
|
165
|
+
await ensureSectionCollection(config);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
log.warn(
|
|
168
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
169
|
+
"memory-v3: section collection ensure failed; continuing with the dense lane degraded",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const workingSet = new WorkingSet(
|
|
174
|
+
config.memory.v3.workingSet.maxPages,
|
|
175
|
+
config.memory.v3.workingSet.evictWindow,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
sectionIndex,
|
|
180
|
+
needle,
|
|
181
|
+
denseConfig: config,
|
|
182
|
+
edgeGraph,
|
|
183
|
+
workingSet,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Lazy, memoized accessor for the shadow lanes. */
|
|
188
|
+
function getLanes(config: AssistantConfig): Promise<ShadowLanes> {
|
|
189
|
+
if (!lanesPromise) {
|
|
190
|
+
lanesPromise = initLanes(config).catch((err) => {
|
|
191
|
+
// Reset on failure so a transient init error doesn't permanently wedge
|
|
192
|
+
// the shadow lane — the next turn retries.
|
|
193
|
+
lanesPromise = null;
|
|
194
|
+
throw err;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return lanesPromise;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Read the live NOW.md scratchpad (the user's short "what's salient right now"
|
|
202
|
+
* file), stripped of its comment lines. Mirrors `readNowScratchpad` but reads
|
|
203
|
+
* through the light platform / strip utilities directly, keeping the v3
|
|
204
|
+
* plugin's load (and its test) free of heavier module graphs. Returns `null`
|
|
205
|
+
* when absent, empty, or unreadable.
|
|
206
|
+
*/
|
|
207
|
+
function readNowContext(): string | null {
|
|
208
|
+
const nowPath = getWorkspacePromptPath("NOW.md");
|
|
209
|
+
if (!existsSync(nowPath)) return null;
|
|
210
|
+
try {
|
|
211
|
+
const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
|
|
212
|
+
return stripped.length > 0 ? stripped : null;
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compose the situational signal threaded into pool selection: the current date
|
|
220
|
+
* plus the live NOW.md scratchpad. The date alone is a weak signal, but together
|
|
221
|
+
* with the scratchpad it lets retrieval surface a page the message never names
|
|
222
|
+
* (e.g. an anniversary that falls today). Always returns at least the date line
|
|
223
|
+
* — this mirrors the `c_now`/NOW.md signal the v2 retriever uses.
|
|
224
|
+
*/
|
|
225
|
+
function buildSituationalContext(): string {
|
|
226
|
+
const now = readNowContext();
|
|
227
|
+
const today = `Today is ${new Date().toDateString()}.`;
|
|
228
|
+
return now ? `${today}\n\n${now}` : today;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build a v3 {@link MemoryRoutingTurn} from the conversation's persisted messages.
|
|
233
|
+
* `currentMessage` is the latest user message; `recentContext` is the tail of
|
|
234
|
+
* the recent transcript; `situationalContext` carries the current date and the
|
|
235
|
+
* live NOW.md scratchpad. Returns `null` when there is no user message to route
|
|
236
|
+
* on (nothing to shadow this turn).
|
|
237
|
+
*/
|
|
238
|
+
function buildShadowTurn(
|
|
239
|
+
conversationId: string,
|
|
240
|
+
turnIndex: number,
|
|
241
|
+
): MemoryRoutingTurn | null {
|
|
242
|
+
const rows = getMessages(conversationId);
|
|
243
|
+
if (rows.length === 0) return null;
|
|
244
|
+
|
|
245
|
+
let currentMessage = "";
|
|
246
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
247
|
+
if (rows[i]!.role === "user") {
|
|
248
|
+
currentMessage = stringifyMessageContent(rows[i]!.content);
|
|
249
|
+
if (currentMessage.length > 0) break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (currentMessage.length === 0) return null;
|
|
253
|
+
|
|
254
|
+
const recentContext = rows
|
|
255
|
+
.slice(-RECENT_CONTEXT_MESSAGES)
|
|
256
|
+
.map((r) => stringifyMessageContent(r.content))
|
|
257
|
+
.filter((t) => t.length > 0)
|
|
258
|
+
.join("\n");
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
conversationId,
|
|
262
|
+
turnNumber: turnIndex,
|
|
263
|
+
currentMessage,
|
|
264
|
+
recentContext,
|
|
265
|
+
situationalContext: buildSituationalContext(),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface SelectionRow {
|
|
270
|
+
slug: Slug;
|
|
271
|
+
source: SelectionSource;
|
|
272
|
+
pinned: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Map an orchestrate result onto telemetry rows with precise per-lane source
|
|
277
|
+
* attribution.
|
|
278
|
+
*
|
|
279
|
+
* - A current selection is tagged with the candgen lane that FIRST surfaced its
|
|
280
|
+
* page this turn — `result.laneBySlug` (`"needle"` / `"dense"` / `"edge"`),
|
|
281
|
+
* recorded at pool-build time. (`"needle"` is the fallback if a selected slug
|
|
282
|
+
* is somehow absent from the lane map, which should not happen since every
|
|
283
|
+
* pooled candidate is recorded there.)
|
|
284
|
+
* - A slug in `finalInjection` but NOT re-selected this turn → `"carry-forward"`.
|
|
285
|
+
*/
|
|
286
|
+
export function attributeSelections(result: OrchestrateResult): SelectionRow[] {
|
|
287
|
+
const rows: SelectionRow[] = [];
|
|
288
|
+
const seen = new Set<Slug>();
|
|
289
|
+
for (const sel of result.currentSelections) {
|
|
290
|
+
seen.add(sel.slug);
|
|
291
|
+
rows.push({
|
|
292
|
+
slug: sel.slug,
|
|
293
|
+
source: result.laneBySlug.get(sel.slug) ?? "needle",
|
|
294
|
+
pinned: sel.pinned ? 1 : 0,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
for (const slug of result.finalInjection) {
|
|
298
|
+
if (seen.has(slug)) continue;
|
|
299
|
+
seen.add(slug);
|
|
300
|
+
rows.push({ slug, source: "carry-forward", pinned: 0 });
|
|
301
|
+
}
|
|
302
|
+
return rows;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Write the attributed selection rows to `memory_v3_selections`. */
|
|
306
|
+
export function writeSelections(
|
|
307
|
+
conversationId: string,
|
|
308
|
+
turn: number,
|
|
309
|
+
rows: SelectionRow[],
|
|
310
|
+
): void {
|
|
311
|
+
if (rows.length === 0) return;
|
|
312
|
+
const raw = getSqliteFrom(getDb());
|
|
313
|
+
// PK is (conversation_id, turn, slug); OR REPLACE keeps the write
|
|
314
|
+
// idempotent if the same turn is observed twice (e.g. a retried turn).
|
|
315
|
+
const stmt = raw.query(/*sql*/ `
|
|
316
|
+
INSERT OR REPLACE INTO memory_v3_selections (
|
|
317
|
+
conversation_id, turn, slug, source, pinned, created_at
|
|
318
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
319
|
+
`);
|
|
320
|
+
const now = Date.now();
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
stmt.run(conversationId, turn, row.slug, row.source, row.pinned, now);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Run v3 orchestration for one turn and log the selection set, returning the
|
|
328
|
+
* orchestrate result so a live caller can render it. Never throws — all
|
|
329
|
+
* failures are logged and swallowed (returning `null`) so the live turn is
|
|
330
|
+
* unaffected. Returns `null` when there is no user message to route on.
|
|
331
|
+
*/
|
|
332
|
+
export async function observeTurn(
|
|
333
|
+
conversationId: string,
|
|
334
|
+
turnIndex: number,
|
|
335
|
+
): Promise<OrchestrateResult | null> {
|
|
336
|
+
try {
|
|
337
|
+
const turn = buildShadowTurn(conversationId, turnIndex);
|
|
338
|
+
if (!turn) return null;
|
|
339
|
+
|
|
340
|
+
const cfg = getConfig();
|
|
341
|
+
const lanes = await getLanes(cfg);
|
|
342
|
+
const v3 = cfg.memory.v3;
|
|
343
|
+
const result = await orchestrate(turn, {
|
|
344
|
+
sectionIndex: lanes.sectionIndex,
|
|
345
|
+
needle: lanes.needle,
|
|
346
|
+
denseConfig: lanes.denseConfig,
|
|
347
|
+
edgeGraph: lanes.edgeGraph,
|
|
348
|
+
workingSet: lanes.workingSet,
|
|
349
|
+
needleK: v3.needleK,
|
|
350
|
+
denseK: v3.denseK,
|
|
351
|
+
edgeSeeds: v3.edge.seedCount,
|
|
352
|
+
edgePerSeed: v3.edge.perSeed,
|
|
353
|
+
edgeCap: v3.edge.cap,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const rows = attributeSelections(result);
|
|
357
|
+
writeSelections(conversationId, turnIndex, rows);
|
|
358
|
+
return result;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
log.warn(
|
|
361
|
+
{ err: err instanceof Error ? err.message : String(err), conversationId },
|
|
362
|
+
"memory-v3 orchestration failed (non-fatal)",
|
|
363
|
+
);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Test-facing shadow wrapper: gate on the shadow flag, then run v3
|
|
370
|
+
* orchestration for one turn and log the selection set. Production injection
|
|
371
|
+
* goes through `produce()` → {@link observeTurn} directly (which checks both
|
|
372
|
+
* flags); this wrapper exists so tests can drive shadow observation in
|
|
373
|
+
* isolation. Never throws — all failures are logged and swallowed so the live
|
|
374
|
+
* turn is unaffected.
|
|
375
|
+
*/
|
|
376
|
+
export async function runShadowObservation(
|
|
377
|
+
conversationId: string,
|
|
378
|
+
turnIndex: number,
|
|
379
|
+
): Promise<void> {
|
|
380
|
+
if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
|
|
381
|
+
await observeTurn(conversationId, turnIndex);
|
|
382
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export type Slug = string;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Injection-block id for the v3 live `<memory>` block. Shared between the
|
|
5
|
+
* producer (the v3 injector in `shadow-plugin.ts`) and the v2-suppression
|
|
6
|
+
* consumer (`conversation-runtime-assembly.ts`), which keys off this id to
|
|
7
|
+
* detect that v3 actually produced a block this turn. Keeping it in one place
|
|
8
|
+
* makes a rename a compile error on both sides instead of a silent
|
|
9
|
+
* suppression bypass.
|
|
10
|
+
*/
|
|
11
|
+
export const MEMORY_V3_BLOCK_ID = "memory-v3" as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A single section of a page: the lead (text before the first `## heading`,
|
|
15
|
+
* ordinal 0) or a heading-delimited block. Over-long sections are split into
|
|
16
|
+
* multiple ordered `Section`s, each with its own consecutive `ordinal`, so each
|
|
17
|
+
* fits a typical embedding window. `text` is prefixed with a
|
|
18
|
+
* `${lastSlugSegment} — ${title}` head line for lexical/dense matching.
|
|
19
|
+
*/
|
|
20
|
+
export interface Section {
|
|
21
|
+
article: Slug;
|
|
22
|
+
title: string;
|
|
23
|
+
text: string;
|
|
24
|
+
ordinal: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A flat, deterministic index of every page's sections plus an article→section
|
|
29
|
+
* lookup. `byArticle` maps each article slug to the indices (into `sections`)
|
|
30
|
+
* of that article's sections, in order.
|
|
31
|
+
*/
|
|
32
|
+
export interface SectionIndex {
|
|
33
|
+
sections: Section[];
|
|
34
|
+
byArticle: Map<Slug, number[]>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A page selected from the candidate pool, with whether the turn centers on it. */
|
|
38
|
+
export interface SelectedPage {
|
|
39
|
+
slug: Slug;
|
|
40
|
+
pinned: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WorkingSetEntry {
|
|
44
|
+
slug: Slug;
|
|
45
|
+
selectedAtTurn: number;
|
|
46
|
+
pinned: boolean;
|
|
47
|
+
lastSeenTurn: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MemoryRoutingTurn {
|
|
51
|
+
conversationId: string;
|
|
52
|
+
turnNumber: number;
|
|
53
|
+
currentMessage: string;
|
|
54
|
+
recentContext: string;
|
|
55
|
+
/**
|
|
56
|
+
* Optional situational signal — the current date plus the live NOW.md
|
|
57
|
+
* scratchpad — so a leaf or page can be routed/selected on a date or
|
|
58
|
+
* live-state cue the message itself never names (e.g. a person whose
|
|
59
|
+
* anniversary is today). Omitted when unavailable; the router and selector
|
|
60
|
+
* render nothing for an undefined value.
|
|
61
|
+
*/
|
|
62
|
+
situationalContext?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Canonical ordered list of the lane sources recorded per selection. The
|
|
67
|
+
* {@link SelectionSource} type is derived from this so a new lane is added in
|
|
68
|
+
* exactly one place and the runtime list (used for telemetry roll-ups and
|
|
69
|
+
* source validation) can never drift from the type.
|
|
70
|
+
*
|
|
71
|
+
* The `memory_v3_selections.source` column is free-text, so tightening this set
|
|
72
|
+
* needs no migration: any historical rows with older labels still read back
|
|
73
|
+
* fine via the permissive `z.string()` row schema.
|
|
74
|
+
*/
|
|
75
|
+
export const SELECTION_SOURCES = [
|
|
76
|
+
"needle",
|
|
77
|
+
"dense",
|
|
78
|
+
"edge",
|
|
79
|
+
"carry-forward",
|
|
80
|
+
] as const;
|
|
81
|
+
|
|
82
|
+
export type SelectionSource = (typeof SELECTION_SOURCES)[number];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The candidate-generation lanes — the strict subset of {@link SelectionSource}
|
|
86
|
+
* a pooled candidate can be tagged with at pool-build time. (`carry-forward` is
|
|
87
|
+
* the one source assigned later, by the orchestrator's working-set union, not by
|
|
88
|
+
* a candgen lane.) Defined as `Exclude<SelectionSource, "carry-forward">` so it
|
|
89
|
+
* can never drift from {@link SELECTION_SOURCES}: adding a lane there widens this
|
|
90
|
+
* automatically.
|
|
91
|
+
*/
|
|
92
|
+
export type CandidateLane = Exclude<SelectionSource, "carry-forward">;
|
|
@@ -42,13 +42,16 @@ export class WorkingSet {
|
|
|
42
42
|
/**
|
|
43
43
|
* Prune the working set, in order:
|
|
44
44
|
* 1. Core slugs never belong here — drop any entry whose slug is core.
|
|
45
|
+
* The section-lane pipeline has no core set, so `coreSlugs` defaults to
|
|
46
|
+
* empty (no-op); the tree pipeline passed its core slugs here. Stale and
|
|
47
|
+
* cap eviction semantics are identical either way.
|
|
45
48
|
* 2. Pinned entries never evict.
|
|
46
49
|
* 3. Window eviction: drop a non-pinned entry unseen for more than
|
|
47
50
|
* `evictWindow` turns.
|
|
48
51
|
* 4. Cap eviction: while over `maxPages`, evict the lowest-scoring
|
|
49
52
|
* non-pinned entry first (ties broken deterministically).
|
|
50
53
|
*/
|
|
51
|
-
evict(currentTurn: number, coreSlugs: Set<Slug>): void {
|
|
54
|
+
evict(currentTurn: number, coreSlugs: Set<Slug> = new Set()): void {
|
|
52
55
|
// 1. Core slugs are owned by core, not the working set.
|
|
53
56
|
for (const slug of this.entries.keys()) {
|
|
54
57
|
if (coreSlugs.has(slug)) {
|