@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
|
@@ -2,25 +2,28 @@
|
|
|
2
2
|
* Tests for the ACP route handlers.
|
|
3
3
|
*
|
|
4
4
|
* Suites:
|
|
5
|
-
* - POST /v1/acp/spawn
|
|
6
|
-
*
|
|
5
|
+
* - POST /v1/acp/spawn: resolver failure paths (acp_disabled,
|
|
6
|
+
* unknown_agent) and the body-shape guard. The binary_not_found /
|
|
7
|
+
* auto-install surface is covered in `__tests__/acp-routes.test.ts`.
|
|
7
8
|
* - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
|
|
8
9
|
* from the credential broker (policy-gated + audited) and merged into
|
|
9
10
|
* `agentConfig.env` ONLY for the `claude` agent.
|
|
10
11
|
* - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
|
|
11
12
|
* wipes terminal-state rows (completed/failed/cancelled) from
|
|
12
|
-
* `acp_session_history` while leaving running/initializing rows
|
|
13
|
+
* `acp_session_history` while leaving running/initializing rows and
|
|
14
|
+
* rows with an in-flight resume intact.
|
|
13
15
|
* - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
|
|
14
|
-
* running → 409, unknown id → idempotent
|
|
16
|
+
* running or mid-resume → 409, unknown id → idempotent
|
|
17
|
+
* { deleted: false }.
|
|
15
18
|
*
|
|
16
19
|
* The spawn tests mirror the resolver's test setup using the shared
|
|
17
20
|
* `installAcpConfigStub` and `installWhichStub` helpers so the host
|
|
18
21
|
* environment doesn't influence the resolver's PATH preflight.
|
|
19
22
|
*
|
|
20
|
-
* The
|
|
21
|
-
*
|
|
22
|
-
* and use the real DB (initialized via the test preload's
|
|
23
|
-
* workspace) to verify the row is actually removed.
|
|
23
|
+
* The delete tests stub `getAcpSessionManager` so we can drive the
|
|
24
|
+
* in-memory-status and pending-resume checks without spawning real ACP
|
|
25
|
+
* child processes, and use the real DB (initialized via the test preload's
|
|
26
|
+
* per-file workspace) to verify the row is actually removed.
|
|
24
27
|
*/
|
|
25
28
|
|
|
26
29
|
import {
|
|
@@ -48,8 +51,10 @@ afterAll(() => {
|
|
|
48
51
|
// in-memory-status check without spawning real ACP processes, and so the
|
|
49
52
|
// env-injection spawn tests can capture the `agentConfig` arg without
|
|
50
53
|
// launching a real subprocess. Stored in mutable state so individual tests
|
|
51
|
-
// can plant arbitrary states / inspect capture.
|
|
54
|
+
// can plant arbitrary states / inspect capture. `pendingResumeIds` models
|
|
55
|
+
// ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
|
|
52
56
|
const inMemoryStates = new Map<string, AcpSessionState>();
|
|
57
|
+
const pendingResumeIds = new Set<string>();
|
|
53
58
|
|
|
54
59
|
interface CapturedSpawn {
|
|
55
60
|
agent: string;
|
|
@@ -70,6 +75,9 @@ mock.module("../../acp/index.js", () => ({
|
|
|
70
75
|
if (!state) throw new Error(`ACP session "${id}" not found`);
|
|
71
76
|
return state;
|
|
72
77
|
},
|
|
78
|
+
getActiveAndPendingIds: () => [
|
|
79
|
+
...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
|
|
80
|
+
],
|
|
73
81
|
spawn: async (
|
|
74
82
|
agent: string,
|
|
75
83
|
agentConfig: { env?: Record<string, string> },
|
|
@@ -226,22 +234,6 @@ describe("POST /v1/acp/spawn", () => {
|
|
|
226
234
|
).rejects.toThrow('Unknown agent "nonexistent"');
|
|
227
235
|
});
|
|
228
236
|
|
|
229
|
-
test("throws FailedDependencyError when the agent binary is missing", async () => {
|
|
230
|
-
config.setConfig({ agents: {} });
|
|
231
|
-
which.setWhich({});
|
|
232
|
-
|
|
233
|
-
const handler = getSpawnHandler();
|
|
234
|
-
await expect(
|
|
235
|
-
handler({
|
|
236
|
-
body: {
|
|
237
|
-
agent: "codex",
|
|
238
|
-
task: "do a thing",
|
|
239
|
-
conversationId: "conv-1",
|
|
240
|
-
},
|
|
241
|
-
}),
|
|
242
|
-
).rejects.toThrow("codex-acp is not on PATH");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
237
|
test("body-shape guard short-circuits before the resolver runs", async () => {
|
|
246
238
|
config.setConfig({ enabled: false });
|
|
247
239
|
|
|
@@ -457,6 +449,8 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
457
449
|
});
|
|
458
450
|
|
|
459
451
|
beforeEach(() => {
|
|
452
|
+
inMemoryStates.clear();
|
|
453
|
+
pendingResumeIds.clear();
|
|
460
454
|
getSqlite().run("DELETE FROM acp_session_history");
|
|
461
455
|
});
|
|
462
456
|
|
|
@@ -482,6 +476,57 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
|
|
|
482
476
|
]);
|
|
483
477
|
});
|
|
484
478
|
|
|
479
|
+
test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
|
|
480
|
+
// A resumed session reuses its original id: the in-memory state is
|
|
481
|
+
// `running` while its history row still carries the old terminal
|
|
482
|
+
// status until the next terminal upsert. The bulk delete must not
|
|
483
|
+
// remove that row out from under the live session.
|
|
484
|
+
seedHistoryRow("row-resumed", "completed", 1000);
|
|
485
|
+
seedHistoryRow("row-completed", "completed", 2000);
|
|
486
|
+
inMemoryStates.set("row-resumed", {
|
|
487
|
+
id: "row-resumed",
|
|
488
|
+
agentId: "claude",
|
|
489
|
+
acpSessionId: "proto-resumed",
|
|
490
|
+
parentConversationId: "conv-test",
|
|
491
|
+
status: "running",
|
|
492
|
+
startedAt: 1000,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const handler = getBulkDeleteHandler();
|
|
496
|
+
const result = (await handler({
|
|
497
|
+
queryParams: { status: "completed" },
|
|
498
|
+
})) as {
|
|
499
|
+
deleted: number;
|
|
500
|
+
};
|
|
501
|
+
expect(result.deleted).toBe(1);
|
|
502
|
+
|
|
503
|
+
const remaining = listRows();
|
|
504
|
+
expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("excludes terminal rows whose session has a resume in flight", async () => {
|
|
508
|
+
// A resume that is still awaiting env preparation has no in-memory
|
|
509
|
+
// SessionEntry yet, but its history row (still terminal) must survive:
|
|
510
|
+
// deleting it mid-resume would let the later terminal upsert resurrect
|
|
511
|
+
// it as an orphan row.
|
|
512
|
+
seedHistoryRow("row-pending-resume", "completed", 1000);
|
|
513
|
+
seedHistoryRow("row-completed", "completed", 2000);
|
|
514
|
+
pendingResumeIds.add("row-pending-resume");
|
|
515
|
+
|
|
516
|
+
const handler = getBulkDeleteHandler();
|
|
517
|
+
const result = (await handler({
|
|
518
|
+
queryParams: { status: "completed" },
|
|
519
|
+
})) as {
|
|
520
|
+
deleted: number;
|
|
521
|
+
};
|
|
522
|
+
expect(result.deleted).toBe(1);
|
|
523
|
+
|
|
524
|
+
const remaining = listRows();
|
|
525
|
+
expect(remaining).toEqual([
|
|
526
|
+
{ id: "row-pending-resume", status: "completed" },
|
|
527
|
+
]);
|
|
528
|
+
});
|
|
529
|
+
|
|
485
530
|
test("returns deleted=0 when no terminal rows are present", async () => {
|
|
486
531
|
seedHistoryRow("row-running", "running", 1000);
|
|
487
532
|
|
|
@@ -559,6 +604,7 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
559
604
|
|
|
560
605
|
beforeEach(() => {
|
|
561
606
|
inMemoryStates.clear();
|
|
607
|
+
pendingResumeIds.clear();
|
|
562
608
|
getDb().delete(acpSessionHistory).run();
|
|
563
609
|
});
|
|
564
610
|
|
|
@@ -610,6 +656,24 @@ describe("DELETE /v1/acp/sessions/:id", () => {
|
|
|
610
656
|
},
|
|
611
657
|
);
|
|
612
658
|
|
|
659
|
+
test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
|
|
660
|
+
pendingResumeIds.add("sess-resuming");
|
|
661
|
+
insertHistoryRow({ id: "sess-resuming", status: "completed" });
|
|
662
|
+
|
|
663
|
+
const handler = getDeleteSessionHandler();
|
|
664
|
+
expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
|
|
665
|
+
"resume in flight",
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
// Row untouched.
|
|
669
|
+
const remaining = getDb()
|
|
670
|
+
.select()
|
|
671
|
+
.from(acpSessionHistory)
|
|
672
|
+
.where(eq(acpSessionHistory.id, "sess-resuming"))
|
|
673
|
+
.all();
|
|
674
|
+
expect(remaining).toHaveLength(1);
|
|
675
|
+
});
|
|
676
|
+
|
|
613
677
|
test("idempotent for unknown id — returns { deleted: false }", async () => {
|
|
614
678
|
const handler = getDeleteSessionHandler();
|
|
615
679
|
const result = (await handler({
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* Exposes spawn, steer, cancel, close, sessions, and permission operations
|
|
5
5
|
* over HTTP and IPC.
|
|
6
6
|
*/
|
|
7
|
-
import { desc, eq, inArray } from "drizzle-orm";
|
|
7
|
+
import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
+
import { resolveAgentWithAutoInstall } from "../../acp/auto-install.js";
|
|
10
11
|
import { getAcpSessionManager } from "../../acp/index.js";
|
|
11
12
|
import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
|
|
12
|
-
import {
|
|
13
|
+
import { formatResolveFailure } from "../../acp/resolve-agent.js";
|
|
14
|
+
import { AcpResumeError } from "../../acp/session-manager.js";
|
|
13
15
|
import type { AcpSessionState } from "../../acp/types.js";
|
|
14
16
|
import { getDb } from "../../memory/db-connection.js";
|
|
15
17
|
import { rawChanges } from "../../memory/raw-query.js";
|
|
@@ -61,26 +63,19 @@ async function spawnSession({ body }: RouteHandlerArgs) {
|
|
|
61
63
|
throw new BadRequestError("agent, task, and conversationId are required");
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
// Resolve the agent, silently auto-installing a missing allowlisted
|
|
67
|
+
// adapter binary (see acp/auto-install.ts). Shared with the skill-tool
|
|
68
|
+
// path in tools/acp/spawn.ts; only the transport mapping differs.
|
|
69
|
+
const { resolved, failureMessage } = await resolveAgentWithAutoInstall(agent);
|
|
70
|
+
if (failureMessage) {
|
|
71
|
+
throw new FailedDependencyError(failureMessage);
|
|
72
|
+
}
|
|
65
73
|
if (!resolved.ok) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
case "unknown_agent":
|
|
70
|
-
throw new BadRequestError(
|
|
71
|
-
`Unknown agent "${agent}". Available: ${resolved.available.join(", ")}.`,
|
|
72
|
-
);
|
|
73
|
-
case "binary_not_found":
|
|
74
|
-
throw new FailedDependencyError(
|
|
75
|
-
`${resolved.command} is not on PATH. ${resolved.hint}`,
|
|
76
|
-
);
|
|
77
|
-
default: {
|
|
78
|
-
const _exhaustive: never = resolved;
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Unexpected acp resolver reason: ${(_exhaustive as { reason: string }).reason}`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
74
|
+
const message = formatResolveFailure(agent, resolved);
|
|
75
|
+
if (resolved.reason === "binary_not_found") {
|
|
76
|
+
throw new FailedDependencyError(message);
|
|
83
77
|
}
|
|
78
|
+
throw new BadRequestError(message);
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
// Inject required env vars and preflight via the shared helper. See
|
|
@@ -116,13 +111,31 @@ async function steerSession({ pathParams, body }: RouteHandlerArgs) {
|
|
|
116
111
|
throw new BadRequestError("instruction is required");
|
|
117
112
|
}
|
|
118
113
|
|
|
114
|
+
// Sessions no longer in memory (completed, or lost to a daemon restart)
|
|
115
|
+
// are transparently resumed from persisted history with the instruction
|
|
116
|
+
// fired in the same call, mirroring the acp_steer skill tool.
|
|
117
|
+
// broadcastMessage plays the sender role spawnSession gives it, so
|
|
118
|
+
// connected clients render the session.
|
|
119
119
|
const manager = getAcpSessionManager();
|
|
120
120
|
try {
|
|
121
|
-
await manager.
|
|
122
|
-
|
|
121
|
+
const { resumed } = await manager.steerOrResume(
|
|
122
|
+
id,
|
|
123
|
+
instruction,
|
|
124
|
+
broadcastMessage,
|
|
125
|
+
);
|
|
126
|
+
return resumed
|
|
127
|
+
? { acpSessionId: id, steered: true, resumed: true }
|
|
128
|
+
: { acpSessionId: id, steered: true };
|
|
129
|
+
} catch (err) {
|
|
130
|
+
// Resume errors carry the actionable hint (legacy row without cwd,
|
|
131
|
+
// agent capability missing, resolver failures, ...).
|
|
132
|
+
if (err instanceof AcpResumeError) {
|
|
133
|
+
throw new FailedDependencyError(err.message);
|
|
134
|
+
}
|
|
135
|
+
// Unknown ids (no in-memory session, no history row) and plain steer
|
|
136
|
+
// failures both map to 404, as before.
|
|
123
137
|
throw new NotFoundError("ACP session not found");
|
|
124
138
|
}
|
|
125
|
-
return { acpSessionId: id, steered: true };
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
async function cancelSession({ pathParams }: RouteHandlerArgs) {
|
|
@@ -161,9 +174,25 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
|
|
|
161
174
|
"status query param is required and must be 'completed'",
|
|
162
175
|
);
|
|
163
176
|
}
|
|
177
|
+
// Exclude sessions currently active in memory AND ids with a resume in
|
|
178
|
+
// flight (reserved but not yet registered): a resumed session reuses its
|
|
179
|
+
// original id, and its history row keeps the old terminal status until
|
|
180
|
+
// the next terminal upsert - a status-only delete would remove the row
|
|
181
|
+
// out from under the live (or resuming) session, and the later terminal
|
|
182
|
+
// upsert would resurrect it. Mirrors the 409 guard on the single-id
|
|
183
|
+
// delete route.
|
|
184
|
+
const activeIds = getAcpSessionManager().getActiveAndPendingIds();
|
|
185
|
+
const terminalFilter = inArray(
|
|
186
|
+
acpSessionHistory.status,
|
|
187
|
+
TERMINAL_SESSION_STATUSES,
|
|
188
|
+
);
|
|
164
189
|
getDb()
|
|
165
190
|
.delete(acpSessionHistory)
|
|
166
|
-
.where(
|
|
191
|
+
.where(
|
|
192
|
+
activeIds.length > 0
|
|
193
|
+
? and(terminalFilter, notInArray(acpSessionHistory.id, activeIds))
|
|
194
|
+
: terminalFilter,
|
|
195
|
+
)
|
|
167
196
|
.run();
|
|
168
197
|
const deleted = rawChanges();
|
|
169
198
|
log.info({ deleted }, "Bulk-cleared terminal ACP session history");
|
|
@@ -172,9 +201,10 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
|
|
|
172
201
|
|
|
173
202
|
function deleteSession({ pathParams }: RouteHandlerArgs) {
|
|
174
203
|
const id = pathParams?.id as string;
|
|
204
|
+
const manager = getAcpSessionManager();
|
|
175
205
|
|
|
176
206
|
try {
|
|
177
|
-
const state =
|
|
207
|
+
const state = manager.getStatus(id);
|
|
178
208
|
if (
|
|
179
209
|
!Array.isArray(state) &&
|
|
180
210
|
(state.status === "running" || state.status === "initializing")
|
|
@@ -185,7 +215,16 @@ function deleteSession({ pathParams }: RouteHandlerArgs) {
|
|
|
185
215
|
}
|
|
186
216
|
} catch (err) {
|
|
187
217
|
if (err instanceof ConflictError) throw err;
|
|
188
|
-
// Not in memory
|
|
218
|
+
// Not registered in memory, but a resume may be in flight (id reserved
|
|
219
|
+
// while awaiting env preparation). Its history row must survive until
|
|
220
|
+
// the resume lands - the later terminal upsert would resurrect a
|
|
221
|
+
// deleted row.
|
|
222
|
+
if (manager.getActiveAndPendingIds().includes(id)) {
|
|
223
|
+
throw new ConflictError(
|
|
224
|
+
`ACP session "${id}" has a resume in flight. Wait for it to finish before deleting.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
// Otherwise fall through to the (idempotent) DB delete.
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
getDb().delete(acpSessionHistory).where(eq(acpSessionHistory.id, id)).run();
|
|
@@ -233,7 +272,11 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
233
272
|
},
|
|
234
273
|
handler: steerSession,
|
|
235
274
|
summary: "Steer ACP session",
|
|
236
|
-
description:
|
|
275
|
+
description:
|
|
276
|
+
"Send a steering instruction to an ACP session. Sessions no longer " +
|
|
277
|
+
"in memory (completed, or lost to a daemon restart) are " +
|
|
278
|
+
"transparently resumed from persisted history first, when the agent " +
|
|
279
|
+
"supports ACP session loading.",
|
|
237
280
|
tags: ["acp"],
|
|
238
281
|
requestBody: z.object({
|
|
239
282
|
instruction: z.string(),
|
|
@@ -241,6 +284,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
241
284
|
responseBody: z.object({
|
|
242
285
|
acpSessionId: z.string(),
|
|
243
286
|
steered: z.boolean(),
|
|
287
|
+
resumed: z
|
|
288
|
+
.boolean()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe(
|
|
291
|
+
"True when the session was resumed from persisted history before steering.",
|
|
292
|
+
),
|
|
244
293
|
}),
|
|
245
294
|
},
|
|
246
295
|
{
|
|
@@ -324,7 +373,9 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
324
373
|
summary: "Bulk-clear terminal ACP sessions",
|
|
325
374
|
description:
|
|
326
375
|
"Remove every terminal-state row (completed/failed/cancelled) from " +
|
|
327
|
-
"the persisted acp_session_history table."
|
|
376
|
+
"the persisted acp_session_history table. Rows whose session is " +
|
|
377
|
+
"currently active in memory (e.g. resumed) or has a resume in " +
|
|
378
|
+
"flight are excluded.",
|
|
328
379
|
tags: ["acp"],
|
|
329
380
|
queryParams: [
|
|
330
381
|
{
|
|
@@ -350,7 +401,8 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
350
401
|
summary: "Delete ACP session from history",
|
|
351
402
|
description:
|
|
352
403
|
"Remove a persisted ACP session row. Rejects with 409 when the " +
|
|
353
|
-
"session is still active in memory
|
|
404
|
+
"session is still active in memory or has a resume in flight; " +
|
|
405
|
+
"idempotent for unknown ids.",
|
|
354
406
|
tags: ["acp"],
|
|
355
407
|
responseBody: z.object({
|
|
356
408
|
deleted: z.boolean(),
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
import { findConversation } from "../../daemon/conversation-
|
|
12
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
13
13
|
import { getConversationByKey } from "../../memory/conversation-key-store.js";
|
|
14
14
|
import type { UserDecision } from "../../permissions/types.js";
|
|
15
15
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
BROWSER_OPERATIONS,
|
|
17
17
|
type BrowserOperation,
|
|
18
18
|
} from "../../browser/types.js";
|
|
19
|
-
import { findConversation } from "../../daemon/conversation-
|
|
19
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
20
20
|
import type { ContentBlock } from "../../providers/types.js";
|
|
21
21
|
import { LOCAL_PRINCIPALS } from "../auth/route-policy.js";
|
|
22
22
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
|
-
import { findConversation } from "../../daemon/conversation-
|
|
10
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
11
11
|
import { getCdpClient } from "../../tools/browser/cdp-client/factory.js";
|
|
12
12
|
import {
|
|
13
13
|
clearPinnedTab,
|
|
@@ -73,11 +73,7 @@ async function handleBrowserTabs({ body = {} }: RouteHandlerArgs) {
|
|
|
73
73
|
? result.clientId
|
|
74
74
|
: undefined;
|
|
75
75
|
if (result?.tabId !== undefined) {
|
|
76
|
-
setPinnedTab(
|
|
77
|
-
resolvedConversationId,
|
|
78
|
-
String(result.tabId),
|
|
79
|
-
clientId,
|
|
80
|
-
);
|
|
76
|
+
setPinnedTab(resolvedConversationId, String(result.tabId), clientId);
|
|
81
77
|
}
|
|
82
78
|
return { ok: true, tab: result };
|
|
83
79
|
} finally {
|
|
@@ -88,10 +84,10 @@ async function handleBrowserTabs({ body = {} }: RouteHandlerArgs) {
|
|
|
88
84
|
if (command === "new") {
|
|
89
85
|
const cdp = getCdpClient(context, cdpOptions);
|
|
90
86
|
try {
|
|
91
|
-
const result = await cdp.send<{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
87
|
+
const result = await cdp.send<{
|
|
88
|
+
tabId?: number | string;
|
|
89
|
+
clientId?: string;
|
|
90
|
+
}>("Vellum.createTab", {});
|
|
95
91
|
// Normalise to string for internal use (setCdpSessionId / setPinnedTab)
|
|
96
92
|
// and keep the numeric form for the API response.
|
|
97
93
|
const newTabIdStr: string | undefined =
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
-
import { findConversation } from "../../daemon/conversation-
|
|
12
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
13
13
|
import { clearAllConversations as clearAllActive } from "../../daemon/handlers/conversations.js";
|
|
14
14
|
import { formatJson, formatMarkdown } from "../../export/formatter.js";
|
|
15
15
|
import { ipcCall as ipcCallGateway } from "../../ipc/gateway-client.js";
|
|
@@ -72,7 +72,7 @@ import { type LogRow } from "../../memory/llm-request-log-store.js";
|
|
|
72
72
|
import { getMemoryRecallLogByMessageIds } from "../../memory/memory-recall-log-store.js";
|
|
73
73
|
import { getMemoryV2ActivationLogByMessageIds } from "../../memory/memory-v2-activation-log-store.js";
|
|
74
74
|
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
|
|
75
|
-
import { getMemoryV3SelectionForInspector } from "../../memory
|
|
75
|
+
import { getMemoryV3SelectionForInspector } from "../../plugins/defaults/memory-v3-shadow/selection-log-store.js";
|
|
76
76
|
import {
|
|
77
77
|
createConnection,
|
|
78
78
|
listConnections,
|
|
@@ -680,7 +680,14 @@ export function handleListMessages({
|
|
|
680
680
|
// assistant message has tool_use blocks but its matching user tool_result
|
|
681
681
|
// is left visible, the result will render as a standalone orphan because
|
|
682
682
|
// `mergeToolResultsIntoAssistantMessages` has nothing to merge it into.
|
|
683
|
-
|
|
683
|
+
//
|
|
684
|
+
// Only renderable roles reach this UI-facing transcript. `system` rows (a
|
|
685
|
+
// permitted `MessageRole`, e.g. skill-authored context) are agent-context
|
|
686
|
+
// scaffolding, never a displayed turn, so they are dropped here at the
|
|
687
|
+
// source rather than narrowed away per-client.
|
|
688
|
+
const visibleFilter = (m: MessageRow) =>
|
|
689
|
+
!isHiddenMessage(m.metadata) &&
|
|
690
|
+
(m.role === "user" || m.role === "assistant");
|
|
684
691
|
|
|
685
692
|
if (isPaginated) {
|
|
686
693
|
const result = getMessagesPaginated(
|
|
@@ -772,14 +779,21 @@ export function handleListMessages({
|
|
|
772
779
|
},
|
|
773
780
|
);
|
|
774
781
|
|
|
782
|
+
// `visibleFilter` has already dropped every non-renderable role, so the
|
|
783
|
+
// only values reaching here are `user` and `assistant`; narrow the raw DB
|
|
784
|
+
// `string` to the wire union.
|
|
785
|
+
const role: "user" | "assistant" =
|
|
786
|
+
msg.role === "assistant" ? "assistant" : "user";
|
|
787
|
+
|
|
775
788
|
return {
|
|
776
789
|
id: msg.id,
|
|
777
|
-
role
|
|
790
|
+
role,
|
|
778
791
|
content,
|
|
779
792
|
createdAt: msg.createdAt,
|
|
780
793
|
sentAt,
|
|
781
794
|
subagentNotification,
|
|
782
795
|
slackMessage,
|
|
796
|
+
clientMessageId: msg.clientMessageId ?? undefined,
|
|
783
797
|
};
|
|
784
798
|
});
|
|
785
799
|
|
|
@@ -916,6 +930,7 @@ export function handleListMessages({
|
|
|
916
930
|
return {
|
|
917
931
|
id: m.id ?? "",
|
|
918
932
|
...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
|
|
933
|
+
...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
|
|
919
934
|
role: m.role,
|
|
920
935
|
// Flat plain-text body the legacy Swift client reads directly; see the
|
|
921
936
|
// `content` field on ConversationMessageSchema for why this must stay.
|
|
@@ -1354,6 +1369,11 @@ export async function handleSendMessage(
|
|
|
1354
1369
|
mapping.conversationId,
|
|
1355
1370
|
requestedInferenceProfile,
|
|
1356
1371
|
);
|
|
1372
|
+
conversation.applyInferenceProfileState({
|
|
1373
|
+
profile: requestedInferenceProfile,
|
|
1374
|
+
sessionId: null,
|
|
1375
|
+
expiresAt: null,
|
|
1376
|
+
});
|
|
1357
1377
|
}
|
|
1358
1378
|
|
|
1359
1379
|
// Store pre-chat onboarding context on the conversation when this is the
|
|
@@ -2657,6 +2677,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2657
2677
|
conversationType: z.string().optional(),
|
|
2658
2678
|
slashCommand: z.string().optional(),
|
|
2659
2679
|
clientTimezone: z.string().optional(),
|
|
2680
|
+
clientMessageId: z
|
|
2681
|
+
.string()
|
|
2682
|
+
.describe(
|
|
2683
|
+
"Client-generated idempotency nonce. Persisted on the row and echoed back on the message_echo event and the messages snapshot so the client can correlate its optimistic row by identity. Duplicate sends for the same (conversation, clientMessageId) are deduplicated server-side.",
|
|
2684
|
+
)
|
|
2685
|
+
.optional(),
|
|
2660
2686
|
inferenceProfile: z.string().nullable().optional(),
|
|
2661
2687
|
riskThreshold: z.enum(VALID_RISK_THRESHOLDS).optional(),
|
|
2662
2688
|
onboarding: z
|
|
@@ -11,6 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
import {
|
|
12
12
|
checkpointKey,
|
|
13
13
|
CK_ITEM_COUNT,
|
|
14
|
+
CK_LAST_ATTEMPT_AT,
|
|
14
15
|
CK_LAST_GEN_AT,
|
|
15
16
|
countActiveMemoryNodes,
|
|
16
17
|
getCheckpointValue,
|
|
@@ -42,10 +43,11 @@ const starterItemSchema = z.object({
|
|
|
42
43
|
|
|
43
44
|
type StarterItem = z.infer<typeof starterItemSchema>;
|
|
44
45
|
|
|
45
|
-
export const CONVERSATION_STARTERS_STALE_TTL_MS =
|
|
46
|
+
export const CONVERSATION_STARTERS_STALE_TTL_MS = 4 * 60 * 60 * 1000;
|
|
46
47
|
|
|
47
|
-
/** Minimum interval between re-enqueue attempts
|
|
48
|
-
|
|
48
|
+
/** Minimum interval between re-enqueue attempts (prevents tight retry loops
|
|
49
|
+
* when generation repeatedly fails or produces 0 valid starters). */
|
|
50
|
+
const REFRESH_COOLDOWN_MS = 5 * 60 * 1000;
|
|
49
51
|
|
|
50
52
|
function hasActiveConversationStarterJob(
|
|
51
53
|
db: ReturnType<typeof getDb>,
|
|
@@ -199,12 +201,16 @@ function handleListConversationStarters({
|
|
|
199
201
|
Date.now() - lastGenAt >= CONVERSATION_STARTERS_STALE_TTL_MS;
|
|
200
202
|
const checkpointAhead = lastCount != null && totalActive < lastCount;
|
|
201
203
|
let hasActiveJob = hasActiveConversationStarterJob(db, scopeId);
|
|
204
|
+
const lastAttemptAt = parseCheckpointInt(
|
|
205
|
+
getCheckpointValue(checkpointKey(CK_LAST_ATTEMPT_AT, scopeId)),
|
|
206
|
+
);
|
|
202
207
|
const withinCooldown =
|
|
203
|
-
|
|
208
|
+
lastAttemptAt != null && Date.now() - lastAttemptAt < REFRESH_COOLDOWN_MS;
|
|
204
209
|
const shouldRefresh =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
!withinCooldown &&
|
|
211
|
+
(staleByAge ||
|
|
212
|
+
checkpointAhead ||
|
|
213
|
+
(invalidItemCount > 0 && totalActive > 0));
|
|
208
214
|
|
|
209
215
|
if (shouldRefresh && !hasActiveJob && isMemoryEnabled()) {
|
|
210
216
|
enqueueMemoryJob("generate_conversation_starters", { scopeId });
|
|
@@ -46,7 +46,10 @@ interface ImportPayload {
|
|
|
46
46
|
|
|
47
47
|
// -- Helpers (ported from CLI) --
|
|
48
48
|
|
|
49
|
-
function resolveTimestamps(
|
|
49
|
+
function resolveTimestamps(
|
|
50
|
+
conv: ImportConversation,
|
|
51
|
+
messages: ImportMessage[],
|
|
52
|
+
): {
|
|
50
53
|
convCreatedAt: number;
|
|
51
54
|
convUpdatedAt: number;
|
|
52
55
|
messageTimestamps: number[];
|
|
@@ -54,13 +57,23 @@ function resolveTimestamps(conv: ImportConversation): {
|
|
|
54
57
|
const now = Date.now();
|
|
55
58
|
const convCreatedAt = conv.createdAt ?? now;
|
|
56
59
|
const convUpdatedAt = conv.updatedAt ?? conv.createdAt ?? now;
|
|
57
|
-
const messageTimestamps =
|
|
60
|
+
const messageTimestamps = messages.map((msg, i) => {
|
|
58
61
|
if (msg.createdAt != null) return msg.createdAt;
|
|
59
62
|
return convCreatedAt + i;
|
|
60
63
|
});
|
|
61
64
|
return { convCreatedAt, convUpdatedAt, messageTimestamps };
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The messages store is UI-facing (`ConversationMessage`), so only renderable
|
|
69
|
+
* turns are persisted. Non-renderable roles (e.g. agent-context `system`
|
|
70
|
+
* rows an export might carry) are dropped here rather than imported — the
|
|
71
|
+
* import still succeeds with the displayable turns.
|
|
72
|
+
*/
|
|
73
|
+
function isRenderableRole(role: string): role is "user" | "assistant" {
|
|
74
|
+
return role === "user" || role === "assistant";
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
// -- Handler --
|
|
65
78
|
|
|
66
79
|
async function handleConversationsImport({ body }: RouteHandlerArgs) {
|
|
@@ -99,12 +112,16 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
|
|
|
99
112
|
}
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
const messages = conv.messages.filter((msg) =>
|
|
116
|
+
isRenderableRole(msg.role),
|
|
117
|
+
);
|
|
118
|
+
|
|
102
119
|
const { convCreatedAt, convUpdatedAt, messageTimestamps } =
|
|
103
|
-
resolveTimestamps(conv);
|
|
120
|
+
resolveTimestamps(conv, messages);
|
|
104
121
|
|
|
105
122
|
const conversation = createConversation(conv.title);
|
|
106
123
|
|
|
107
|
-
for (const msg of
|
|
124
|
+
for (const msg of messages) {
|
|
108
125
|
const contentStr =
|
|
109
126
|
typeof msg.content === "string"
|
|
110
127
|
? msg.content
|
|
@@ -144,8 +161,8 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
|
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
// Index messages
|
|
147
|
-
for (let i = 0; i < dbMessages.length && i <
|
|
148
|
-
const msg =
|
|
164
|
+
for (let i = 0; i < dbMessages.length && i < messages.length; i++) {
|
|
165
|
+
const msg = messages[i];
|
|
149
166
|
const contentStr =
|
|
150
167
|
typeof msg.content === "string"
|
|
151
168
|
? msg.content
|
|
@@ -176,7 +193,7 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
|
|
|
176
193
|
}
|
|
177
194
|
|
|
178
195
|
imported++;
|
|
179
|
-
totalMessages +=
|
|
196
|
+
totalMessages += messages.length;
|
|
180
197
|
} catch (err) {
|
|
181
198
|
errors.push({
|
|
182
199
|
index: idx,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
|
-
import { findConversation } from "../../daemon/conversation-
|
|
17
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
18
18
|
import type {
|
|
19
19
|
HostAppControlResultPayload,
|
|
20
20
|
HostAppControlState,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
-
import { findConversation } from "../../daemon/conversation-
|
|
9
|
+
import { findConversation } from "../../daemon/conversation-registry.js";
|
|
10
10
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
11
11
|
import {
|
|
12
12
|
enforceSameActorOrThrow,
|