@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
|
@@ -53,6 +53,11 @@ mock.module("../config/loader.js", () => ({
|
|
|
53
53
|
// Real DB — same pattern as conversation-crud-inference-profile.test.ts
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
55
|
|
|
56
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
57
|
+
import {
|
|
58
|
+
deleteConversation,
|
|
59
|
+
setConversation,
|
|
60
|
+
} from "../daemon/conversation-registry.js";
|
|
56
61
|
import {
|
|
57
62
|
createConversation,
|
|
58
63
|
getConversation,
|
|
@@ -78,6 +83,37 @@ function resetDb() {
|
|
|
78
83
|
db.run(`DELETE FROM conversations`);
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Minimal live `Conversation` stub exposing just the inference-profile
|
|
88
|
+
* mirror fields and {@link Conversation.applyInferenceProfileState}, so the
|
|
89
|
+
* handler's live-instance sync can be observed without constructing a full
|
|
90
|
+
* conversation.
|
|
91
|
+
*/
|
|
92
|
+
function makeLiveConversationStub(conversationId: string): Conversation {
|
|
93
|
+
return {
|
|
94
|
+
conversationId,
|
|
95
|
+
inferenceProfile: null as string | null,
|
|
96
|
+
inferenceProfileSessionId: null as string | null,
|
|
97
|
+
inferenceProfileExpiresAt: null as number | null,
|
|
98
|
+
applyInferenceProfileState(
|
|
99
|
+
this: {
|
|
100
|
+
inferenceProfile: string | null;
|
|
101
|
+
inferenceProfileSessionId: string | null;
|
|
102
|
+
inferenceProfileExpiresAt: number | null;
|
|
103
|
+
},
|
|
104
|
+
state: {
|
|
105
|
+
profile: string | null;
|
|
106
|
+
sessionId: string | null;
|
|
107
|
+
expiresAt: number | null;
|
|
108
|
+
},
|
|
109
|
+
) {
|
|
110
|
+
this.inferenceProfile = state.profile;
|
|
111
|
+
this.inferenceProfileSessionId = state.sessionId;
|
|
112
|
+
this.inferenceProfileExpiresAt = state.expiresAt;
|
|
113
|
+
},
|
|
114
|
+
} as unknown as Conversation;
|
|
115
|
+
}
|
|
116
|
+
|
|
81
117
|
// ---------------------------------------------------------------------------
|
|
82
118
|
// Tests
|
|
83
119
|
// ---------------------------------------------------------------------------
|
|
@@ -175,6 +211,56 @@ describe("setInferenceProfileSession", () => {
|
|
|
175
211
|
expect(second.replaced!.expiresAt).toBe(first.expiresAt);
|
|
176
212
|
});
|
|
177
213
|
|
|
214
|
+
test("mirrors the session state onto the live in-memory Conversation on open", async () => {
|
|
215
|
+
// GIVEN a conversation with a live in-memory instance registered for it
|
|
216
|
+
const conv = createConversation("sess-handler-live-sync-open");
|
|
217
|
+
const liveConversation = makeLiveConversationStub(conv.id);
|
|
218
|
+
setConversation(conv.id, liveConversation);
|
|
219
|
+
|
|
220
|
+
// WHEN a session-backed override is opened
|
|
221
|
+
const opened = await setInferenceProfileSession({
|
|
222
|
+
conversationId: conv.id,
|
|
223
|
+
profile: "balanced",
|
|
224
|
+
ttlSeconds: 600,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// THEN the live instance mirrors the persisted session state, so the
|
|
228
|
+
// per-turn override derivation reads it without re-fetching the DB row
|
|
229
|
+
expect(liveConversation.inferenceProfile).toBe("balanced");
|
|
230
|
+
expect(liveConversation.inferenceProfileSessionId).toBe(opened.sessionId);
|
|
231
|
+
expect(liveConversation.inferenceProfileExpiresAt).toBe(opened.expiresAt);
|
|
232
|
+
|
|
233
|
+
deleteConversation(conv.id);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("clears the live in-memory Conversation state on clear", async () => {
|
|
237
|
+
// GIVEN a conversation with a live in-memory instance registered for it
|
|
238
|
+
const conv = createConversation("sess-handler-live-sync-clear");
|
|
239
|
+
const liveConversation = makeLiveConversationStub(conv.id);
|
|
240
|
+
setConversation(conv.id, liveConversation);
|
|
241
|
+
|
|
242
|
+
// AND an active session-backed override mirrored onto that instance
|
|
243
|
+
await setInferenceProfileSession({
|
|
244
|
+
conversationId: conv.id,
|
|
245
|
+
profile: "balanced",
|
|
246
|
+
ttlSeconds: 600,
|
|
247
|
+
});
|
|
248
|
+
expect(liveConversation.inferenceProfile).toBe("balanced");
|
|
249
|
+
|
|
250
|
+
// WHEN the override is cleared
|
|
251
|
+
await setInferenceProfileSession({
|
|
252
|
+
conversationId: conv.id,
|
|
253
|
+
profile: null,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// THEN the live instance is cleared in lock-step with the DB row
|
|
257
|
+
expect(liveConversation.inferenceProfile).toBeNull();
|
|
258
|
+
expect(liveConversation.inferenceProfileSessionId).toBeNull();
|
|
259
|
+
expect(liveConversation.inferenceProfileExpiresAt).toBeNull();
|
|
260
|
+
|
|
261
|
+
deleteConversation(conv.id);
|
|
262
|
+
});
|
|
263
|
+
|
|
178
264
|
test("open with profile=null — all three cleared; replaced set if prior existed", async () => {
|
|
179
265
|
const conv = createConversation("sess-handler-clear");
|
|
180
266
|
|
|
@@ -23,7 +23,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
23
23
|
import {
|
|
24
24
|
DEFAULT_INJECTOR_ORDER,
|
|
25
25
|
defaultInjectors,
|
|
26
|
-
} from "../plugins/defaults/injectors
|
|
26
|
+
} from "../plugins/defaults/memory-retrieval/injectors.js";
|
|
27
27
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
28
28
|
|
|
29
29
|
function findInjector(name: string): Injector {
|
|
@@ -59,10 +59,8 @@ describe("background-turn injector", () => {
|
|
|
59
59
|
test("returns null when isBackgroundConversation is false", async () => {
|
|
60
60
|
const result = await backgroundInjector.produce(
|
|
61
61
|
makeContext({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
isNonInteractive: true,
|
|
65
|
-
},
|
|
62
|
+
isBackgroundConversation: false,
|
|
63
|
+
isNonInteractive: true,
|
|
66
64
|
}),
|
|
67
65
|
);
|
|
68
66
|
expect(result).toBeNull();
|
|
@@ -70,7 +68,7 @@ describe("background-turn injector", () => {
|
|
|
70
68
|
|
|
71
69
|
test("returns null when isBackgroundConversation is unset", async () => {
|
|
72
70
|
const result = await backgroundInjector.produce(
|
|
73
|
-
makeContext({
|
|
71
|
+
makeContext({ isNonInteractive: true }),
|
|
74
72
|
);
|
|
75
73
|
expect(result).toBeNull();
|
|
76
74
|
});
|
|
@@ -78,10 +76,8 @@ describe("background-turn injector", () => {
|
|
|
78
76
|
test("returns null when the guardian is actively connected (interactive turn)", async () => {
|
|
79
77
|
const result = await backgroundInjector.produce(
|
|
80
78
|
makeContext({
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
isNonInteractive: false,
|
|
84
|
-
},
|
|
79
|
+
isBackgroundConversation: true,
|
|
80
|
+
isNonInteractive: false,
|
|
85
81
|
}),
|
|
86
82
|
);
|
|
87
83
|
expect(result).toBeNull();
|
|
@@ -89,7 +85,7 @@ describe("background-turn injector", () => {
|
|
|
89
85
|
|
|
90
86
|
test("returns null when isNonInteractive is unset", async () => {
|
|
91
87
|
const result = await backgroundInjector.produce(
|
|
92
|
-
makeContext({
|
|
88
|
+
makeContext({ isBackgroundConversation: true }),
|
|
93
89
|
);
|
|
94
90
|
expect(result).toBeNull();
|
|
95
91
|
});
|
|
@@ -97,10 +93,8 @@ describe("background-turn injector", () => {
|
|
|
97
93
|
test("wraps configured text in <background_turn> tags when active and non-interactive", async () => {
|
|
98
94
|
const block = await backgroundInjector.produce(
|
|
99
95
|
makeContext({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
isNonInteractive: true,
|
|
103
|
-
},
|
|
96
|
+
isBackgroundConversation: true,
|
|
97
|
+
isNonInteractive: true,
|
|
104
98
|
}),
|
|
105
99
|
);
|
|
106
100
|
|
|
@@ -119,10 +113,8 @@ describe("background-turn injector", () => {
|
|
|
119
113
|
|
|
120
114
|
const result = await backgroundInjector.produce(
|
|
121
115
|
makeContext({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
isNonInteractive: true,
|
|
125
|
-
},
|
|
116
|
+
isBackgroundConversation: true,
|
|
117
|
+
isNonInteractive: true,
|
|
126
118
|
}),
|
|
127
119
|
);
|
|
128
120
|
expect(result).toBeNull();
|
|
@@ -133,10 +125,8 @@ describe("background-turn injector", () => {
|
|
|
133
125
|
|
|
134
126
|
const block = await backgroundInjector.produce(
|
|
135
127
|
makeContext({
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
isNonInteractive: true,
|
|
139
|
-
},
|
|
128
|
+
isBackgroundConversation: true,
|
|
129
|
+
isNonInteractive: true,
|
|
140
130
|
}),
|
|
141
131
|
);
|
|
142
132
|
|
|
@@ -38,23 +38,66 @@ mock.module("../config/loader.js", () => ({
|
|
|
38
38
|
},
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// `applyRuntimeInjections` computes the `<turn_context>` `current_time` live
|
|
42
|
+
// via `formatTurnTimestamp`; pin it so the rendered block matches the
|
|
43
|
+
// deterministic `buildUnifiedTurnContextBlock` expectations below. The rest of
|
|
44
|
+
// the module (timezone canonicalization/resolution) keeps its real behavior.
|
|
45
|
+
const FIXED_TURN_TIMESTAMP = "2026-04-22";
|
|
46
|
+
const realDateContext = await import("../daemon/date-context.js");
|
|
47
|
+
mock.module("../daemon/date-context.js", () => ({
|
|
48
|
+
...realDateContext,
|
|
49
|
+
formatTurnTimestamp: () => FIXED_TURN_TIMESTAMP,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
applyRuntimeInjections,
|
|
54
|
+
buildSubagentStatusBlock,
|
|
55
|
+
composeInjectorChain,
|
|
56
|
+
} = await import("../daemon/conversation-runtime-assembly.js");
|
|
43
57
|
const { DEFAULT_INJECTOR_ORDER, defaultInjectors } =
|
|
44
|
-
await import("../plugins/defaults/injectors
|
|
58
|
+
await import("../plugins/defaults/memory-retrieval/injectors.js");
|
|
45
59
|
const { getInjectorChain } =
|
|
46
60
|
await import("../plugins/defaults/memory-retrieval/injector-chain.js");
|
|
61
|
+
import { eq } from "drizzle-orm";
|
|
62
|
+
|
|
63
|
+
import {
|
|
64
|
+
clearConversations,
|
|
65
|
+
setConversation,
|
|
66
|
+
} from "../daemon/conversation-registry.js";
|
|
47
67
|
import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
|
|
68
|
+
import { getDb } from "../memory/db-connection.js";
|
|
69
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
48
70
|
import { getPkbRoot } from "../memory/pkb/types.js";
|
|
71
|
+
import { conversations, messages } from "../memory/schema.js";
|
|
72
|
+
import {
|
|
73
|
+
type SlackMessageMetadata,
|
|
74
|
+
writeSlackMetadata,
|
|
75
|
+
} from "../messaging/providers/slack/message-metadata.js";
|
|
76
|
+
import { buildUnifiedTurnContextBlock } from "../plugins/defaults/memory-retrieval/unified-turn-context.js";
|
|
49
77
|
import type { TurnContext } from "../plugins/types.js";
|
|
50
78
|
import type { Message } from "../providers/types.js";
|
|
79
|
+
import { getSubagentManager } from "../subagent/index.js";
|
|
80
|
+
import type { SubagentState } from "../subagent/types.js";
|
|
51
81
|
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
52
82
|
|
|
83
|
+
// `applyRuntimeInjections` self-resolves the Slack active-thread focus block
|
|
84
|
+
// from the persisted message rows, so the schema must exist for Slack-channel
|
|
85
|
+
// turns; with no seeded rows the focus loader resolves to null.
|
|
86
|
+
initializeDb();
|
|
87
|
+
|
|
88
|
+
// `makeTurnContext` and the workspace registry seed share this id so the
|
|
89
|
+
// `workspace-context` injector resolves the seeded block for the turn.
|
|
90
|
+
const TEST_CONVERSATION_ID = "conv-test-1";
|
|
91
|
+
// Conversation id the fallback-context test registers its live conversation
|
|
92
|
+
// under; passed explicitly now that `applyRuntimeInjections` requires the
|
|
93
|
+
// caller to name the conversation it resolves through.
|
|
94
|
+
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
95
|
+
|
|
53
96
|
/** A fake TurnContext sufficient for driving `composeInjectorChain`. */
|
|
54
97
|
function makeTurnContext(): TurnContext {
|
|
55
98
|
return {
|
|
56
99
|
requestId: "req-test-1",
|
|
57
|
-
conversationId:
|
|
100
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
58
101
|
turnIndex: 0,
|
|
59
102
|
trust: {
|
|
60
103
|
sourceChannel: "vellum",
|
|
@@ -94,10 +137,158 @@ function clearNowScratchpad(): void {
|
|
|
94
137
|
rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
|
|
95
138
|
}
|
|
96
139
|
|
|
140
|
+
// The workspace-context injector sources its block off the live `Conversation`
|
|
141
|
+
// looked up by `conversationId`. Seed a fake instance carrying a non-dirty
|
|
142
|
+
// workspace cache under the id `makeTurnContext()` uses so the injector emits
|
|
143
|
+
// the block; `clearConversations()` between tests keeps suites that assert the
|
|
144
|
+
// workspace block is absent unaffected.
|
|
145
|
+
function seedWorkspaceContext(
|
|
146
|
+
text: string,
|
|
147
|
+
currentTurnTemporalSnapshot?: {
|
|
148
|
+
clientTimezone: string | null;
|
|
149
|
+
},
|
|
150
|
+
interfaceName?: string,
|
|
151
|
+
): void {
|
|
152
|
+
setConversation(TEST_CONVERSATION_ID, {
|
|
153
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
154
|
+
workingDir: "/sandbox",
|
|
155
|
+
workspaceTopLevelContext: text,
|
|
156
|
+
workspaceTopLevelDirty: false,
|
|
157
|
+
currentTurnTemporalSnapshot,
|
|
158
|
+
currentTurnInterfaceContext: interfaceName
|
|
159
|
+
? {
|
|
160
|
+
userMessageInterface: interfaceName,
|
|
161
|
+
assistantMessageInterface: interfaceName,
|
|
162
|
+
}
|
|
163
|
+
: undefined,
|
|
164
|
+
} as never);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// `applyRuntimeInjections` gates the `<turn_context>` block on the live
|
|
168
|
+
// conversation's frozen `currentTurnTemporalSnapshot` (computing `current_time`
|
|
169
|
+
// live). Tests that drive the chain without a caller-supplied `turnContext`
|
|
170
|
+
// fall back to the runtime-assembly fallback conversation, so seed the snapshot
|
|
171
|
+
// there.
|
|
172
|
+
function seedFallbackTemporalSnapshot(): void {
|
|
173
|
+
setConversation("runtime-assembly-fallback", {
|
|
174
|
+
conversationId: "runtime-assembly-fallback",
|
|
175
|
+
workingDir: "/sandbox",
|
|
176
|
+
workspaceTopLevelContext: "",
|
|
177
|
+
workspaceTopLevelDirty: false,
|
|
178
|
+
currentTurnTemporalSnapshot: { clientTimezone: null },
|
|
179
|
+
} as never);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Persist Slack-channel rows for the turn conversation so
|
|
183
|
+
// `applyRuntimeInjections` self-resolves the chronological transcript from
|
|
184
|
+
// conversation state, exactly as production does (the slack-messages injector
|
|
185
|
+
// reads the live conversation rather than receiving a pre-built transcript).
|
|
186
|
+
const SLACK_CHANNEL_ID = "C0123CHANNEL";
|
|
187
|
+
function seedSlackChannelRows(
|
|
188
|
+
conversationId: string,
|
|
189
|
+
rows: Array<{
|
|
190
|
+
id: string;
|
|
191
|
+
role: "user" | "assistant";
|
|
192
|
+
text: string;
|
|
193
|
+
channelTs: string;
|
|
194
|
+
displayName: string;
|
|
195
|
+
createdAt: number;
|
|
196
|
+
}>,
|
|
197
|
+
): void {
|
|
198
|
+
const db = getDb();
|
|
199
|
+
const now = Date.now();
|
|
200
|
+
db.insert(conversations)
|
|
201
|
+
.values({ id: conversationId, createdAt: now, updatedAt: now })
|
|
202
|
+
.onConflictDoNothing()
|
|
203
|
+
.run();
|
|
204
|
+
for (const r of rows) {
|
|
205
|
+
const meta: SlackMessageMetadata = {
|
|
206
|
+
source: "slack",
|
|
207
|
+
channelId: SLACK_CHANNEL_ID,
|
|
208
|
+
channelTs: r.channelTs,
|
|
209
|
+
eventKind: "message",
|
|
210
|
+
displayName: r.displayName,
|
|
211
|
+
} as SlackMessageMetadata;
|
|
212
|
+
db.insert(messages)
|
|
213
|
+
.values({
|
|
214
|
+
id: r.id,
|
|
215
|
+
conversationId,
|
|
216
|
+
role: r.role,
|
|
217
|
+
content: JSON.stringify([{ type: "text", text: r.text }]),
|
|
218
|
+
createdAt: r.createdAt,
|
|
219
|
+
metadata: JSON.stringify({
|
|
220
|
+
provenanceTrustClass: "guardian",
|
|
221
|
+
slackMeta: writeSlackMetadata(meta),
|
|
222
|
+
}),
|
|
223
|
+
})
|
|
224
|
+
.run();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// `applyRuntimeInjections` self-resolves the `<active_subagents>` block from the
|
|
229
|
+
// global subagent manager keyed by the conversation, so tests seed children
|
|
230
|
+
// directly into the manager's private maps rather than threading a pre-built
|
|
231
|
+
// block through options.
|
|
232
|
+
interface SubagentManagerTestInternals {
|
|
233
|
+
subagents: Map<string, { state: SubagentState }>;
|
|
234
|
+
parentToChildren: Map<string, Set<string>>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function makeSubagentState(
|
|
238
|
+
id: string,
|
|
239
|
+
label: string,
|
|
240
|
+
status: SubagentState["status"],
|
|
241
|
+
): SubagentState {
|
|
242
|
+
return {
|
|
243
|
+
config: {
|
|
244
|
+
id,
|
|
245
|
+
parentConversationId: TEST_CONVERSATION_ID,
|
|
246
|
+
label,
|
|
247
|
+
objective: "obj",
|
|
248
|
+
},
|
|
249
|
+
status,
|
|
250
|
+
conversationId: `conv-${id}`,
|
|
251
|
+
isFork: false,
|
|
252
|
+
createdAt: Date.now() - 60_000,
|
|
253
|
+
startedAt: Date.now() - 55_000,
|
|
254
|
+
completedAt: status === "completed" ? Date.now() : undefined,
|
|
255
|
+
usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function seedSubagentChild(
|
|
260
|
+
parentConversationId: string,
|
|
261
|
+
state: SubagentState,
|
|
262
|
+
): void {
|
|
263
|
+
const internals =
|
|
264
|
+
getSubagentManager() as unknown as SubagentManagerTestInternals;
|
|
265
|
+
internals.subagents.set(state.config.id, { state });
|
|
266
|
+
const ids =
|
|
267
|
+
internals.parentToChildren.get(parentConversationId) ?? new Set<string>();
|
|
268
|
+
ids.add(state.config.id);
|
|
269
|
+
internals.parentToChildren.set(parentConversationId, ids);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function clearSeededSubagents(): void {
|
|
273
|
+
const internals =
|
|
274
|
+
getSubagentManager() as unknown as SubagentManagerTestInternals;
|
|
275
|
+
internals.subagents.clear();
|
|
276
|
+
internals.parentToChildren.clear();
|
|
277
|
+
}
|
|
278
|
+
|
|
97
279
|
describe("injector chain", () => {
|
|
98
280
|
beforeEach(() => {
|
|
99
281
|
clearPkbContent();
|
|
100
282
|
clearNowScratchpad();
|
|
283
|
+
clearConversations();
|
|
284
|
+
clearSeededSubagents();
|
|
285
|
+
const db = getDb();
|
|
286
|
+
db.delete(messages)
|
|
287
|
+
.where(eq(messages.conversationId, TEST_CONVERSATION_ID))
|
|
288
|
+
.run();
|
|
289
|
+
db.delete(conversations)
|
|
290
|
+
.where(eq(conversations.id, TEST_CONVERSATION_ID))
|
|
291
|
+
.run();
|
|
101
292
|
});
|
|
102
293
|
|
|
103
294
|
test("defaultInjectors lists the defaults in the documented order", () => {
|
|
@@ -188,7 +379,7 @@ describe("injector chain", () => {
|
|
|
188
379
|
];
|
|
189
380
|
|
|
190
381
|
const result = await applyRuntimeInjections(runMessages, {
|
|
191
|
-
|
|
382
|
+
...makeTurnContext(),
|
|
192
383
|
});
|
|
193
384
|
|
|
194
385
|
expect(result.blocks.injectorChainBlock).toBeUndefined();
|
|
@@ -200,22 +391,28 @@ describe("injector chain", () => {
|
|
|
200
391
|
test("applyRuntimeInjections without turnContext still runs the chain under a synthesized context", async () => {
|
|
201
392
|
// The static chain is the canonical injection path, so
|
|
202
393
|
// `applyRuntimeInjections` must drive it even when the caller doesn't
|
|
203
|
-
// pass a `turnContext`.
|
|
204
|
-
//
|
|
205
|
-
//
|
|
394
|
+
// pass a `turnContext`. With no caller-supplied context the assembly
|
|
395
|
+
// falls back to the runtime-assembly fallback conversation id, so the
|
|
396
|
+
// live-sourced unified turn context resolves off that conversation.
|
|
206
397
|
const runMessages: Message[] = [
|
|
207
398
|
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
208
399
|
];
|
|
209
400
|
|
|
401
|
+
// The fallback conversation has no per-turn or origin interface, so the
|
|
402
|
+
// unified-turn-context injector resolves the interface label to the `web`
|
|
403
|
+
// default.
|
|
404
|
+
const synthesizedBlock = buildUnifiedTurnContextBlock({
|
|
405
|
+
timestamp: FIXED_TURN_TIMESTAMP,
|
|
406
|
+
interfaceName: "web",
|
|
407
|
+
});
|
|
408
|
+
seedFallbackTemporalSnapshot();
|
|
210
409
|
const result = await applyRuntimeInjections(runMessages, {
|
|
211
|
-
|
|
410
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
212
411
|
});
|
|
213
412
|
|
|
214
413
|
// The unified-turn-context injector fires even without a caller-supplied
|
|
215
414
|
// turnContext, proving the chain runs under the synthesized context.
|
|
216
|
-
expect(result.blocks.unifiedTurnContext).toBe(
|
|
217
|
-
"<turn_context>\nsynthesized\n</turn_context>",
|
|
218
|
-
);
|
|
415
|
+
expect(result.blocks.unifiedTurnContext).toBe(synthesizedBlock);
|
|
219
416
|
});
|
|
220
417
|
|
|
221
418
|
test("golden-path: default chain injects workspace + unified-turn + PKB + NOW + subagent in the correct positions", async () => {
|
|
@@ -248,16 +445,21 @@ describe("injector chain", () => {
|
|
|
248
445
|
|
|
249
446
|
const workspaceText =
|
|
250
447
|
"<workspace>\nRoot: /sandbox\nDirectories: src, lib\n</workspace>";
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
448
|
+
// The interface label flows through the options bag; the timestamp is
|
|
449
|
+
// computed live at injection time.
|
|
450
|
+
const unifiedTurn = buildUnifiedTurnContextBlock({
|
|
451
|
+
timestamp: FIXED_TURN_TIMESTAMP,
|
|
452
|
+
interfaceName: "macos",
|
|
453
|
+
});
|
|
454
|
+
// A completed child renders deterministically (no elapsed clock), so the
|
|
455
|
+
// block the injector self-resolves matches `buildSubagentStatusBlock`.
|
|
456
|
+
const subagentChild = makeSubagentState("sub-1", "worker", "completed");
|
|
457
|
+
seedSubagentChild(TEST_CONVERSATION_ID, subagentChild);
|
|
458
|
+
const subagentBlock = buildSubagentStatusBlock([subagentChild])!;
|
|
255
459
|
|
|
460
|
+
seedWorkspaceContext(workspaceText, { clientTimezone: null }, "macos");
|
|
256
461
|
const result = await applyRuntimeInjections(runMessages, {
|
|
257
|
-
|
|
258
|
-
workspaceTopLevelContext: workspaceText,
|
|
259
|
-
unifiedTurnContext: unifiedTurn,
|
|
260
|
-
subagentStatusBlock: subagentBlock,
|
|
462
|
+
...makeTurnContext(),
|
|
261
463
|
});
|
|
262
464
|
|
|
263
465
|
// Extract the tail user message content as a list of text strings.
|
|
@@ -295,13 +497,12 @@ describe("injector chain", () => {
|
|
|
295
497
|
);
|
|
296
498
|
});
|
|
297
499
|
|
|
298
|
-
test("slack-messages injector replaces runMessages
|
|
299
|
-
// End-to-end verification for the `replace-run-messages` placement:
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// splices still find them.
|
|
500
|
+
test("slack-messages injector replaces runMessages with the self-resolved transcript", async () => {
|
|
501
|
+
// End-to-end verification for the `replace-run-messages` placement: a
|
|
502
|
+
// Slack channel turn swaps the incoming `runMessages` for the
|
|
503
|
+
// self-resolved chronological transcript before the after-memory/append
|
|
504
|
+
// placements run. Memory-prefix blocks from the original tail are
|
|
505
|
+
// re-prepended onto the new tail so PKB / NOW splices still find them.
|
|
305
506
|
const originalRun: Message[] = [
|
|
306
507
|
{
|
|
307
508
|
role: "user",
|
|
@@ -316,19 +517,30 @@ describe("injector chain", () => {
|
|
|
316
517
|
],
|
|
317
518
|
},
|
|
318
519
|
];
|
|
319
|
-
|
|
520
|
+
seedSlackChannelRows(TEST_CONVERSATION_ID, [
|
|
320
521
|
{
|
|
522
|
+
id: "s1",
|
|
321
523
|
role: "user",
|
|
322
|
-
|
|
524
|
+
text: "kickoff",
|
|
525
|
+
channelTs: "1700000000.000001",
|
|
526
|
+
displayName: "alice",
|
|
527
|
+
createdAt: 1700000000_000,
|
|
323
528
|
},
|
|
324
529
|
{
|
|
530
|
+
id: "s2",
|
|
325
531
|
role: "user",
|
|
326
|
-
|
|
532
|
+
text: "What's happening?",
|
|
533
|
+
channelTs: "1700000005.000001",
|
|
534
|
+
displayName: "user",
|
|
535
|
+
createdAt: 1700000005_000,
|
|
327
536
|
},
|
|
328
|
-
];
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
537
|
+
]);
|
|
538
|
+
setConversation(TEST_CONVERSATION_ID, {
|
|
539
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
540
|
+
workingDir: "/sandbox",
|
|
541
|
+
workspaceTopLevelContext: "",
|
|
542
|
+
workspaceTopLevelDirty: false,
|
|
543
|
+
trustContext: { trustClass: "guardian" },
|
|
332
544
|
channelCapabilities: {
|
|
333
545
|
channel: "slack",
|
|
334
546
|
dashboardCapable: false,
|
|
@@ -336,7 +548,9 @@ describe("injector chain", () => {
|
|
|
336
548
|
supportsVoiceInput: false,
|
|
337
549
|
chatType: "channel",
|
|
338
550
|
},
|
|
339
|
-
|
|
551
|
+
} as never);
|
|
552
|
+
const result = await applyRuntimeInjections(originalRun, {
|
|
553
|
+
...makeTurnContext(),
|
|
340
554
|
});
|
|
341
555
|
|
|
342
556
|
// The swap replaced the run-messages wholesale but preserved the
|
|
@@ -354,7 +568,7 @@ describe("injector chain", () => {
|
|
|
354
568
|
true,
|
|
355
569
|
);
|
|
356
570
|
expect(texts).toContain("<memory __injected>\nrecalled fact\n</memory>");
|
|
357
|
-
expect(texts[texts.length - 1]).
|
|
571
|
+
expect(texts[texts.length - 1]).toContain("What's happening?");
|
|
358
572
|
});
|
|
359
573
|
|
|
360
574
|
test("minimal mode: only unified-turn-context survives; workspace/PKB/NOW/subagent are skipped", async () => {
|
|
@@ -363,6 +577,21 @@ describe("injector chain", () => {
|
|
|
363
577
|
// opts out in minimal mode, so the tail should carry only the turn
|
|
364
578
|
// context prepend plus any non-injector hardcoded content (none
|
|
365
579
|
// here).
|
|
580
|
+
// Empty workspace text keeps that injector inert while the unified
|
|
581
|
+
// turn-context timestamp flows through the conversation's frozen temporal
|
|
582
|
+
// snapshot. The interface label is sourced from the live conversation,
|
|
583
|
+
// which has no per-turn or origin interface here and so resolves to the
|
|
584
|
+
// `web` default. A live child subagent is seeded so the subagent-status
|
|
585
|
+
// injector has a block to skip.
|
|
586
|
+
const minimalTurnBlock = buildUnifiedTurnContextBlock({
|
|
587
|
+
timestamp: FIXED_TURN_TIMESTAMP,
|
|
588
|
+
interfaceName: "web",
|
|
589
|
+
});
|
|
590
|
+
seedWorkspaceContext("", { clientTimezone: null });
|
|
591
|
+
seedSubagentChild(
|
|
592
|
+
TEST_CONVERSATION_ID,
|
|
593
|
+
makeSubagentState("sub-1", "worker", "running"),
|
|
594
|
+
);
|
|
366
595
|
const result = await applyRuntimeInjections(
|
|
367
596
|
[
|
|
368
597
|
{
|
|
@@ -371,11 +600,8 @@ describe("injector chain", () => {
|
|
|
371
600
|
},
|
|
372
601
|
],
|
|
373
602
|
{
|
|
374
|
-
|
|
603
|
+
...makeTurnContext(),
|
|
375
604
|
mode: "minimal",
|
|
376
|
-
workspaceTopLevelContext: "<workspace>...</workspace>",
|
|
377
|
-
unifiedTurnContext: "<turn_context>...</turn_context>",
|
|
378
|
-
subagentStatusBlock: "<active_subagents>...</active_subagents>",
|
|
379
605
|
},
|
|
380
606
|
);
|
|
381
607
|
|
|
@@ -384,10 +610,8 @@ describe("injector chain", () => {
|
|
|
384
610
|
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
385
611
|
.map((b) => b.text);
|
|
386
612
|
|
|
387
|
-
expect(texts).toEqual([
|
|
388
|
-
expect(result.blocks.unifiedTurnContext).toBe(
|
|
389
|
-
"<turn_context>...</turn_context>",
|
|
390
|
-
);
|
|
613
|
+
expect(texts).toEqual([minimalTurnBlock, "hi"]);
|
|
614
|
+
expect(result.blocks.unifiedTurnContext).toBe(minimalTurnBlock);
|
|
391
615
|
expect(result.blocks.workspaceBlock).toBeUndefined();
|
|
392
616
|
expect(result.blocks.pkbContextBlock).toBeUndefined();
|
|
393
617
|
expect(result.blocks.nowScratchpadBlock).toBeUndefined();
|