@vellumai/assistant 0.8.8 → 0.8.9-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +6 -6
- package/bun.lock +2 -2
- 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 +235 -6
- package/package.json +2 -2
- 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 +1 -1
- 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 +5 -4
- 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 +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- 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 +142 -218
- package/src/__tests__/conversation-agent-loop.test.ts +297 -586
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- 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 +12 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -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 +1 -1
- 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__/memory-retrieval-hook.test.ts +131 -26
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -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 +33 -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 +1 -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/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- 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/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +225 -9
- package/src/acp/resolve-agent.ts +122 -17
- 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 +414 -248
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -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 +6 -0
- package/src/config/bundled-skills/acp/SKILL.md +85 -33
- 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 -2
- package/src/config/feature-flag-registry.json +15 -6
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +175 -1055
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +404 -57
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/server.ts +2 -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__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -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/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- 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 +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
- 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/unified-turn-context.ts +223 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- 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 +75 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -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/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/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- 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 +315 -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__/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 +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- 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/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 +28 -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/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 +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +8 -2
- package/src/tools/acp/spawn.test.ts +176 -195
- 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/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -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/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- 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/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/plugins/external-api.ts +0 -104
- 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/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /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__/health.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +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}/core.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}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
|
@@ -33,12 +33,12 @@ import { z } from "zod";
|
|
|
33
33
|
import {
|
|
34
34
|
extractToolUse,
|
|
35
35
|
getConfiguredProvider,
|
|
36
|
-
} from "
|
|
37
|
-
import type { Message, ToolDefinition } from "
|
|
38
|
-
import { getLogger } from "
|
|
36
|
+
} from "../../../providers/provider-send-message.js";
|
|
37
|
+
import type { Message, ToolDefinition } from "../../../providers/types.js";
|
|
38
|
+
import { getLogger } from "../../../util/logger.js";
|
|
39
39
|
import { retryForResult } from "./llm-retry.js";
|
|
40
40
|
import { cachedTextBlock } from "./provider-blocks.js";
|
|
41
|
-
import type { LeafPath, LeafTree,
|
|
41
|
+
import type { LeafPath, LeafTree, MemoryRoutingTurn } from "./types.js";
|
|
42
42
|
|
|
43
43
|
const log = getLogger("memory-v3-router");
|
|
44
44
|
|
|
@@ -117,7 +117,7 @@ export function renderLeafBlock(tree: LeafTree): string {
|
|
|
117
117
|
* degrading to the deterministic recall lanes the orchestrator unions in.
|
|
118
118
|
*/
|
|
119
119
|
export async function routeL1(
|
|
120
|
-
turn:
|
|
120
|
+
turn: MemoryRoutingTurn,
|
|
121
121
|
tree: LeafTree,
|
|
122
122
|
): Promise<LeafPath[]> {
|
|
123
123
|
const paths = sortedLeaves(tree);
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* across turns) and can't be reproduced after the fact.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { MemoryV3SelectionLog } from "
|
|
11
|
-
import { isAssistantFeatureFlagEnabled } from "
|
|
12
|
-
import { getConfig } from "
|
|
13
|
-
import { getDb, getSqliteFrom } from "
|
|
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
14
|
import { renderV3PageContent } from "./page-content.js";
|
|
15
15
|
import { renderMemoryBlock } from "./render-injection.js";
|
|
16
16
|
import type { Slug } from "./types.js";
|
|
@@ -33,14 +33,14 @@ import { z } from "zod";
|
|
|
33
33
|
import {
|
|
34
34
|
extractToolUse,
|
|
35
35
|
getConfiguredProvider,
|
|
36
|
-
} from "
|
|
37
|
-
import type { Message, ToolDefinition } from "
|
|
38
|
-
import { getLogger } from "
|
|
39
|
-
import { mapLimit } from "
|
|
36
|
+
} from "../../../providers/provider-send-message.js";
|
|
37
|
+
import type { Message, ToolDefinition } from "../../../providers/types.js";
|
|
38
|
+
import { getLogger } from "../../../util/logger.js";
|
|
39
|
+
import { mapLimit } from "../../../util/map-limit.js";
|
|
40
40
|
import { retryForResult } from "./llm-retry.js";
|
|
41
41
|
import { cachedTextBlock } from "./provider-blocks.js";
|
|
42
42
|
import { membersOf } from "./tree.js";
|
|
43
|
-
import type { LeafPath, LeafTree,
|
|
43
|
+
import type { LeafPath, LeafTree, MemoryRoutingTurn, Slug } from "./types.js";
|
|
44
44
|
|
|
45
45
|
const log = getLogger("memory-v3-selector");
|
|
46
46
|
|
|
@@ -122,7 +122,7 @@ async function renderPagesBlock(
|
|
|
122
122
|
*/
|
|
123
123
|
export async function selectFromLeaf(
|
|
124
124
|
leaf: LeafPath,
|
|
125
|
-
turn:
|
|
125
|
+
turn: MemoryRoutingTurn,
|
|
126
126
|
tree: LeafTree,
|
|
127
127
|
pageSummary: (slug: Slug) => Promise<string>,
|
|
128
128
|
): Promise<SelectedPage[]> {
|
|
@@ -214,7 +214,7 @@ export async function selectFromLeaf(
|
|
|
214
214
|
*/
|
|
215
215
|
export async function selectAcrossLeaves(
|
|
216
216
|
leaves: LeafPath[],
|
|
217
|
-
turn:
|
|
217
|
+
turn: MemoryRoutingTurn,
|
|
218
218
|
tree: LeafTree,
|
|
219
219
|
pageSummary: (slug: Slug) => Promise<string>,
|
|
220
220
|
concurrency = 4,
|
|
@@ -1,75 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory v3 — flag-gated shadow/live
|
|
2
|
+
* Memory v3 — flag-gated shadow/live orchestration engine.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* injection behavior:
|
|
4
|
+
* Runs the v3 orchestrator each turn and records its selection set to
|
|
5
|
+
* `memory_v3_selections`. Two flags gate behavior:
|
|
7
6
|
*
|
|
8
|
-
* - `memory-v3-shadow` (
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
14
|
* (`after-memory-prefix`). Selections are still logged.
|
|
15
|
-
* - both OFF:
|
|
15
|
+
* - both OFF: orchestration is skipped entirely.
|
|
16
16
|
*
|
|
17
17
|
* On each turn (either flag on):
|
|
18
18
|
* 1. Lazy-init the v3 lanes ONCE across the whole process (leaf tree, core
|
|
19
19
|
* set, BM25 needle, carry-forward working set), memoizing the init
|
|
20
20
|
* promise so concurrent first turns share a single build.
|
|
21
|
-
* 2. Build a {@link
|
|
21
|
+
* 2. Build a {@link MemoryRoutingTurn} from the conversation's recent messages.
|
|
22
22
|
* 3. Run {@link orchestrate} and record its selection set to
|
|
23
23
|
* `memory_v3_selections` with a best-effort lane attribution.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
* logged and swallowed so it can never affect the live turn.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* empty selection) falls back to v2 memory rather than dropping all memory.
|
|
25
|
+
* {@link observeTurn} wraps everything after the flag read in try/catch — any
|
|
26
|
+
* failure is logged and swallowed so it can never affect the live turn. The
|
|
27
|
+
* injector treats a `null`/empty result as "no v3 injection", so v2 memory
|
|
28
|
+
* remains the fallback rather than dropping all memory.
|
|
30
29
|
*/
|
|
31
30
|
|
|
32
31
|
import { existsSync, readFileSync } from "node:fs";
|
|
33
32
|
|
|
34
|
-
import { isAssistantFeatureFlagEnabled } from "
|
|
35
|
-
import { getConfig } from "
|
|
36
|
-
import type { AssistantConfig } from "
|
|
37
|
-
import { getMessages } from "
|
|
38
|
-
import { getDb, getSqliteFrom } from "
|
|
39
|
-
import { stringifyMessageContent } from "
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
type Injector,
|
|
43
|
-
type TurnContext as PluginTurnContext,
|
|
44
|
-
} from "../../plugins/types.js";
|
|
45
|
-
import { getLogger } from "../../util/logger.js";
|
|
33
|
+
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
34
|
+
import { getConfig } from "../../../config/loader.js";
|
|
35
|
+
import type { AssistantConfig } from "../../../config/schema.js";
|
|
36
|
+
import { getMessages } from "../../../memory/conversation-crud.js";
|
|
37
|
+
import { getDb, getSqliteFrom } from "../../../memory/db-connection.js";
|
|
38
|
+
import { stringifyMessageContent } from "../../../memory/message-content.js";
|
|
39
|
+
import { getPageIndex } from "../../../memory/v2/page-index.js";
|
|
40
|
+
import { getLogger } from "../../../util/logger.js";
|
|
46
41
|
import {
|
|
47
42
|
getWorkspaceDir,
|
|
48
43
|
getWorkspacePromptPath,
|
|
49
|
-
} from "
|
|
50
|
-
import { stripCommentLines } from "
|
|
51
|
-
import { getPageIndex } from "../v2/page-index.js";
|
|
44
|
+
} from "../../../util/platform.js";
|
|
45
|
+
import { stripCommentLines } from "../../../util/strip-comment-lines.js";
|
|
52
46
|
import { injectCapabilitiesLeaf, isCapabilitySlug } from "./capabilities.js";
|
|
53
47
|
import { loadCore } from "./core.js";
|
|
54
48
|
import type { NeedleIndex } from "./needle.js";
|
|
55
49
|
import { buildNeedleIndex } from "./needle.js";
|
|
56
50
|
import type { OrchestrateResult } from "./orchestrate.js";
|
|
57
51
|
import { orchestrate } from "./orchestrate.js";
|
|
58
|
-
import { renderV3PageContent } from "./page-content.js";
|
|
59
|
-
import { renderMemoryBlock } from "./render-injection.js";
|
|
60
52
|
import { coreSlugs, loadLeafTree, resolveDataDir } from "./tree.js";
|
|
61
53
|
import {
|
|
62
54
|
type LeafPath,
|
|
63
55
|
type LeafTree,
|
|
64
|
-
|
|
56
|
+
type MemoryRoutingTurn,
|
|
65
57
|
type SelectionSource,
|
|
66
58
|
type Slug,
|
|
67
|
-
type TurnContext,
|
|
68
59
|
} from "./types.js";
|
|
69
60
|
import { WorkingSet } from "./working-set.js";
|
|
70
61
|
|
|
71
|
-
const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
|
|
72
|
-
const MEMORY_V3_LIVE = "memory-v3-live" as const;
|
|
62
|
+
export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
|
|
63
|
+
export const MEMORY_V3_LIVE = "memory-v3-live" as const;
|
|
73
64
|
|
|
74
65
|
const log = getLogger("memory-v3-shadow");
|
|
75
66
|
|
|
@@ -202,7 +193,7 @@ function buildSituationalContext(): string {
|
|
|
202
193
|
}
|
|
203
194
|
|
|
204
195
|
/**
|
|
205
|
-
* Build a v3 {@link
|
|
196
|
+
* Build a v3 {@link MemoryRoutingTurn} from the conversation's persisted messages.
|
|
206
197
|
* `currentMessage` is the latest user message; `recentContext` is the tail of
|
|
207
198
|
* the recent transcript; `situationalContext` carries the current date and the
|
|
208
199
|
* live NOW.md scratchpad. Returns `null` when there is no user message to route
|
|
@@ -211,7 +202,7 @@ function buildSituationalContext(): string {
|
|
|
211
202
|
function buildShadowTurn(
|
|
212
203
|
conversationId: string,
|
|
213
204
|
turnIndex: number,
|
|
214
|
-
):
|
|
205
|
+
): MemoryRoutingTurn | null {
|
|
215
206
|
const rows = getMessages(conversationId);
|
|
216
207
|
if (rows.length === 0) return null;
|
|
217
208
|
|
|
@@ -310,7 +301,7 @@ function writeSelections(
|
|
|
310
301
|
* failures are logged and swallowed (returning `null`) so the live turn is
|
|
311
302
|
* unaffected. Returns `null` when there is no user message to route on.
|
|
312
303
|
*/
|
|
313
|
-
async function observeTurn(
|
|
304
|
+
export async function observeTurn(
|
|
314
305
|
conversationId: string,
|
|
315
306
|
turnIndex: number,
|
|
316
307
|
): Promise<OrchestrateResult | null> {
|
|
@@ -356,56 +347,3 @@ export async function runShadowObservation(
|
|
|
356
347
|
if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
|
|
357
348
|
await observeTurn(conversationId, turnIndex);
|
|
358
349
|
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* The v3 injector. Reads both flags:
|
|
362
|
-
* - `memory-v3-live` on → orchestrate, log, render the working-set selection
|
|
363
|
-
* into a `<memory>` block, and return it at v2's dynamic-memory placement.
|
|
364
|
-
* - `memory-v3-shadow` on (live off) → orchestrate + log only, return `null`.
|
|
365
|
-
* - both off → return `null` (no orchestration).
|
|
366
|
-
*
|
|
367
|
-
* Empty selection and any failure return `null` (no v3 injection). v2
|
|
368
|
-
* suppression keys off BOTH the flag AND this return value, so a `null` here
|
|
369
|
-
* (failure or empty selection) falls back to v2 memory rather than dropping all
|
|
370
|
-
* memory.
|
|
371
|
-
*/
|
|
372
|
-
export const memoryV3Injector: Injector = {
|
|
373
|
-
name: "memory-v3-shadow",
|
|
374
|
-
// High order so it sorts last; the live `<memory>` block uses the
|
|
375
|
-
// after-memory-prefix placement so it lands at the memory boundary regardless
|
|
376
|
-
// of this sort key, which only orders content-producing injectors.
|
|
377
|
-
order: 1000,
|
|
378
|
-
async produce(ctx: PluginTurnContext): Promise<InjectionBlock | null> {
|
|
379
|
-
const config = getConfig();
|
|
380
|
-
const live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
|
|
381
|
-
const shadow = isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config);
|
|
382
|
-
if (!live && !shadow) return null;
|
|
383
|
-
|
|
384
|
-
const result = await observeTurn(ctx.conversationId, ctx.turnIndex);
|
|
385
|
-
if (!live || !result) return null;
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
// `renderMemoryBlock` returns "" for an empty selection; inject nothing.
|
|
389
|
-
const text = await renderMemoryBlock(
|
|
390
|
-
result.finalInjection,
|
|
391
|
-
renderV3PageContent,
|
|
392
|
-
);
|
|
393
|
-
if (text.length === 0) return null;
|
|
394
|
-
return {
|
|
395
|
-
id: MEMORY_V3_BLOCK_ID,
|
|
396
|
-
text,
|
|
397
|
-
// Mirror v2's dynamic `<memory>` block placement.
|
|
398
|
-
placement: "after-memory-prefix",
|
|
399
|
-
};
|
|
400
|
-
} catch (err) {
|
|
401
|
-
log.warn(
|
|
402
|
-
{
|
|
403
|
-
err: err instanceof Error ? err.message : String(err),
|
|
404
|
-
conversationId: ctx.conversationId,
|
|
405
|
-
},
|
|
406
|
-
"memory-v3 live render failed (non-fatal) — falling back to v2",
|
|
407
|
-
);
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
};
|
package/src/plugins/pipeline.ts
CHANGED
|
@@ -1,297 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin
|
|
2
|
+
* Plugin hook runner.
|
|
3
3
|
*
|
|
4
|
-
* A "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* covering success, error, and timeout uniformly.
|
|
4
|
+
* A "hook" is a named lifecycle event (`user-prompt-submit`, `post-tool-use`,
|
|
5
|
+
* …) that every registered plugin may handle. The runner walks each plugin's
|
|
6
|
+
* hook for a given event in registration order, threading a context value
|
|
7
|
+
* through the chain so hooks can observe and transform it.
|
|
9
8
|
*
|
|
10
|
-
* Design doc: `.private/plans/agent-plugin-system.md
|
|
11
|
-
*
|
|
12
|
-
* Semantics:
|
|
13
|
-
* - Onion composition — the first element of `middlewares` is the outermost
|
|
14
|
-
* wrapper; it sees the request first and the response last. Terminal runs
|
|
15
|
-
* in the middle.
|
|
16
|
-
* - Strict-fail — there is NO `try/catch` around user middleware. Errors,
|
|
17
|
-
* including `PluginTimeoutError` from a breached budget, propagate to the
|
|
18
|
-
* caller. The `finally` block only handles logging, never error recovery.
|
|
19
|
-
* - Timeout — when `timeoutMs` is a finite number, the invocation races a
|
|
20
|
-
* timer. A timeout rejection is a `PluginTimeoutError` carrying the
|
|
21
|
-
* pipeline name, the best-known offending plugin (from `ctx.pluginName`),
|
|
22
|
-
* and elapsed ms. When `timeoutMs` is `null`/`undefined`, no timer is
|
|
23
|
-
* armed — pipelines like `compaction` rely on downstream timeouts instead.
|
|
9
|
+
* Design doc: `.private/plans/agent-plugin-system.md`.
|
|
24
10
|
*/
|
|
25
11
|
|
|
26
|
-
import type { Logger } from "pino";
|
|
27
|
-
|
|
28
12
|
import type { HookName } from "../plugin-api/constants.js";
|
|
29
|
-
import { getLogger } from "../util/logger.js";
|
|
30
13
|
import { getHooksFor } from "./registry.js";
|
|
31
|
-
import {
|
|
32
|
-
type Middleware,
|
|
33
|
-
type PipelineName,
|
|
34
|
-
PluginTimeoutError,
|
|
35
|
-
type TurnContext,
|
|
36
|
-
} from "./types.js";
|
|
37
|
-
|
|
38
|
-
const moduleLogger = getLogger("plugin-pipeline");
|
|
39
|
-
|
|
40
|
-
// ─── Default timeouts ───────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Default per-pipeline timeout budgets in milliseconds. A value of `null`
|
|
44
|
-
* means the runner does NOT arm a timer — the pipeline relies on its
|
|
45
|
-
* downstream for budgeting (e.g. `compaction` defers to the provider-level
|
|
46
|
-
* HTTP timeouts of the summary call it wraps).
|
|
47
|
-
*
|
|
48
|
-
* Callers pass the appropriate entry as `runPipeline`'s `timeoutMs` argument.
|
|
49
|
-
* The design doc locks these numbers in; do not tweak without coordinating
|
|
50
|
-
* a design update.
|
|
51
|
-
*/
|
|
52
|
-
export const DEFAULT_TIMEOUTS: Record<PipelineName, number | null> = {
|
|
53
|
-
compaction: null,
|
|
54
|
-
overflowReduce: null,
|
|
55
|
-
circuitBreaker: null,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// ─── Composition ────────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Compose an ordered list of {@link Middleware}s around a terminal handler
|
|
62
|
-
* using onion semantics. The first element of `middlewares` is the outermost
|
|
63
|
-
* layer.
|
|
64
|
-
*
|
|
65
|
-
* The returned function accepts `(args, ctx)` and runs the entire chain:
|
|
66
|
-
* ```
|
|
67
|
-
* middlewares[0] → middlewares[1] → ... → terminal → ... → middlewares[1] → middlewares[0]
|
|
68
|
-
* ```
|
|
69
|
-
*
|
|
70
|
-
* Middlewares that never call `next` short-circuit the chain — the terminal
|
|
71
|
-
* and any deeper middleware are never invoked. Middlewares that throw abort
|
|
72
|
-
* the chain; the error flows back out through any outer middleware unchanged
|
|
73
|
-
* (no internal try/catch).
|
|
74
|
-
*/
|
|
75
|
-
export function composeMiddleware<A, R>(
|
|
76
|
-
middlewares: ReadonlyArray<Middleware<A, R>>,
|
|
77
|
-
terminal: (args: A) => Promise<R>,
|
|
78
|
-
): (args: A, ctx: TurnContext) => Promise<R> {
|
|
79
|
-
return (args, ctx) => {
|
|
80
|
-
// Walk back-to-front, wrapping each middleware around `next`. The last
|
|
81
|
-
// middleware in the array wraps `terminal`; earlier entries wrap the
|
|
82
|
-
// accumulated chain so they execute first.
|
|
83
|
-
let next: (args: A) => Promise<R> = terminal;
|
|
84
|
-
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
85
|
-
const mw = middlewares[i]!;
|
|
86
|
-
const downstream = next;
|
|
87
|
-
next = (innerArgs: A) => mw(innerArgs, downstream, ctx);
|
|
88
|
-
}
|
|
89
|
-
return next(args);
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ─── Abort-signal linking ───────────────────────────────────────────────────
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Return a shallow-cloned `args` object where every `AbortSignal`-typed
|
|
97
|
-
* top-level property is swapped for a signal linked to `internalController`.
|
|
98
|
-
*
|
|
99
|
-
* "Linked" here means: the returned signal aborts when either the caller's
|
|
100
|
-
* original signal aborts OR `internalController` aborts (e.g. the pipeline
|
|
101
|
-
* timer fires). The caller's args are never mutated.
|
|
102
|
-
*
|
|
103
|
-
* When `args` carries no `AbortSignal` property, the original object is
|
|
104
|
-
* returned unchanged — pipelines whose terminals don't consume a signal
|
|
105
|
-
* (e.g. `circuitBreaker`) see identical behavior to before.
|
|
106
|
-
* The return value's `cleanup()` tears down any `addEventListener("abort",
|
|
107
|
-
* ...)` handlers attached to the caller's signal so a pipeline that
|
|
108
|
-
* completes successfully doesn't leak listeners on the caller's controller.
|
|
109
|
-
*/
|
|
110
|
-
function linkAbortSignal<A>(
|
|
111
|
-
args: A,
|
|
112
|
-
internalController: AbortController,
|
|
113
|
-
): { args: A; cleanup: () => void } {
|
|
114
|
-
if (args === null || typeof args !== "object") {
|
|
115
|
-
return { args, cleanup: () => {} };
|
|
116
|
-
}
|
|
117
|
-
const abortListeners: Array<{
|
|
118
|
-
signal: AbortSignal;
|
|
119
|
-
listener: () => void;
|
|
120
|
-
}> = [];
|
|
121
|
-
const record = args as Record<string, unknown>;
|
|
122
|
-
const patched: Record<string, unknown> = { ...record };
|
|
123
|
-
let swappedAny = false;
|
|
124
|
-
for (const key of Object.keys(record)) {
|
|
125
|
-
const value = record[key];
|
|
126
|
-
if (value instanceof AbortSignal) {
|
|
127
|
-
swappedAny = true;
|
|
128
|
-
if (value.aborted) {
|
|
129
|
-
// Caller already aborted — propagate immediately so the inner call
|
|
130
|
-
// sees the abort without waiting for the listener to fire.
|
|
131
|
-
internalController.abort();
|
|
132
|
-
} else {
|
|
133
|
-
const listener = () => internalController.abort();
|
|
134
|
-
value.addEventListener("abort", listener, { once: true });
|
|
135
|
-
abortListeners.push({ signal: value, listener });
|
|
136
|
-
}
|
|
137
|
-
patched[key] = internalController.signal;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const cleanup = () => {
|
|
141
|
-
for (const { signal, listener } of abortListeners) {
|
|
142
|
-
signal.removeEventListener("abort", listener);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
return { args: (swappedAny ? patched : args) as A, cleanup };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ─── Runner ─────────────────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
type PipelineLogRecord = {
|
|
151
|
-
event: "plugin.pipeline";
|
|
152
|
-
pipeline: PipelineName;
|
|
153
|
-
chain: ReadonlyArray<string>;
|
|
154
|
-
durationMs: number;
|
|
155
|
-
outcome: "success" | "error" | "timeout";
|
|
156
|
-
pluginName?: string;
|
|
157
|
-
errorName?: string;
|
|
158
|
-
errorMessage?: string;
|
|
159
|
-
errorStack?: string;
|
|
160
|
-
timeoutMs?: number;
|
|
161
|
-
requestId: string;
|
|
162
|
-
conversationId: string;
|
|
163
|
-
turnIndex?: number;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Best-effort detection of a pino-compatible logger on the `TurnContext`.
|
|
168
|
-
*
|
|
169
|
-
* `TurnContext` intentionally doesn't require a logger slot — most code paths
|
|
170
|
-
* use the module-level logger. Some callers (notably plugin-scoped pipelines
|
|
171
|
-
* in later PRs) may attach a child logger via `(ctx as any).logger`. We type
|
|
172
|
-
* the field loosely to avoid coupling `TurnContext` to pino.
|
|
173
|
-
*/
|
|
174
|
-
function selectLogger(ctx: TurnContext): Logger {
|
|
175
|
-
const maybe = (ctx as { logger?: unknown }).logger;
|
|
176
|
-
if (maybe && typeof maybe === "object" && "info" in maybe) {
|
|
177
|
-
return maybe as Logger;
|
|
178
|
-
}
|
|
179
|
-
return moduleLogger;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Execute a named pipeline: compose middleware, run it under an optional
|
|
184
|
-
* timeout, and emit one structured log record covering success, error, or
|
|
185
|
-
* timeout uniformly.
|
|
186
|
-
*
|
|
187
|
-
* @param name The pipeline identifier. Used for telemetry and error
|
|
188
|
-
* attribution.
|
|
189
|
-
* @param middlewares Ordered list of middlewares to wrap around `terminal`.
|
|
190
|
-
* Names for the log `chain` are pulled from `Function.name`
|
|
191
|
-
* (empty strings are kept so length always matches).
|
|
192
|
-
* @param terminal The original behavior. Runs when all middlewares call
|
|
193
|
-
* `next`.
|
|
194
|
-
* @param args Pipeline-specific input payload.
|
|
195
|
-
* @param ctx Per-turn context. May carry an optional `logger` slot;
|
|
196
|
-
* otherwise the module logger is used.
|
|
197
|
-
* @param timeoutMs Deadline in milliseconds. `null`/`undefined` disables
|
|
198
|
-
* the timer entirely (inherits downstream timeouts).
|
|
199
|
-
*
|
|
200
|
-
* @throws {PluginTimeoutError} When `timeoutMs` is non-null and the chain
|
|
201
|
-
* exceeds the budget. `ctx.pluginName` (if set) is attached as the
|
|
202
|
-
* offending plugin.
|
|
203
|
-
* @throws Any error produced by user middleware or the terminal — flows
|
|
204
|
-
* through unchanged.
|
|
205
|
-
*/
|
|
206
|
-
export async function runPipeline<A, R>(
|
|
207
|
-
name: PipelineName,
|
|
208
|
-
middlewares: ReadonlyArray<Middleware<A, R>>,
|
|
209
|
-
terminal: (args: A) => Promise<R>,
|
|
210
|
-
args: A,
|
|
211
|
-
ctx: TurnContext,
|
|
212
|
-
timeoutMs?: number | null,
|
|
213
|
-
): Promise<R> {
|
|
214
|
-
const logger = selectLogger(ctx);
|
|
215
|
-
const chain = middlewares.map((m) => m.name || "anonymous");
|
|
216
|
-
const composed = composeMiddleware(middlewares, terminal);
|
|
217
|
-
const start = performance.now();
|
|
218
|
-
|
|
219
|
-
let outcome: "success" | "error" | "timeout" = "success";
|
|
220
|
-
let thrown: unknown = undefined;
|
|
221
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
|
|
225
|
-
const budget = timeoutMs;
|
|
226
|
-
// Internal controller: fires on either (a) the timer, so the inner call
|
|
227
|
-
// actually observes the budget breach instead of running forever after
|
|
228
|
-
// `Promise.race` rejects; or (b) the caller's own signal, so external
|
|
229
|
-
// cancellation still reaches the inner call transparently. Any
|
|
230
|
-
// `AbortSignal`-typed property on `args` (e.g. `signal` on
|
|
231
|
-
// `CompactionArgs`, `abortSignal` on `OverflowReduceArgs`) is swapped
|
|
232
|
-
// for this linked signal on a shallow-cloned args object — we never
|
|
233
|
-
// mutate the caller's args.
|
|
234
|
-
const internalController = new AbortController();
|
|
235
|
-
const { args: effectiveArgs, cleanup } = linkAbortSignal(
|
|
236
|
-
args,
|
|
237
|
-
internalController,
|
|
238
|
-
);
|
|
239
|
-
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
240
|
-
timer = setTimeout(() => {
|
|
241
|
-
internalController.abort();
|
|
242
|
-
reject(
|
|
243
|
-
new PluginTimeoutError(
|
|
244
|
-
name,
|
|
245
|
-
ctx.pluginName,
|
|
246
|
-
Math.round(performance.now() - start),
|
|
247
|
-
),
|
|
248
|
-
);
|
|
249
|
-
}, budget);
|
|
250
|
-
});
|
|
251
|
-
try {
|
|
252
|
-
return await Promise.race([
|
|
253
|
-
composed(effectiveArgs, ctx),
|
|
254
|
-
timeoutPromise,
|
|
255
|
-
]);
|
|
256
|
-
} finally {
|
|
257
|
-
cleanup();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return await composed(args, ctx);
|
|
261
|
-
} catch (err) {
|
|
262
|
-
thrown = err;
|
|
263
|
-
outcome = err instanceof PluginTimeoutError ? "timeout" : "error";
|
|
264
|
-
throw err;
|
|
265
|
-
} finally {
|
|
266
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
267
|
-
const durationMs = Math.round(performance.now() - start);
|
|
268
|
-
const record: PipelineLogRecord = {
|
|
269
|
-
event: "plugin.pipeline",
|
|
270
|
-
pipeline: name,
|
|
271
|
-
chain,
|
|
272
|
-
durationMs,
|
|
273
|
-
outcome,
|
|
274
|
-
requestId: ctx.requestId,
|
|
275
|
-
conversationId: ctx.conversationId,
|
|
276
|
-
};
|
|
277
|
-
if (ctx.turnIndex !== undefined) record.turnIndex = ctx.turnIndex;
|
|
278
|
-
if (ctx.pluginName !== undefined) record.pluginName = ctx.pluginName;
|
|
279
|
-
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
|
|
280
|
-
record.timeoutMs = timeoutMs;
|
|
281
|
-
}
|
|
282
|
-
if (thrown !== undefined) {
|
|
283
|
-
if (thrown instanceof Error) {
|
|
284
|
-
record.errorName = thrown.name;
|
|
285
|
-
record.errorMessage = thrown.message;
|
|
286
|
-
if (thrown.stack) record.errorStack = thrown.stack;
|
|
287
|
-
} else {
|
|
288
|
-
record.errorName = "NonError";
|
|
289
|
-
record.errorMessage = String(thrown);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
logger.info(record, "plugin.pipeline");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
14
|
|
|
296
15
|
// ─── Hook runner ────────────────────────────────────────────────────────────
|
|
297
16
|
|
|
@@ -302,12 +21,6 @@ export async function runPipeline<A, R>(
|
|
|
302
21
|
* that replaces the threaded value for subsequent hooks. The final context
|
|
303
22
|
* after the chain settles is returned.
|
|
304
23
|
*
|
|
305
|
-
* `runHook` is the hook-side counterpart to {@link runPipeline}:
|
|
306
|
-
* `runPipeline` composes middleware around a terminal handler for stateful
|
|
307
|
-
* request/response pipelines (compaction, overflow reduction, etc.);
|
|
308
|
-
* `runHook` walks ordered hook functions for declarative chain-style
|
|
309
|
-
* context transformations (`user-prompt-submit`, `post-tool-use`).
|
|
310
|
-
*
|
|
311
24
|
* @param name The hook identifier — pick one from {@link HOOKS}.
|
|
312
25
|
* @param initialCtx Context the first hook receives.
|
|
313
26
|
* @returns The final context after the chain settles. Same reference as
|