@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
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
clearConversations,
|
|
5
|
+
setConversation,
|
|
6
|
+
} from "../daemon/conversation-registry.js";
|
|
3
7
|
import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
|
|
8
|
+
import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
|
|
9
|
+
import { createApp } from "../memory/app-store.js";
|
|
4
10
|
import type { Message } from "../providers/types.js";
|
|
5
11
|
|
|
6
12
|
// ---------------------------------------------------------------------------
|
|
@@ -11,6 +17,46 @@ function userMsg(text: string): Message {
|
|
|
11
17
|
return { role: "user", content: [{ type: "text", text }] };
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
// `applyRuntimeInjections` synthesizes this conversation id when no
|
|
21
|
+
// `turnContext` is supplied, so the `workspace-context` injector resolves the
|
|
22
|
+
// live workspace block from the registry under this key.
|
|
23
|
+
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
24
|
+
|
|
25
|
+
// Register the fallback conversation in the live registry so the runtime
|
|
26
|
+
// injectors resolve their blocks from it (the orchestrator no longer threads
|
|
27
|
+
// workspace or active-surface content as options).
|
|
28
|
+
function registerFallbackConversation(fields: Record<string, unknown>): void {
|
|
29
|
+
setConversation(FALLBACK_CONVERSATION_ID, {
|
|
30
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
31
|
+
workingDir: "/sandbox",
|
|
32
|
+
// Non-dirty empty workspace by default so the workspace-context injector
|
|
33
|
+
// skips both the filesystem rescan and the DB refresh unless a test
|
|
34
|
+
// explicitly seeds a block via `workspaceTopLevelContext`.
|
|
35
|
+
workspaceTopLevelContext: "",
|
|
36
|
+
workspaceTopLevelDirty: false,
|
|
37
|
+
...fields,
|
|
38
|
+
} as never);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Seed the live conversation registry with a pre-rendered top-level block. The
|
|
42
|
+
// cache is non-dirty with non-null content, so `resolveWorkspaceTopLevelContext`
|
|
43
|
+
// returns it verbatim without rescanning the filesystem.
|
|
44
|
+
function seedWorkspaceContext(text: string): void {
|
|
45
|
+
registerFallbackConversation({
|
|
46
|
+
workspaceTopLevelContext: text,
|
|
47
|
+
workspaceTopLevelDirty: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build the conversation surface-state map that `buildActiveSurfaceContext`
|
|
52
|
+
// reads to render the `<active_workspace>` block.
|
|
53
|
+
function makeSurfaceState(
|
|
54
|
+
surfaceId: string,
|
|
55
|
+
data: SurfaceData,
|
|
56
|
+
): Map<string, { surfaceType: SurfaceType; data: SurfaceData }> {
|
|
57
|
+
return new Map([[surfaceId, { surfaceType: "dynamic_page", data }]]);
|
|
58
|
+
}
|
|
59
|
+
|
|
14
60
|
// ---------------------------------------------------------------------------
|
|
15
61
|
// Tests
|
|
16
62
|
// ---------------------------------------------------------------------------
|
|
@@ -19,14 +65,21 @@ const sampleContext =
|
|
|
19
65
|
"<workspace>\nRoot: /sandbox\nDirectories: src, lib, tests\n</workspace>";
|
|
20
66
|
|
|
21
67
|
// The workspace-context default injector emits the workspace block as a
|
|
22
|
-
// `prepend-user-tail` placement during `applyRuntimeInjections`.
|
|
23
|
-
//
|
|
68
|
+
// `prepend-user-tail` placement during `applyRuntimeInjections`. It sources
|
|
69
|
+
// the rendered block from the per-conversation workspace registry and
|
|
70
|
+
// (re)injects only when the block is absent from the working messages, so the
|
|
71
|
+
// suite seeds the registry and exercises that end-to-end path.
|
|
24
72
|
|
|
25
73
|
describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
26
|
-
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
clearConversations();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("injects workspace context when registered", async () => {
|
|
79
|
+
seedWorkspaceContext(sampleContext);
|
|
27
80
|
const messages: Message[] = [userMsg("Hello")];
|
|
28
81
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
29
|
-
|
|
82
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
30
83
|
});
|
|
31
84
|
|
|
32
85
|
expect(result).toHaveLength(1);
|
|
@@ -35,21 +88,43 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
35
88
|
expect((result[0].content[1] as { text: string }).text).toBe("Hello");
|
|
36
89
|
});
|
|
37
90
|
|
|
38
|
-
test("does not inject when workspace context is
|
|
91
|
+
test("does not inject when no workspace context is registered", async () => {
|
|
39
92
|
const messages: Message[] = [userMsg("Hello")];
|
|
40
93
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
41
|
-
|
|
94
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
42
95
|
});
|
|
43
96
|
|
|
44
97
|
expect(result).toHaveLength(1);
|
|
45
98
|
expect(result[0].content).toHaveLength(1);
|
|
46
99
|
});
|
|
47
100
|
|
|
101
|
+
test("does not re-inject when the workspace block is already present", async () => {
|
|
102
|
+
// GIVEN the registry holds a workspace block AND the working messages
|
|
103
|
+
// already carry that block (a normal cached turn, post-injection).
|
|
104
|
+
seedWorkspaceContext(sampleContext);
|
|
105
|
+
const messages: Message[] = [userMsg(sampleContext), userMsg("Hello")];
|
|
106
|
+
|
|
107
|
+
// WHEN injections are applied
|
|
108
|
+
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
109
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// THEN presence detection skips the block to keep the prefix stable.
|
|
113
|
+
expect(result).toHaveLength(2);
|
|
114
|
+
expect(result[1].content).toHaveLength(1);
|
|
115
|
+
expect((result[1].content[0] as { text: string }).text).toBe("Hello");
|
|
116
|
+
});
|
|
117
|
+
|
|
48
118
|
test("workspace context appears before active surface context in content", async () => {
|
|
119
|
+
registerFallbackConversation({
|
|
120
|
+
workspaceTopLevelContext: sampleContext,
|
|
121
|
+
workspaceTopLevelDirty: false,
|
|
122
|
+
currentActiveSurfaceId: "sf_1",
|
|
123
|
+
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
124
|
+
});
|
|
49
125
|
const messages: Message[] = [userMsg("Hello")];
|
|
50
126
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
51
|
-
|
|
52
|
-
workspaceTopLevelContext: sampleContext,
|
|
127
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
53
128
|
});
|
|
54
129
|
|
|
55
130
|
// Workspace is injected last (in applyRuntimeInjections order) so it
|
|
@@ -64,15 +139,21 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
64
139
|
});
|
|
65
140
|
|
|
66
141
|
test("app-backed active surface tells the model to load app-builder with the right argument", async () => {
|
|
142
|
+
const app = createApp({
|
|
143
|
+
name: "Example App",
|
|
144
|
+
schemaJson: "{}",
|
|
145
|
+
htmlDefinition: "<div>test</div>",
|
|
146
|
+
});
|
|
147
|
+
registerFallbackConversation({
|
|
148
|
+
currentActiveSurfaceId: "sf_1",
|
|
149
|
+
surfaceState: makeSurfaceState("sf_1", {
|
|
150
|
+
html: "<div>test</div>",
|
|
151
|
+
appId: app.id,
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
67
154
|
const messages: Message[] = [userMsg("Edit this app")];
|
|
68
155
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
69
|
-
|
|
70
|
-
surfaceId: "sf_1",
|
|
71
|
-
html: "<div>test</div>",
|
|
72
|
-
appId: "app-1",
|
|
73
|
-
appName: "Example App",
|
|
74
|
-
},
|
|
75
|
-
workspaceTopLevelContext: null,
|
|
156
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
76
157
|
});
|
|
77
158
|
|
|
78
159
|
const activeWorkspaceText = (result[0].content[0] as { text: string }).text;
|
|
@@ -82,10 +163,15 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
|
|
|
82
163
|
});
|
|
83
164
|
|
|
84
165
|
describe("applyRuntimeInjections — minimal mode skips workspace blocks", () => {
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
clearConversations();
|
|
168
|
+
});
|
|
169
|
+
|
|
85
170
|
test("minimal mode skips workspace top-level context", async () => {
|
|
171
|
+
seedWorkspaceContext(sampleContext);
|
|
86
172
|
const messages: Message[] = [userMsg("Hello")];
|
|
87
173
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
88
|
-
|
|
174
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
89
175
|
mode: "minimal",
|
|
90
176
|
});
|
|
91
177
|
|
|
@@ -95,9 +181,13 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
95
181
|
});
|
|
96
182
|
|
|
97
183
|
test("minimal mode skips active surface context", async () => {
|
|
184
|
+
registerFallbackConversation({
|
|
185
|
+
currentActiveSurfaceId: "sf_1",
|
|
186
|
+
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
187
|
+
});
|
|
98
188
|
const messages: Message[] = [userMsg("Hello")];
|
|
99
189
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
100
|
-
|
|
190
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
101
191
|
mode: "minimal",
|
|
102
192
|
});
|
|
103
193
|
|
|
@@ -107,10 +197,15 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
|
|
|
107
197
|
});
|
|
108
198
|
|
|
109
199
|
test("full mode (default) still includes workspace blocks", async () => {
|
|
200
|
+
registerFallbackConversation({
|
|
201
|
+
workspaceTopLevelContext: sampleContext,
|
|
202
|
+
workspaceTopLevelDirty: false,
|
|
203
|
+
currentActiveSurfaceId: "sf_1",
|
|
204
|
+
surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
|
|
205
|
+
});
|
|
110
206
|
const messages: Message[] = [userMsg("Hello")];
|
|
111
207
|
const { messages: result } = await applyRuntimeInjections(messages, {
|
|
112
|
-
|
|
113
|
-
activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
|
|
208
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
114
209
|
});
|
|
115
210
|
|
|
116
211
|
expect(result[0].content).toHaveLength(3);
|
|
@@ -156,6 +156,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
156
156
|
mock.module("../context/window-manager.js", () => ({
|
|
157
157
|
ContextWindowManager: class {
|
|
158
158
|
constructor() {}
|
|
159
|
+
updateConfig() {}
|
|
159
160
|
shouldCompact() {
|
|
160
161
|
return { needed: false, estimatedTokens: 0 };
|
|
161
162
|
}
|
|
@@ -221,10 +222,11 @@ mock.module("../agent/loop.js", () => ({
|
|
|
221
222
|
getActiveModel() {
|
|
222
223
|
return undefined;
|
|
223
224
|
}
|
|
224
|
-
async run(
|
|
225
|
-
messages: Message[]
|
|
226
|
-
onEvent: (event: AgentEvent) => void | Promise<void
|
|
227
|
-
): Promise<AgentLoopRunResult> {
|
|
225
|
+
async run(options: {
|
|
226
|
+
messages: Message[];
|
|
227
|
+
onEvent: (event: AgentEvent) => void | Promise<void>;
|
|
228
|
+
}): Promise<AgentLoopRunResult> {
|
|
229
|
+
const { messages, onEvent } = options;
|
|
228
230
|
const history = await new Promise<Message[]>((resolve) => {
|
|
229
231
|
pendingRuns.push({ resolve, messages, onEvent });
|
|
230
232
|
});
|
|
@@ -157,6 +157,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
157
157
|
mock.module("../context/window-manager.js", () => ({
|
|
158
158
|
ContextWindowManager: class {
|
|
159
159
|
constructor() {}
|
|
160
|
+
updateConfig() {}
|
|
160
161
|
shouldCompact() {
|
|
161
162
|
return { needed: false, estimatedTokens: 0 };
|
|
162
163
|
}
|
|
@@ -245,10 +246,11 @@ mock.module("../agent/loop.js", () => ({
|
|
|
245
246
|
getActiveModel() {
|
|
246
247
|
return undefined;
|
|
247
248
|
}
|
|
248
|
-
async run(
|
|
249
|
-
messages: Message[]
|
|
250
|
-
onEvent: (event: AgentEvent) => void
|
|
251
|
-
): Promise<Message[]> {
|
|
249
|
+
async run(options: {
|
|
250
|
+
messages: Message[];
|
|
251
|
+
onEvent: (event: AgentEvent) => void;
|
|
252
|
+
}): Promise<Message[]> {
|
|
253
|
+
const { messages, onEvent } = options;
|
|
252
254
|
// Prime the assistant row anchor — production code emits this from
|
|
253
255
|
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
254
256
|
await onEvent({ type: "llm_call_started" });
|
|
@@ -173,6 +173,7 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
173
173
|
mock.module("../context/window-manager.js", () => ({
|
|
174
174
|
ContextWindowManager: class {
|
|
175
175
|
constructor() {}
|
|
176
|
+
updateConfig() {}
|
|
176
177
|
shouldCompact() {
|
|
177
178
|
return { needed: false, estimatedTokens: 0 };
|
|
178
179
|
}
|
|
@@ -198,11 +199,11 @@ let lastAgentLoopConfig: Partial<AgentLoopConfig> | undefined;
|
|
|
198
199
|
mock.module("../agent/loop.js", () => ({
|
|
199
200
|
AgentLoop: class {
|
|
200
201
|
compactionCircuit = new CompactionCircuit("test-conv");
|
|
201
|
-
constructor(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
) {
|
|
202
|
+
constructor(options?: {
|
|
203
|
+
provider?: unknown;
|
|
204
|
+
systemPrompt?: string;
|
|
205
|
+
config?: Partial<AgentLoopConfig>;
|
|
206
|
+
}) {
|
|
206
207
|
lastAgentLoopConfig = options?.config;
|
|
207
208
|
}
|
|
208
209
|
getToolTokenBudget() {
|
|
@@ -214,10 +215,10 @@ mock.module("../agent/loop.js", () => ({
|
|
|
214
215
|
getActiveModel() {
|
|
215
216
|
return undefined;
|
|
216
217
|
}
|
|
217
|
-
async run(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
): Promise<Message[]> {
|
|
218
|
+
async run(_options: {
|
|
219
|
+
messages: Message[];
|
|
220
|
+
onEvent: (event: AgentEvent) => void;
|
|
221
|
+
}): Promise<Message[]> {
|
|
221
222
|
return [];
|
|
222
223
|
}
|
|
223
224
|
},
|
|
@@ -339,8 +339,8 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
339
339
|
|
|
340
340
|
test("does not re-enqueue for invalid items when within cooldown period", async () => {
|
|
341
341
|
const now = Date.now();
|
|
342
|
-
//
|
|
343
|
-
const
|
|
342
|
+
// Last attempt 30 seconds ago (within the 5-minute cooldown)
|
|
343
|
+
const recentAttemptAt = now - 30_000;
|
|
344
344
|
insertStarter({
|
|
345
345
|
label: "Let me check calendar",
|
|
346
346
|
prompt: "Let me check what Alice has today.",
|
|
@@ -353,9 +353,13 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
353
353
|
category: "productivity",
|
|
354
354
|
createdAt: now - 1,
|
|
355
355
|
});
|
|
356
|
+
setCheckpoint(
|
|
357
|
+
"conversation_starters:last_attempt_at:default",
|
|
358
|
+
String(recentAttemptAt),
|
|
359
|
+
);
|
|
356
360
|
setCheckpoint(
|
|
357
361
|
"conversation_starters:last_gen_at:default",
|
|
358
|
-
String(
|
|
362
|
+
String(recentAttemptAt),
|
|
359
363
|
);
|
|
360
364
|
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
|
|
361
365
|
insertMemoryItem();
|
|
@@ -375,8 +379,8 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
375
379
|
|
|
376
380
|
test("re-enqueues for invalid items after cooldown expires", async () => {
|
|
377
381
|
const now = Date.now();
|
|
378
|
-
//
|
|
379
|
-
const
|
|
382
|
+
// Last attempt 6 minutes ago (past the 5-minute cooldown)
|
|
383
|
+
const oldAttemptAt = now - 6 * 60_000;
|
|
380
384
|
insertStarter({
|
|
381
385
|
label: "Let me check calendar",
|
|
382
386
|
prompt: "Let me check what Alice has today.",
|
|
@@ -389,9 +393,13 @@ describe("GET /v1/conversation-starters", () => {
|
|
|
389
393
|
category: "productivity",
|
|
390
394
|
createdAt: now - 1,
|
|
391
395
|
});
|
|
396
|
+
setCheckpoint(
|
|
397
|
+
"conversation_starters:last_attempt_at:default",
|
|
398
|
+
String(oldAttemptAt),
|
|
399
|
+
);
|
|
392
400
|
setCheckpoint(
|
|
393
401
|
"conversation_starters:last_gen_at:default",
|
|
394
|
-
String(
|
|
402
|
+
String(oldAttemptAt),
|
|
395
403
|
);
|
|
396
404
|
setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
|
|
397
405
|
insertMemoryItem();
|
|
@@ -140,6 +140,7 @@ mock.module("../memory/retrieval-budget.js", () => ({
|
|
|
140
140
|
mock.module("../context/window-manager.js", () => ({
|
|
141
141
|
ContextWindowManager: class {
|
|
142
142
|
constructor() {}
|
|
143
|
+
updateConfig() {}
|
|
143
144
|
shouldCompact() {
|
|
144
145
|
return { needed: false, estimatedTokens: 0 };
|
|
145
146
|
}
|
|
@@ -176,10 +177,11 @@ mock.module("../agent/loop.js", () => ({
|
|
|
176
177
|
getActiveModel() {
|
|
177
178
|
return undefined;
|
|
178
179
|
}
|
|
179
|
-
async run(
|
|
180
|
-
messages: Message[]
|
|
181
|
-
onEvent: (event: AgentEvent) => void
|
|
182
|
-
): Promise<Message[]> {
|
|
180
|
+
async run(options: {
|
|
181
|
+
messages: Message[];
|
|
182
|
+
onEvent: (event: AgentEvent) => void;
|
|
183
|
+
}): Promise<Message[]> {
|
|
184
|
+
const { messages, onEvent } = options;
|
|
183
185
|
const assistantMessage: Message = {
|
|
184
186
|
role: "assistant",
|
|
185
187
|
content: [{ type: "text", text: "ok" }],
|
|
@@ -198,6 +200,7 @@ mock.module("../agent/loop.js", () => ({
|
|
|
198
200
|
}));
|
|
199
201
|
|
|
200
202
|
import { Conversation } from "../daemon/conversation.js";
|
|
203
|
+
import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
|
|
201
204
|
|
|
202
205
|
// ---------------------------------------------------------------------------
|
|
203
206
|
// Helpers
|
|
@@ -249,7 +252,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
249
252
|
});
|
|
250
253
|
|
|
251
254
|
test("refreshWorkspaceTopLevelContextIfNeeded populates context and clears dirty", () => {
|
|
252
|
-
|
|
255
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
253
256
|
|
|
254
257
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
255
258
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
@@ -265,10 +268,10 @@ describe("Conversation workspace cache state", () => {
|
|
|
265
268
|
});
|
|
266
269
|
|
|
267
270
|
test("refreshWorkspaceTopLevelContextIfNeeded no-ops when not dirty and cache exists", () => {
|
|
268
|
-
|
|
271
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
269
272
|
const first = conversation.getWorkspaceTopLevelContext();
|
|
270
273
|
|
|
271
|
-
|
|
274
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
272
275
|
const second = conversation.getWorkspaceTopLevelContext();
|
|
273
276
|
|
|
274
277
|
// Same reference — no recomputation
|
|
@@ -276,7 +279,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
276
279
|
});
|
|
277
280
|
|
|
278
281
|
test("markWorkspaceTopLevelDirty sets dirty flag", () => {
|
|
279
|
-
|
|
282
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
280
283
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
281
284
|
|
|
282
285
|
conversation.markWorkspaceTopLevelDirty();
|
|
@@ -284,10 +287,10 @@ describe("Conversation workspace cache state", () => {
|
|
|
284
287
|
});
|
|
285
288
|
|
|
286
289
|
test("refresh after marking dirty produces fresh context", () => {
|
|
287
|
-
|
|
290
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
288
291
|
|
|
289
292
|
conversation.markWorkspaceTopLevelDirty();
|
|
290
|
-
|
|
293
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
291
294
|
|
|
292
295
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
293
296
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
@@ -299,7 +302,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
299
302
|
test("renders client-reported host env when set on the conversation", () => {
|
|
300
303
|
conversation.hostHomeDir = "/Users/alice";
|
|
301
304
|
conversation.hostUsername = "alice";
|
|
302
|
-
|
|
305
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
303
306
|
|
|
304
307
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
305
308
|
expect(block).not.toBeNull();
|
|
@@ -309,7 +312,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
309
312
|
|
|
310
313
|
test("falls back to daemon os info when client host env is absent", async () => {
|
|
311
314
|
const { homedir, userInfo } = await import("node:os");
|
|
312
|
-
|
|
315
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
313
316
|
|
|
314
317
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
315
318
|
expect(block).not.toBeNull();
|
|
@@ -320,7 +323,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
320
323
|
test("re-renders with updated host env after marking dirty", () => {
|
|
321
324
|
conversation.hostHomeDir = "/Users/alice";
|
|
322
325
|
conversation.hostUsername = "alice";
|
|
323
|
-
|
|
326
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
324
327
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
325
328
|
"Host home directory: /Users/alice",
|
|
326
329
|
);
|
|
@@ -328,7 +331,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
328
331
|
conversation.hostHomeDir = "/Users/bob";
|
|
329
332
|
conversation.hostUsername = "bob";
|
|
330
333
|
conversation.markWorkspaceTopLevelDirty();
|
|
331
|
-
|
|
334
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
332
335
|
|
|
333
336
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
334
337
|
expect(block).not.toBeNull();
|
|
@@ -344,7 +347,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
344
347
|
// Simulate a macOS turn populating host env.
|
|
345
348
|
conversation.hostHomeDir = "/Users/alice";
|
|
346
349
|
conversation.hostUsername = "alice";
|
|
347
|
-
|
|
350
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
348
351
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
349
352
|
"Host home directory: /Users/alice",
|
|
350
353
|
);
|
|
@@ -355,7 +358,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
355
358
|
conversation.hostHomeDir = undefined;
|
|
356
359
|
conversation.hostUsername = undefined;
|
|
357
360
|
conversation.markWorkspaceTopLevelDirty();
|
|
358
|
-
|
|
361
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
359
362
|
|
|
360
363
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
361
364
|
expect(block).not.toBeNull();
|
|
@@ -381,7 +384,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
381
384
|
expect(conversation.hostUsername).toBe("alice");
|
|
382
385
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
|
|
383
386
|
|
|
384
|
-
|
|
387
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
385
388
|
const block = conversation.getWorkspaceTopLevelContext();
|
|
386
389
|
expect(block!).toContain("Host home directory: /Users/alice");
|
|
387
390
|
expect(block!).toContain("Host username: alice");
|
|
@@ -455,7 +458,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
455
458
|
hostUsername: "alice",
|
|
456
459
|
});
|
|
457
460
|
// Render once so the dirty flag clears.
|
|
458
|
-
|
|
461
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
459
462
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
460
463
|
|
|
461
464
|
// Re-apply the same values — dirty flag should remain false so we don't
|
|
@@ -476,7 +479,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
476
479
|
hostHomeDir: "/Users/alice",
|
|
477
480
|
hostUsername: "alice",
|
|
478
481
|
});
|
|
479
|
-
|
|
482
|
+
refreshWorkspaceTopLevelContextIfNeeded(conversation);
|
|
480
483
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
481
484
|
|
|
482
485
|
// New values — should mark dirty so the next render picks them up.
|
|
@@ -500,7 +503,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
500
503
|
|
|
501
504
|
try {
|
|
502
505
|
const tempConversation = makeConversation(workspaceRoot);
|
|
503
|
-
|
|
506
|
+
refreshWorkspaceTopLevelContextIfNeeded(tempConversation);
|
|
504
507
|
|
|
505
508
|
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
506
509
|
`Current conversation attachments: conversations/${legacyDirName}/attachments/`,
|
|
@@ -144,7 +144,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
144
144
|
getMessageById: () => null,
|
|
145
145
|
getLastUserTimestampBefore: () => 0,
|
|
146
146
|
setLastNotifiedInferenceProfile: () => {},
|
|
147
|
-
|
|
147
|
+
resolveOverrideProfile: () => undefined,
|
|
148
148
|
updateMessageMetadata: () => {},
|
|
149
149
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
150
150
|
updateMessageContent: mock(() => {}),
|
|
@@ -183,6 +183,7 @@ mock.module("../memory/retrieval-budget.js", () => ({
|
|
|
183
183
|
}));
|
|
184
184
|
mock.module("../context/window-manager.js", () => ({
|
|
185
185
|
ContextWindowManager: class {
|
|
186
|
+
updateConfig() {}
|
|
186
187
|
shouldCompact() {
|
|
187
188
|
return { needed: false, estimatedTokens: 0 };
|
|
188
189
|
}
|
|
@@ -244,10 +245,11 @@ mock.module("../agent/loop.js", () => ({
|
|
|
244
245
|
getActiveModel() {
|
|
245
246
|
return undefined;
|
|
246
247
|
}
|
|
247
|
-
async run(
|
|
248
|
-
messages: Message[]
|
|
249
|
-
onEvent: (event: AgentEvent) => void
|
|
250
|
-
): Promise<AgentLoopRunResult> {
|
|
248
|
+
async run(options: {
|
|
249
|
+
messages: Message[];
|
|
250
|
+
onEvent: (event: AgentEvent) => void;
|
|
251
|
+
}): Promise<AgentLoopRunResult> {
|
|
252
|
+
const { messages, onEvent } = options;
|
|
251
253
|
// Prime the assistant row anchor — production code emits this from
|
|
252
254
|
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
253
255
|
await onEvent({ type: "llm_call_started" });
|
|
@@ -295,6 +297,14 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
|
295
297
|
}));
|
|
296
298
|
|
|
297
299
|
import { Conversation } from "../daemon/conversation.js";
|
|
300
|
+
import {
|
|
301
|
+
clearConversations,
|
|
302
|
+
findConversation,
|
|
303
|
+
removeSubagentConversation,
|
|
304
|
+
setConversation,
|
|
305
|
+
setSubagentConversation,
|
|
306
|
+
} from "../daemon/conversation-registry.js";
|
|
307
|
+
import { resolveWorkspaceTopLevelContext } from "../daemon/conversation-workspace.js";
|
|
298
308
|
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
299
309
|
|
|
300
310
|
function makeConversation(): Conversation {
|
|
@@ -309,7 +319,7 @@ function makeConversation(): Conversation {
|
|
|
309
319
|
};
|
|
310
320
|
},
|
|
311
321
|
};
|
|
312
|
-
|
|
322
|
+
const conversation = new Conversation(
|
|
313
323
|
"conv-1",
|
|
314
324
|
provider,
|
|
315
325
|
"system prompt",
|
|
@@ -317,6 +327,10 @@ function makeConversation(): Conversation {
|
|
|
317
327
|
"/tmp",
|
|
318
328
|
{ maxTokens: 4096 },
|
|
319
329
|
);
|
|
330
|
+
// Mirror production: top-level conversations are registered in the store, so
|
|
331
|
+
// the workspace injector can resolve them by id via the conversation registry.
|
|
332
|
+
setConversation(conversation.conversationId, conversation);
|
|
333
|
+
return conversation;
|
|
320
334
|
}
|
|
321
335
|
|
|
322
336
|
function messageText(message: Message): string {
|
|
@@ -335,6 +349,7 @@ describe("Conversation workspace injection", () => {
|
|
|
335
349
|
runCalls = [];
|
|
336
350
|
agentLoopScript = () => {};
|
|
337
351
|
scanCallCount = 0;
|
|
352
|
+
clearConversations();
|
|
338
353
|
resetPluginRegistryAndRegisterDefaults();
|
|
339
354
|
});
|
|
340
355
|
|
|
@@ -417,11 +432,58 @@ describe("Conversation workspace injection", () => {
|
|
|
417
432
|
});
|
|
418
433
|
});
|
|
419
434
|
|
|
435
|
+
describe("Conversation workspace injection — subagents", () => {
|
|
436
|
+
beforeEach(() => {
|
|
437
|
+
scanCallCount = 0;
|
|
438
|
+
clearConversations();
|
|
439
|
+
resetPluginRegistryAndRegisterDefaults();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("workspace context resolves for subagent conversations not in the store", () => {
|
|
443
|
+
const provider = {
|
|
444
|
+
name: "mock",
|
|
445
|
+
async sendMessage(): Promise<ProviderResponse> {
|
|
446
|
+
return {
|
|
447
|
+
content: [],
|
|
448
|
+
model: "mock",
|
|
449
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
450
|
+
stopReason: "end_turn",
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const subagentId = "subagent-conv-1";
|
|
455
|
+
const conversation = new Conversation(
|
|
456
|
+
subagentId,
|
|
457
|
+
provider,
|
|
458
|
+
"system prompt",
|
|
459
|
+
() => {},
|
|
460
|
+
"/tmp",
|
|
461
|
+
{ maxTokens: 4096 },
|
|
462
|
+
);
|
|
463
|
+
setSubagentConversation(subagentId, conversation);
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
// Subagents live only in the manager's index, never in the
|
|
467
|
+
// eviction-managed store, so the store lookup must not see them.
|
|
468
|
+
expect(findConversation(subagentId)).toBeUndefined();
|
|
469
|
+
|
|
470
|
+
// The per-conversation workspace lookup still resolves them, so subagent
|
|
471
|
+
// turns retain workspace grounding.
|
|
472
|
+
const context = resolveWorkspaceTopLevelContext(subagentId);
|
|
473
|
+
expect(context).not.toBeNull();
|
|
474
|
+
expect(context).toContain("Root: /tmp");
|
|
475
|
+
} finally {
|
|
476
|
+
removeSubagentConversation(subagentId, conversation);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
420
481
|
describe("Conversation workspace dirty-refresh E2E", () => {
|
|
421
482
|
beforeEach(() => {
|
|
422
483
|
runCalls = [];
|
|
423
484
|
agentLoopScript = () => {};
|
|
424
485
|
scanCallCount = 0;
|
|
486
|
+
clearConversations();
|
|
425
487
|
resetPluginRegistryAndRegisterDefaults();
|
|
426
488
|
});
|
|
427
489
|
|