@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62
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 +273 -121
- package/package.json +2 -2
- package/scripts/sync-llm-catalog.ts +2 -0
- 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__/build-persisted-content.test.ts +75 -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__/config-managed-gemini-defaults.test.ts +2 -46
- 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-execution-feature-gates.test.ts +5 -4
- 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__/embedding-managed-proxy-selection.test.ts +6 -40
- 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-catalog-parity.test.ts +4 -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__/persistence-secret-redaction.test.ts +78 -0
- 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__/retry-thinking-adaptive-only.test.ts +245 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- 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__/tool-start-timestamp.test.ts +218 -0
- 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/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/responses/conversation-message.ts +72 -13
- 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 -56
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/loader.ts +5 -22
- 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 +120 -42
- 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/shared.ts +9 -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/embedding-backend.ts +5 -22
- 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 +57 -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 +57 -7
- 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,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — orchestrator composing the section-grain lanes into one turn.
|
|
3
|
+
*
|
|
4
|
+
* Each turn runs:
|
|
5
|
+
* 1. Candidate generation over three deterministic lanes:
|
|
6
|
+
* - the section-grain BM25 needle (`SectionNeedle.query`, KB articles),
|
|
7
|
+
* - the dense lane (`denseLane`, KD articles), and
|
|
8
|
+
* - link-graph edge expansion (`edgeExpand`) over the top needle+dense
|
|
9
|
+
* article seeds.
|
|
10
|
+
* Each lane only ever ADDS candidates, so the pool is recall-safe by
|
|
11
|
+
* construction.
|
|
12
|
+
* 2. Build the unified candidate pool of `{ slug, descriptor }`. The
|
|
13
|
+
* descriptor is the matched section's text for needle/dense hits (resolved
|
|
14
|
+
* via the section index); for edge-only pages it is the curated `links`
|
|
15
|
+
* description when the traversed edge carried one, else the page's best
|
|
16
|
+
* section against the query. Edge-only pages record NO matched section, so
|
|
17
|
+
* they inject the FULL page (the lead the query never hit is often empty;
|
|
18
|
+
* the link-relevant content is elsewhere) — the best-section text is only a
|
|
19
|
+
* descriptor fallback. Synthetic capability pages (skills / CLI commands)
|
|
20
|
+
* carry sections in the section index too, so they enter the pool through
|
|
21
|
+
* the lanes like any other page rather than being always-added.
|
|
22
|
+
* 3. A SINGLE forced-tool select (`selectPool`) over the whole pool. The
|
|
23
|
+
* result is this turn's selections.
|
|
24
|
+
* 4. Age the carry-forward working set to this turn (evict stale non-pinned
|
|
25
|
+
* entries, then the cap) and snapshot it — the pages carried in from
|
|
26
|
+
* EARLIER turns.
|
|
27
|
+
* 5. Final injection = unique union of this turn's selected slugs and that
|
|
28
|
+
* carried-forward set, so pages selected on earlier turns carry forward
|
|
29
|
+
* even when this turn does not re-select them.
|
|
30
|
+
* 6. Record this turn's selections into the working set for LATER turns. This
|
|
31
|
+
* runs AFTER the snapshot so the cap is spent on genuinely carried pages,
|
|
32
|
+
* not on this turn's selections (which are injected directly) — otherwise a
|
|
33
|
+
* turn selecting more pages than the cap would evict the entire carry.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { AssistantConfig } from "../../../config/schema.js";
|
|
37
|
+
import { denseLane } from "./dense.js";
|
|
38
|
+
import type { EdgeGraph } from "./edge.js";
|
|
39
|
+
import { edgeExpand } from "./edge.js";
|
|
40
|
+
import type { PoolCandidate } from "./pool-select.js";
|
|
41
|
+
import { selectPool } from "./pool-select.js";
|
|
42
|
+
import type { SectionNeedle } from "./section-needle.js";
|
|
43
|
+
import type {
|
|
44
|
+
CandidateLane,
|
|
45
|
+
MemoryRoutingTurn,
|
|
46
|
+
Section,
|
|
47
|
+
SectionIndex,
|
|
48
|
+
SelectedPage,
|
|
49
|
+
Slug,
|
|
50
|
+
} from "./types.js";
|
|
51
|
+
import { WorkingSet } from "./working-set.js";
|
|
52
|
+
|
|
53
|
+
/** Default number of BM25 needle articles to fold into the pool. */
|
|
54
|
+
export const DEFAULT_NEEDLE_K = 100;
|
|
55
|
+
/** Default number of dense-lane articles to fold into the pool. */
|
|
56
|
+
export const DEFAULT_DENSE_K = 100;
|
|
57
|
+
|
|
58
|
+
export interface OrchestrateDeps {
|
|
59
|
+
sectionIndex: SectionIndex;
|
|
60
|
+
needle: SectionNeedle;
|
|
61
|
+
/** Config the dense lane needs to embed the query + search the section
|
|
62
|
+
* collection. */
|
|
63
|
+
denseConfig: AssistantConfig;
|
|
64
|
+
edgeGraph: EdgeGraph;
|
|
65
|
+
workingSet: WorkingSet;
|
|
66
|
+
/** Number of BM25 needle articles. Defaults to {@link DEFAULT_NEEDLE_K}. */
|
|
67
|
+
needleK?: number;
|
|
68
|
+
/** Number of dense-lane articles. Defaults to {@link DEFAULT_DENSE_K}. */
|
|
69
|
+
denseK?: number;
|
|
70
|
+
/** Number of top needle+dense seeds expanded. When omitted, the edge lane's
|
|
71
|
+
* own default applies (canonical value: `memory.v3.edge.seedCount`). */
|
|
72
|
+
edgeSeeds?: number;
|
|
73
|
+
/** Neighbours surfaced per expanded edge seed. When omitted, the edge lane's
|
|
74
|
+
* own default applies (canonical value: `memory.v3.edge.perSeed`). */
|
|
75
|
+
edgePerSeed?: number;
|
|
76
|
+
/** Hard cap on total edge-lane surfaced articles. When omitted, the edge
|
|
77
|
+
* lane's own default applies (canonical value: `memory.v3.edge.cap`). */
|
|
78
|
+
edgeCap?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface OrchestrateResult {
|
|
82
|
+
/** This turn's selections, deduped by slug (pinned flags ORed). */
|
|
83
|
+
currentSelections: SelectedPage[];
|
|
84
|
+
/** The carried-forward set: selections from EARLIER turns, aged to this turn
|
|
85
|
+
* (snapshotted before this turn's selections are recorded). */
|
|
86
|
+
workingSetUnion: Set<Slug>;
|
|
87
|
+
/** Slugs to inject: this turn's selections ∪ the carried-forward working set. */
|
|
88
|
+
finalInjection: Slug[];
|
|
89
|
+
/** The matched `Section` for each pooled slug that had one, keyed by slug.
|
|
90
|
+
* Populated from the lane hits and consumed by the injector to render each
|
|
91
|
+
* selected slug's matched section (progressive disclosure). */
|
|
92
|
+
sectionBySlug: Map<Slug, Section>;
|
|
93
|
+
/** The candgen lane that FIRST surfaced each pooled slug, keyed by slug.
|
|
94
|
+
* Insertion order is needle → dense → edge, so a page surfaced by more than
|
|
95
|
+
* one lane records the highest-precedence one. Consumed by the selection
|
|
96
|
+
* telemetry to attribute each selection to its true lane. */
|
|
97
|
+
laneBySlug: Map<Slug, CandidateLane>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Stable-order de-duplication preserving first occurrence. */
|
|
101
|
+
function unique<T>(items: Iterable<T>): T[] {
|
|
102
|
+
return [...new Set(items)];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function orchestrate(
|
|
106
|
+
turn: MemoryRoutingTurn,
|
|
107
|
+
deps: OrchestrateDeps,
|
|
108
|
+
): Promise<OrchestrateResult> {
|
|
109
|
+
const needleK = deps.needleK ?? DEFAULT_NEEDLE_K;
|
|
110
|
+
const denseK = deps.denseK ?? DEFAULT_DENSE_K;
|
|
111
|
+
const { sections } = deps.sectionIndex;
|
|
112
|
+
|
|
113
|
+
// Step 1: needle (sync BM25) and dense (async embed + Qdrant) lanes run in
|
|
114
|
+
// parallel. Both return distinct articles each tagged with their best-scoring
|
|
115
|
+
// section index/ordinal.
|
|
116
|
+
const [needled, densed] = await Promise.all([
|
|
117
|
+
Promise.resolve(deps.needle.query(turn.currentMessage, needleK)),
|
|
118
|
+
denseLane(deps.denseConfig, turn.currentMessage, denseK),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// The pool accumulates one entry per distinct article. `sectionBySlug` records
|
|
122
|
+
// the matched `Section` (when one is known) for downstream injection;
|
|
123
|
+
// `laneBySlug` records the lane that first surfaced each slug for telemetry.
|
|
124
|
+
const poolBySlug = new Map<Slug, PoolCandidate>();
|
|
125
|
+
const sectionBySlug = new Map<Slug, Section>();
|
|
126
|
+
const laneBySlug = new Map<Slug, CandidateLane>();
|
|
127
|
+
|
|
128
|
+
// Add one pool candidate, recording its matched `Section` (when known) for
|
|
129
|
+
// downstream injection and the `lane` that surfaced it for telemetry.
|
|
130
|
+
// `descriptor` overrides the section text when supplied (the edge lane prefers
|
|
131
|
+
// a curated `links` description). The first lane to surface a slug wins the
|
|
132
|
+
// pool entry, the recorded section, AND the recorded lane — so the
|
|
133
|
+
// needle → dense → edge call order encodes lane precedence.
|
|
134
|
+
const addCandidate = (
|
|
135
|
+
slug: Slug,
|
|
136
|
+
section: Section | undefined,
|
|
137
|
+
descriptor: string | undefined,
|
|
138
|
+
lane: CandidateLane,
|
|
139
|
+
): void => {
|
|
140
|
+
if (section && !sectionBySlug.has(slug)) {
|
|
141
|
+
sectionBySlug.set(slug, section);
|
|
142
|
+
}
|
|
143
|
+
if (poolBySlug.has(slug)) return;
|
|
144
|
+
laneBySlug.set(slug, lane);
|
|
145
|
+
poolBySlug.set(slug, {
|
|
146
|
+
slug,
|
|
147
|
+
descriptor: descriptor ?? section?.text ?? "",
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Step 2a: needle hits — descriptor is the matched section's text. `section`
|
|
152
|
+
// is an index into `sections`.
|
|
153
|
+
for (const hit of needled) {
|
|
154
|
+
addCandidate(hit.article, sections[hit.section], undefined, "needle");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Step 2b: dense hits — `section` is the matched ORDINAL; resolve it to the
|
|
158
|
+
// concrete `Section` via the section index. Falls back to undefined (blank
|
|
159
|
+
// descriptor) if the ordinal is not in the in-memory index.
|
|
160
|
+
for (const hit of densed) {
|
|
161
|
+
// A deleted page's points can linger in Qdrant; keep only live-index
|
|
162
|
+
// articles. The section index is rebuilt from `getPageIndex` at `initLanes`,
|
|
163
|
+
// so `byArticle` holds exactly the live pages (synthetic capability slugs
|
|
164
|
+
// included) — only truly-deleted pages are dropped here.
|
|
165
|
+
if (!deps.sectionIndex.byArticle.has(hit.article)) continue;
|
|
166
|
+
addCandidate(
|
|
167
|
+
hit.article,
|
|
168
|
+
sectionByOrdinal(deps.sectionIndex, hit.article, hit.section),
|
|
169
|
+
undefined,
|
|
170
|
+
"dense",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 2c: edge expansion over the top needle+dense article seeds. `alive`
|
|
175
|
+
// skips slugs already pooled. An edge-only page was surfaced because its
|
|
176
|
+
// NEIGHBOUR matched — the query did not lexically hit the page itself, so
|
|
177
|
+
// `bestSection` returns its first/lead section on a zero-score match. That
|
|
178
|
+
// lead is often empty for heading-structured pages, and it is the curated
|
|
179
|
+
// `links` description (not the lead) that made the candidate relevant. So we
|
|
180
|
+
// record NO matched section for edge-only pages (pass `undefined`), which
|
|
181
|
+
// makes injection fall back to the FULL page — where the link-relevant content
|
|
182
|
+
// lives. `bestSection`'s text is kept only as the select-pool DESCRIPTOR
|
|
183
|
+
// fallback for when the traversed edge carried no curated `links` description.
|
|
184
|
+
const seeds = unique<Slug>([
|
|
185
|
+
...needled.map((h) => h.article),
|
|
186
|
+
...densed.map((h) => h.article),
|
|
187
|
+
]);
|
|
188
|
+
const surfaced = edgeExpand(deps.edgeGraph, seeds, {
|
|
189
|
+
seedCount: deps.edgeSeeds,
|
|
190
|
+
perSeed: deps.edgePerSeed,
|
|
191
|
+
cap: deps.edgeCap,
|
|
192
|
+
alive: (slug) => !poolBySlug.has(slug),
|
|
193
|
+
});
|
|
194
|
+
for (const neighbor of surfaced) {
|
|
195
|
+
const best = deps.needle.bestSection(neighbor.article, turn.currentMessage);
|
|
196
|
+
const fallbackDescriptor = best >= 0 ? sections[best]?.text : undefined;
|
|
197
|
+
addCandidate(
|
|
198
|
+
neighbor.article,
|
|
199
|
+
undefined,
|
|
200
|
+
neighbor.description ?? fallbackDescriptor,
|
|
201
|
+
"edge",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Step 3: a SINGLE forced-tool select over the unified pool.
|
|
206
|
+
const selected = await selectPool([...poolBySlug.values()], turn);
|
|
207
|
+
const bySlug = new Map<Slug, SelectedPage>();
|
|
208
|
+
for (const page of selected) {
|
|
209
|
+
const existing = bySlug.get(page.slug);
|
|
210
|
+
bySlug.set(page.slug, {
|
|
211
|
+
slug: page.slug,
|
|
212
|
+
pinned: (existing?.pinned ?? false) || page.pinned,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const currentSelections = [...bySlug.values()];
|
|
216
|
+
|
|
217
|
+
// Step 4: age the carry-forward set to this turn (drop stale non-pinned
|
|
218
|
+
// entries, then the cap) and snapshot it. This is the set carried in from
|
|
219
|
+
// EARLIER turns; recording this turn happens afterward (step 6) so the cap is
|
|
220
|
+
// spent on genuinely carried pages, not on this turn's selections (which are
|
|
221
|
+
// injected directly anyway).
|
|
222
|
+
deps.workingSet.evict(turn.turnNumber);
|
|
223
|
+
const workingSetUnion = deps.workingSet.union();
|
|
224
|
+
|
|
225
|
+
// Step 5: final injection = this turn's selections ∪ the carried-forward set,
|
|
226
|
+
// so pages selected on earlier turns carry forward even when this turn does
|
|
227
|
+
// not re-select them.
|
|
228
|
+
const finalInjection = unique<Slug>([
|
|
229
|
+
...currentSelections.map((s) => s.slug),
|
|
230
|
+
...workingSetUnion,
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
// Step 6: record this turn's selections so they carry forward to LATER turns.
|
|
234
|
+
for (const sel of currentSelections) {
|
|
235
|
+
deps.workingSet.recordSelection(sel.slug, turn.turnNumber, sel.pinned);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
currentSelections,
|
|
240
|
+
workingSetUnion,
|
|
241
|
+
finalInjection,
|
|
242
|
+
sectionBySlug,
|
|
243
|
+
laneBySlug,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Resolve a dense-lane hit's matched ordinal to the concrete `Section` in the
|
|
249
|
+
* in-memory index. The dense store keys sections by `(article, ordinal)`, so we
|
|
250
|
+
* scan the article's sections for the matching ordinal. Returns `undefined`
|
|
251
|
+
* when the article or ordinal is not in the index (e.g. the dense store is
|
|
252
|
+
* ahead of the in-memory rebuild).
|
|
253
|
+
*/
|
|
254
|
+
function sectionByOrdinal(
|
|
255
|
+
index: SectionIndex,
|
|
256
|
+
article: Slug,
|
|
257
|
+
ordinal: number,
|
|
258
|
+
): Section | undefined {
|
|
259
|
+
const indices = index.byArticle.get(article);
|
|
260
|
+
if (!indices) return undefined;
|
|
261
|
+
for (const i of indices) {
|
|
262
|
+
const section = index.sections[i];
|
|
263
|
+
if (section && section.ordinal === ordinal) return section;
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default-memory-v3-shadow",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "First-party default plugin housing the memory-v3 shadow/live orchestration engine and its injector.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.12.0"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@vellumai/plugin-api": "^0.8.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readPage, renderPageContent } from "../../../memory/v2/page-store.js";
|
|
2
|
+
import { getWorkspaceDir } from "../../../util/platform.js";
|
|
3
|
+
import { renderCapabilityContent } from "./capabilities.js";
|
|
4
|
+
import type { Section, Slug } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prefix `body` with the `# memory/concepts/<slug>.md` header marker the v3
|
|
8
|
+
* `<memory>` block uses. Both the full-page and matched-section renderers emit
|
|
9
|
+
* this exact marker, and the v2 stripper recognizes pages by it — so the two
|
|
10
|
+
* call sites MUST stay byte-identical, which is why this lives in one place.
|
|
11
|
+
*/
|
|
12
|
+
function withConceptHeader(slug: Slug, body: string): string {
|
|
13
|
+
return `# memory/concepts/${slug}.md\n${body}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render a selected page's full content for the v3 `<memory>` block. Mirrors
|
|
18
|
+
* the v2 dynamic-memory layout (`# memory/concepts/<slug>.md\n<frontmatter+body>`)
|
|
19
|
+
* so the working-set block reads like v2's. A missing page (or any read
|
|
20
|
+
* failure) degrades to "" — `renderMemoryBlock` still emits a line for the
|
|
21
|
+
* slug, and a blank section is preferable to throwing into the turn.
|
|
22
|
+
*
|
|
23
|
+
* Synthetic capability slugs (skills, `assistant` CLI commands) have no on-disk
|
|
24
|
+
* page; they resolve through {@link renderCapabilityContent} instead of
|
|
25
|
+
* `readPage`. A non-null result (including "") means the slug was a capability
|
|
26
|
+
* slug and was handled here.
|
|
27
|
+
*
|
|
28
|
+
* Shared by the live injector (`shadow-plugin.ts`) and the inspector
|
|
29
|
+
* selection-log store (`selection-log-store.ts`) so the inspector's rendered
|
|
30
|
+
* block is byte-identical to what live injection produces.
|
|
31
|
+
*/
|
|
32
|
+
export async function renderV3PageContent(slug: Slug): Promise<string> {
|
|
33
|
+
const capability = renderCapabilityContent(slug);
|
|
34
|
+
if (capability !== null) return capability;
|
|
35
|
+
try {
|
|
36
|
+
const page = await readPage(getWorkspaceDir(), slug);
|
|
37
|
+
if (!page) return "";
|
|
38
|
+
const content = renderPageContent(page).trim();
|
|
39
|
+
if (content.length === 0) return "";
|
|
40
|
+
return withConceptHeader(slug, content);
|
|
41
|
+
} catch {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Render a slug's matched section for the v3 `<memory>` block (progressive
|
|
48
|
+
* disclosure: inject only the section the lanes matched, not the full page).
|
|
49
|
+
* Uses the same `# memory/concepts/<slug>.md` header marker as
|
|
50
|
+
* {@link renderV3PageContent} so the block reads like v2's and the v2 stripper
|
|
51
|
+
* recognizes it.
|
|
52
|
+
*
|
|
53
|
+
* - Capability slugs (skills, `assistant` CLI commands) have no on-disk
|
|
54
|
+
* section, so they always render their capability content via
|
|
55
|
+
* {@link renderCapabilityContent}, never a section.
|
|
56
|
+
* - When `section` is undefined (e.g. a carry-forward page with no current
|
|
57
|
+
* match), fall back to {@link renderV3PageContent} (the full/lead page) so the
|
|
58
|
+
* slug still contributes content.
|
|
59
|
+
*/
|
|
60
|
+
export async function renderV3SectionContent(
|
|
61
|
+
slug: Slug,
|
|
62
|
+
section: Section | undefined,
|
|
63
|
+
): Promise<string> {
|
|
64
|
+
const capability = renderCapabilityContent(slug);
|
|
65
|
+
if (capability !== null) return capability;
|
|
66
|
+
if (!section) return renderV3PageContent(slug);
|
|
67
|
+
|
|
68
|
+
const text = section.text.trim();
|
|
69
|
+
if (text.length === 0) return "";
|
|
70
|
+
return withConceptHeader(slug, text);
|
|
71
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v3 — single pool selector.
|
|
3
|
+
*
|
|
4
|
+
* Runs a SINGLE forced-tool call over one unified candidate pool. Each candidate
|
|
5
|
+
* is a page slug paired with a per-candidate `descriptor` the caller supplies —
|
|
6
|
+
* the matched section text for a needle/dense hit, or a curated link description
|
|
7
|
+
* for an edge page. The caller assembles the pool by unioning the section lanes;
|
|
8
|
+
* the pool selector only decides which of those candidates the reply will draw
|
|
9
|
+
* on.
|
|
10
|
+
*
|
|
11
|
+
* No cache breakpoint. The pool is recomputed from per-turn section matches and
|
|
12
|
+
* is therefore dynamic per turn, so it carries no `cache_control` breakpoint:
|
|
13
|
+
* caching a prefix that changes every turn would never hit. Carry-forward (the
|
|
14
|
+
* working set unioned in by the orchestrator) is the cache mechanism here, not a
|
|
15
|
+
* static prefix breakpoint.
|
|
16
|
+
*
|
|
17
|
+
* Failure handling is recall-safe by construction:
|
|
18
|
+
* - explicit `ids` → select exactly those candidates,
|
|
19
|
+
* - explicit empty `ids: []` → select none (deliberate abstention),
|
|
20
|
+
* - omitted `ids` → keep ALL candidates (the recall-safe "all of these are
|
|
21
|
+
* relevant" signal),
|
|
22
|
+
* - infrastructure failure (provider unavailable, a throw that survived the
|
|
23
|
+
* provider's own retries, no usable `tool_use`, or a schema mismatch) →
|
|
24
|
+
* select nothing after a short re-prompt retry, degrading to the
|
|
25
|
+
* deterministic recall lanes the orchestrator unions in regardless.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
extractToolUse,
|
|
32
|
+
getConfiguredProvider,
|
|
33
|
+
} from "../../../providers/provider-send-message.js";
|
|
34
|
+
import type { Message, ToolDefinition } from "../../../providers/types.js";
|
|
35
|
+
import { getLogger } from "../../../util/logger.js";
|
|
36
|
+
import { truncate } from "../../../util/truncate.js";
|
|
37
|
+
import { retryForResult } from "./llm-retry.js";
|
|
38
|
+
import type { MemoryRoutingTurn, SelectedPage, Slug } from "./types.js";
|
|
39
|
+
|
|
40
|
+
const log = getLogger("memory-v3-pool-select");
|
|
41
|
+
|
|
42
|
+
/** A candidate page in the unified pool, with the descriptor that justifies it. */
|
|
43
|
+
export interface PoolCandidate {
|
|
44
|
+
slug: Slug;
|
|
45
|
+
/**
|
|
46
|
+
* The text that justifies this candidate: a matched section for a
|
|
47
|
+
* needle/dense hit, or a curated link description for an edge page. Rendered
|
|
48
|
+
* (truncated) in the numbered candidate list the selector judges against.
|
|
49
|
+
*/
|
|
50
|
+
descriptor: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Tool name forced via `tool_choice`. Shared constant so tests can match it. */
|
|
54
|
+
const SELECT_PAGES_TOOL_NAME = "select_pages";
|
|
55
|
+
|
|
56
|
+
/** Descriptors are truncated to keep the candidate list compact. */
|
|
57
|
+
const DESCRIPTOR_MAX_CHARS = 400;
|
|
58
|
+
|
|
59
|
+
const SelectPagesSchema = z.object({
|
|
60
|
+
// Optional: an omitted `ids` field is the recall-safe "keep everything"
|
|
61
|
+
// signal, distinct from an explicit empty array (deliberate abstention).
|
|
62
|
+
ids: z.array(z.number().int()).optional(),
|
|
63
|
+
pinned_ids: z.array(z.number().int()).optional(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const SELECT_PAGES_TOOL: ToolDefinition = {
|
|
67
|
+
name: SELECT_PAGES_TOOL_NAME,
|
|
68
|
+
description:
|
|
69
|
+
"Select the candidate pages whose content the reply would directly draw " +
|
|
70
|
+
"on. Lean inclusive — when in doubt, keep a candidate; for a list or " +
|
|
71
|
+
'"all of X" request keep every candidate that belongs. Pass `pinned_ids` ' +
|
|
72
|
+
"for pages the conversation is centrally about. Omit `ids` only as a " +
|
|
73
|
+
"recall-safe fallback when you cannot judge the pool (keeps every " +
|
|
74
|
+
"candidate); return `[]` when candidates are present but none are " +
|
|
75
|
+
"relevant.",
|
|
76
|
+
input_schema: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
ids: {
|
|
80
|
+
type: "array",
|
|
81
|
+
items: { type: "integer" },
|
|
82
|
+
},
|
|
83
|
+
pinned_ids: {
|
|
84
|
+
type: "array",
|
|
85
|
+
items: { type: "integer" },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const SYSTEM_PROMPT = `You are given a pool of candidate memory pages, each surfaced because some part of it matched the conversation. Select every page whose specific content the reply to THIS message would directly draw on.
|
|
92
|
+
|
|
93
|
+
Lean inclusive: recall matters more than precision here, so when a candidate could plausibly inform the reply, keep it. For a list or an "all of X" request, keep EVERY candidate that belongs to X rather than guessing a representative subset.
|
|
94
|
+
|
|
95
|
+
A page can be relevant because of the current situation — the date or the live scratchpad — not only the message: keep a page the situation makes pertinent (e.g. a person whose anniversary is today).
|
|
96
|
+
|
|
97
|
+
If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the pool (keeps every candidate); return \`[]\` when candidates are present but none are relevant.`;
|
|
98
|
+
|
|
99
|
+
/** Collapse a descriptor to one line and cap its length for the candidate list. */
|
|
100
|
+
function renderDescriptor(descriptor: string): string {
|
|
101
|
+
return truncate(descriptor.replace(/\s+/g, " ").trim(), DESCRIPTOR_MAX_CHARS);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Render the numbered candidate list: one `[i] slug — descriptor` line per
|
|
106
|
+
* candidate, descriptors collapsed to one line and length-capped. The pool is
|
|
107
|
+
* dynamic per turn, so this block is NOT cached (see the module doc).
|
|
108
|
+
*/
|
|
109
|
+
function renderCandidateList(pool: PoolCandidate[]): string {
|
|
110
|
+
const lines = pool.map(
|
|
111
|
+
(c, i) => `[${i + 1}] ${c.slug} — ${renderDescriptor(c.descriptor)}`,
|
|
112
|
+
);
|
|
113
|
+
return `<candidates>\n${lines.join("\n")}\n</candidates>`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Run the single forced-tool selector over the unified candidate pool. Returns
|
|
118
|
+
* the pages to inject.
|
|
119
|
+
*
|
|
120
|
+
* An omitted `ids` keeps ALL candidates (the recall-safe "all of these are
|
|
121
|
+
* relevant" signal); an explicit `[]` keeps none; an infrastructure failure
|
|
122
|
+
* (after a short re-prompt retry) keeps none, degrading to the deterministic
|
|
123
|
+
* recall lanes the orchestrator unions in.
|
|
124
|
+
*/
|
|
125
|
+
export async function selectPool(
|
|
126
|
+
pool: PoolCandidate[],
|
|
127
|
+
turn: MemoryRoutingTurn,
|
|
128
|
+
): Promise<SelectedPage[]> {
|
|
129
|
+
if (pool.length === 0) return [];
|
|
130
|
+
|
|
131
|
+
const keepAll = (): SelectedPage[] =>
|
|
132
|
+
pool.map((c) => ({ slug: c.slug, pinned: false }));
|
|
133
|
+
|
|
134
|
+
const provider = await getConfiguredProvider("memoryV3SelectL2");
|
|
135
|
+
if (!provider) {
|
|
136
|
+
log.warn(
|
|
137
|
+
{ candidateCount: pool.length },
|
|
138
|
+
"pool selector provider unavailable; degrading to deterministic lanes",
|
|
139
|
+
);
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The pool is dynamic per turn, so there is no static cache breakpoint here;
|
|
144
|
+
// carry-forward is the cache mechanism. The candidate list and the per-turn
|
|
145
|
+
// context go in a single plain text block.
|
|
146
|
+
const userMsg: Message = {
|
|
147
|
+
role: "user",
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text:
|
|
152
|
+
`${renderCandidateList(pool)}\n` +
|
|
153
|
+
(turn.situationalContext
|
|
154
|
+
? `<situation>${turn.situationalContext}</situation>\n`
|
|
155
|
+
: "") +
|
|
156
|
+
`<recent_context>${turn.recentContext}</recent_context>\n` +
|
|
157
|
+
`<current_message>${turn.currentMessage}</current_message>`,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// One forced-tool call, retried a few times so a transient malformed response
|
|
163
|
+
// (no usable tool_use, or tool input that fails the schema) re-prompts before
|
|
164
|
+
// we give up. `null` from an attempt means "unusable, retry"; the provider
|
|
165
|
+
// layer already backs off transient throws, so this loop adds no delay.
|
|
166
|
+
const parsed = await retryForResult(async () => {
|
|
167
|
+
const response = await provider.sendMessage([userMsg], {
|
|
168
|
+
tools: [SELECT_PAGES_TOOL],
|
|
169
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
170
|
+
config: {
|
|
171
|
+
callSite: "memoryV3SelectL2" as const,
|
|
172
|
+
tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
const toolBlock = extractToolUse(response);
|
|
176
|
+
if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) return null;
|
|
177
|
+
const result = SelectPagesSchema.safeParse(toolBlock.input);
|
|
178
|
+
return result.success ? result.data : null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (parsed === null) {
|
|
182
|
+
log.warn(
|
|
183
|
+
{ candidateCount: pool.length },
|
|
184
|
+
"pool selector could not obtain a selection after retries; degrading to deterministic lanes",
|
|
185
|
+
);
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Omitted `ids` is the recall-safe "keep all candidates" signal.
|
|
190
|
+
if (parsed.ids === undefined) return keepAll();
|
|
191
|
+
|
|
192
|
+
const pinned = new Set(parsed.pinned_ids ?? []);
|
|
193
|
+
|
|
194
|
+
// Map 1-based IDs back to candidate slugs, dropping out-of-range IDs without
|
|
195
|
+
// throwing. De-duplicate while preserving model-returned order.
|
|
196
|
+
const seen = new Set<number>();
|
|
197
|
+
const selected: SelectedPage[] = [];
|
|
198
|
+
for (const id of parsed.ids) {
|
|
199
|
+
if (id < 1 || id > pool.length || seen.has(id)) continue;
|
|
200
|
+
seen.add(id);
|
|
201
|
+
selected.push({ slug: pool[id - 1]!.slug, pinned: pinned.has(id) });
|
|
202
|
+
}
|
|
203
|
+
return selected;
|
|
204
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { wrapMemoryBlock } from "
|
|
2
|
-
import { Slug } from "./types.js";
|
|
1
|
+
import { wrapMemoryBlock } from "../../../memory/memory-marker.js";
|
|
2
|
+
import { Section, Slug } from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Render a v3 working-set selection into a single `<memory>…</memory>` text
|
|
6
|
-
* block
|
|
6
|
+
* block, injecting each slug's matched section (progressive disclosure) rather
|
|
7
|
+
* than its full page body.
|
|
7
8
|
*
|
|
8
9
|
* Pure and side-effect-free: all I/O is delegated to the injected
|
|
9
|
-
* `pageContent` resolver, which is awaited once per slug in input order
|
|
10
|
-
*
|
|
10
|
+
* `pageContent` resolver, which is awaited once per slug in input order with
|
|
11
|
+
* that slug's matched section (`sectionBySlug.get(slug)`, possibly undefined).
|
|
12
|
+
* The resulting block uses the shared {@link wrapMemoryBlock} marker — the same
|
|
11
13
|
* wrapper the v2 graph-memory path emits — so the existing strip machinery in
|
|
12
14
|
* `conversation-graph-memory.ts` already recognizes (and evicts) it.
|
|
13
15
|
*
|
|
@@ -16,17 +18,23 @@ import { Slug } from "./types.js";
|
|
|
16
18
|
*
|
|
17
19
|
* @param finalInjection - The working-set selection for the turn, in the order
|
|
18
20
|
* it should appear in the rendered block.
|
|
19
|
-
* @param
|
|
21
|
+
* @param sectionBySlug - The matched `Section` per slug; a slug with no entry
|
|
22
|
+
* (e.g. carry-forward with no current match) renders its full/lead page.
|
|
23
|
+
* @param pageContent - Resolver returning a slug's rendered content given its
|
|
24
|
+
* matched section (or undefined).
|
|
20
25
|
* @returns The wrapped `<memory>` block, or `""` for an empty selection (the
|
|
21
26
|
* caller injects nothing).
|
|
22
27
|
*/
|
|
23
28
|
export async function renderMemoryBlock(
|
|
24
29
|
finalInjection: Slug[],
|
|
25
|
-
|
|
30
|
+
sectionBySlug: Map<Slug, Section>,
|
|
31
|
+
pageContent: (slug: Slug, section: Section | undefined) => Promise<string>,
|
|
26
32
|
): Promise<string> {
|
|
27
33
|
if (finalInjection.length === 0) return "";
|
|
28
34
|
|
|
29
|
-
const contents = await Promise.all(
|
|
35
|
+
const contents = await Promise.all(
|
|
36
|
+
finalInjection.map((slug) => pageContent(slug, sectionBySlug.get(slug))),
|
|
37
|
+
);
|
|
30
38
|
|
|
31
39
|
return wrapMemoryBlock(contents.join("\n"));
|
|
32
40
|
}
|