@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea
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/Dockerfile +20 -4
- package/bun.lock +2 -2
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/examples/plugins/echo/README.md +61 -66
- 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/server-message.ts +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +3378 -335
- package/package.json +2 -2
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.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__/clawhub-files.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.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
- package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
- package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
- package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- 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__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +3 -9
- package/src/__tests__/injector-chain.test.ts +139 -275
- package/src/__tests__/injector-disk-pressure.test.ts +75 -41
- package/src/__tests__/injector-document-comments.test.ts +3 -3
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-api-shim.test.ts +3 -6
- package/src/__tests__/plugin-bootstrap.test.ts +14 -40
- package/src/__tests__/plugin-registry.test.ts +3 -76
- package/src/__tests__/plugin-types.test.ts +0 -193
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- 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__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -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 +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +362 -485
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +374 -0
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- 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/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/acp/SKILL.md +83 -31
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
- package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
- 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/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -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-skills/messaging/SKILL.md +0 -7
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +48 -7
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +128 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +5 -7
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
- package/src/daemon/conversation-agent-loop.ts +301 -997
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +218 -398
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +32 -20
- package/src/daemon/external-plugins-bootstrap.ts +17 -18
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +42 -10
- package/src/daemon/lifecycle.ts +25 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/overflow-reduction-loop.ts +230 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/server.ts +2 -0
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- 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/home/home-greeting-cache.ts +24 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.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/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +32 -5
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
- package/src/memory/v2/consolidation-job.ts +14 -5
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +7 -5
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/package.json +1 -1
- package/src/plugins/defaults/compaction/register.ts +8 -19
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
- 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__/health.test.ts +16 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
- 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 +48 -12
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
- 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 +104 -32
- 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 +96 -30
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
- package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
- package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
- package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
- 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 +51 -45
- 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 +61 -46
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/external-api.ts +2 -2
- package/src/plugins/pipeline.ts +6 -305
- package/src/plugins/registry.ts +10 -55
- package/src/plugins/types.ts +62 -797
- package/src/plugins/user-loader.ts +30 -127
- package/src/proactive-artifact/aux-message-injector.ts +4 -4
- package/src/proactive-artifact/job.test.ts +8 -13
- package/src/prompts/__tests__/system-prompt.test.ts +42 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +10 -6
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +2 -5
- package/src/runtime/assistant-event-hub.ts +37 -7
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- 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/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- 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 +3 -1
- package/src/runtime/routes/conversation-routes.ts +372 -185
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- 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-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +224 -18
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +66 -34
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/signals/cancel.ts +2 -4
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/subagent/manager.ts +17 -5
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +7 -1
- package/src/tools/acp/spawn.test.ts +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/executor.ts +1 -53
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +34 -5
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/docs/plugins.md +0 -836
- package/examples/plugins/echo/register.ts +0 -184
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- 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__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/pipeline-runner.test.ts +0 -564
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/memory/v3/provider-blocks.ts +0 -16
- 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/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- 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/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
- /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__/needle.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__/types.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}/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
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
|
|
8
|
-
import { inArray } from "drizzle-orm";
|
|
8
|
+
import { eq, inArray } from "drizzle-orm";
|
|
9
9
|
|
|
10
|
-
import { findConversation } from "../daemon/conversation-
|
|
10
|
+
import { findConversation } from "../daemon/conversation-registry.js";
|
|
11
11
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
12
12
|
import type { AcpSessionUpdate } from "../daemon/message-types/acp.js";
|
|
13
13
|
import { getDb } from "../memory/db-connection.js";
|
|
@@ -16,10 +16,43 @@ import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
|
16
16
|
import { getLogger } from "../util/logger.js";
|
|
17
17
|
import { AcpAgentProcess } from "./agent-process.js";
|
|
18
18
|
import { VellumAcpClientHandler } from "./client-handler.js";
|
|
19
|
+
import { prepareAgentEnv } from "./prepare-agent-env.js";
|
|
20
|
+
import {
|
|
21
|
+
adapterCommandOf,
|
|
22
|
+
formatResolveFailure,
|
|
23
|
+
resolveAcpAgent,
|
|
24
|
+
} from "./resolve-agent.js";
|
|
25
|
+
import { claudeResumeHint } from "./resume-hint.js";
|
|
19
26
|
import type { AcpAgentConfig, AcpSessionState } from "./types.js";
|
|
20
27
|
|
|
21
28
|
const log = getLogger("acp:session-manager");
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The manager's "unknown session id" error. Thrown whenever an operation
|
|
32
|
+
* references an acpSessionId with no in-memory entry (and, for resume, no
|
|
33
|
+
* persisted history row). Callers (acp_steer tool, /v1/acp/:id/steer route)
|
|
34
|
+
* use `instanceof` checks to map this to their transport's not-found shape.
|
|
35
|
+
*/
|
|
36
|
+
export class AcpSessionNotFoundError extends Error {
|
|
37
|
+
constructor(public readonly acpSessionId: string) {
|
|
38
|
+
super(`ACP session "${acpSessionId}" not found`);
|
|
39
|
+
this.name = "AcpSessionNotFoundError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wraps failures from the resume-then-steer phase of `steerOrResume` so
|
|
45
|
+
* transport callers can distinguish them (HTTP 424 with the actionable
|
|
46
|
+
* resume hint) from plain steer failures (404). The message mirrors the
|
|
47
|
+
* underlying error's message; the original error rides on `cause`.
|
|
48
|
+
*/
|
|
49
|
+
export class AcpResumeError extends Error {
|
|
50
|
+
constructor(cause: unknown) {
|
|
51
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
52
|
+
this.name = "AcpResumeError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
23
56
|
/** Maximum number of update events kept in a session's ring buffer. */
|
|
24
57
|
const MAX_BUFFER_EVENTS = 200;
|
|
25
58
|
/** Maximum aggregate JSON size of a session's ring buffer, in bytes. */
|
|
@@ -41,11 +74,22 @@ interface SessionEntry {
|
|
|
41
74
|
currentPrompt: Promise<unknown> | null;
|
|
42
75
|
parentConversationId: string;
|
|
43
76
|
cwd: string;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
77
|
+
/** Canonical adapter command for the spawned config (e.g.
|
|
78
|
+
* "claude-agent-acp" even when the adapter runs via `bun x`). Used to
|
|
79
|
+
* gate resume hints to the only adapter (claude-agent-acp) whose CLI
|
|
80
|
+
* accepts `--resume`. */
|
|
46
81
|
command: string;
|
|
47
82
|
}
|
|
48
83
|
|
|
84
|
+
/**
|
|
85
|
+
* An `acp_session_history` row that passed resumeFromHistory's validation
|
|
86
|
+
* guards: `cwd` (nullable in the schema for pre-resume-support rows) is
|
|
87
|
+
* guaranteed present.
|
|
88
|
+
*/
|
|
89
|
+
type ResumableHistoryRow = typeof acpSessionHistory.$inferSelect & {
|
|
90
|
+
cwd: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
49
93
|
export class AcpSessionManager {
|
|
50
94
|
private sessions = new Map<string, SessionEntry>();
|
|
51
95
|
/**
|
|
@@ -55,6 +99,25 @@ export class AcpSessionManager {
|
|
|
55
99
|
* `acp_session_history` on terminal transition, then cleared.
|
|
56
100
|
*/
|
|
57
101
|
private eventBuffers = new Map<string, BufferedAcpUpdate[]>();
|
|
102
|
+
/**
|
|
103
|
+
* In-flight resumes by session id, keyed to the promise of the resume's
|
|
104
|
+
* async body. Reserved SYNCHRONOUSLY before the first await so concurrent
|
|
105
|
+
* resumes of the same id cannot both pass the guards (the loser would
|
|
106
|
+
* overwrite the winner's map entry and leak its child process), and so N
|
|
107
|
+
* concurrent resumes of distinct ids cannot exceed maxConcurrent. The
|
|
108
|
+
* entry lives until the resume settles so `steerOrResume` can await a
|
|
109
|
+
* concurrent caller's resume instead of failing the already-active guard.
|
|
110
|
+
* The spawn path needs no such reservation: its check-then-register is
|
|
111
|
+
* synchronous.
|
|
112
|
+
*/
|
|
113
|
+
private pendingResumes = new Map<string, Promise<void>>();
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set by dispose() (the daemon-shutdown path). Resumes that are mid-await
|
|
117
|
+
* when the manager is disposed re-check this flag before spawning a child
|
|
118
|
+
* process nothing would ever kill.
|
|
119
|
+
*/
|
|
120
|
+
private disposed = false;
|
|
58
121
|
|
|
59
122
|
constructor(private readonly maxConcurrent: number) {
|
|
60
123
|
this.cleanupStaleRunningRows();
|
|
@@ -91,6 +154,33 @@ export class AcpSessionManager {
|
|
|
91
154
|
}
|
|
92
155
|
}
|
|
93
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Ids that must be treated as live: registered sessions plus in-flight
|
|
159
|
+
* resume reservations (deduped, since a resuming session appears in both maps
|
|
160
|
+
* between registration and settle). Delete guards use this so a history
|
|
161
|
+
* row cannot be removed out from under a resume that is still awaiting
|
|
162
|
+
* env preparation; the later terminal upsert would resurrect it.
|
|
163
|
+
*/
|
|
164
|
+
getActiveAndPendingIds(): string[] {
|
|
165
|
+
return [
|
|
166
|
+
...new Set([...this.sessions.keys(), ...this.pendingResumes.keys()]),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Concurrency guard shared by spawn() and resumeFromHistory(). Counts
|
|
172
|
+
* both registered sessions and in-flight resume reservations so the cap
|
|
173
|
+
* holds even while a resume is still awaiting prepareAgentEnv.
|
|
174
|
+
*/
|
|
175
|
+
private assertCapacity(): void {
|
|
176
|
+
if (this.getActiveAndPendingIds().length >= this.maxConcurrent) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`ACP concurrency limit reached (max ${this.maxConcurrent}). ` +
|
|
179
|
+
`Close an existing session before spawning a new one.`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
94
184
|
/**
|
|
95
185
|
* Spawns a new ACP agent session. Returns the generated acpSessionId.
|
|
96
186
|
*
|
|
@@ -105,12 +195,7 @@ export class AcpSessionManager {
|
|
|
105
195
|
parentConversationId: string,
|
|
106
196
|
sendToVellum: (msg: ServerMessage) => void,
|
|
107
197
|
): Promise<{ acpSessionId: string; protocolSessionId: string }> {
|
|
108
|
-
|
|
109
|
-
throw new Error(
|
|
110
|
-
`ACP concurrency limit reached (max ${this.maxConcurrent}). ` +
|
|
111
|
-
`Close an existing session before spawning a new one.`,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
198
|
+
this.assertCapacity();
|
|
114
199
|
|
|
115
200
|
const acpSessionId = randomUUID();
|
|
116
201
|
log.info(
|
|
@@ -124,40 +209,103 @@ export class AcpSessionManager {
|
|
|
124
209
|
"ACP spawn requested",
|
|
125
210
|
);
|
|
126
211
|
|
|
212
|
+
const entry = this.registerSession({
|
|
213
|
+
acpSessionId,
|
|
214
|
+
agentId,
|
|
215
|
+
agentConfig,
|
|
216
|
+
parentConversationId,
|
|
217
|
+
cwd,
|
|
218
|
+
startedAt: Date.now(),
|
|
219
|
+
sendToVellum,
|
|
220
|
+
});
|
|
221
|
+
const { process: agentProcess, state } = entry;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
log.info({ acpSessionId, agentId }, "ACP spawning child process");
|
|
225
|
+
agentProcess.spawn(cwd);
|
|
226
|
+
log.info(
|
|
227
|
+
{ acpSessionId, agentId },
|
|
228
|
+
"ACP initializing protocol connection",
|
|
229
|
+
);
|
|
230
|
+
await agentProcess.initialize();
|
|
231
|
+
log.info({ acpSessionId, agentId }, "ACP creating session");
|
|
232
|
+
const acpProtocolSessionId = await agentProcess.createSession(cwd);
|
|
233
|
+
state.acpSessionId = acpProtocolSessionId;
|
|
234
|
+
state.status = "running";
|
|
235
|
+
log.info(
|
|
236
|
+
{ acpSessionId, agentId, acpProtocolSessionId },
|
|
237
|
+
"ACP session running",
|
|
238
|
+
);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
log.error({ acpSessionId, agentId, err }, "ACP spawn failed");
|
|
241
|
+
// No prompt has fired yet, so no permissions can be pending.
|
|
242
|
+
this.teardownSession(acpSessionId, entry);
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.sendSpawnedEvent(acpSessionId, entry);
|
|
247
|
+
|
|
248
|
+
// Fire prompt in the background — don't await
|
|
249
|
+
entry.currentPrompt = this.firePromptInBackground(
|
|
250
|
+
acpSessionId,
|
|
251
|
+
entry,
|
|
252
|
+
state.acpSessionId,
|
|
253
|
+
task,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return { acpSessionId, protocolSessionId: state.acpSessionId };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Wires up the in-memory plumbing shared by spawn() and
|
|
261
|
+
* resumeFromHistory(): the per-session ring buffer, the buffer-mirroring
|
|
262
|
+
* sender, the client handler, the agent process, and the SessionEntry.
|
|
263
|
+
* Registers the entry in the session map (reserving a concurrency slot
|
|
264
|
+
* before any async work) and returns it. Does NOT start the process.
|
|
265
|
+
*/
|
|
266
|
+
private registerSession(opts: {
|
|
267
|
+
acpSessionId: string;
|
|
268
|
+
agentId: string;
|
|
269
|
+
agentConfig: AcpAgentConfig;
|
|
270
|
+
parentConversationId: string;
|
|
271
|
+
cwd: string;
|
|
272
|
+
startedAt: number;
|
|
273
|
+
sendToVellum: (msg: ServerMessage) => void;
|
|
274
|
+
}): SessionEntry {
|
|
275
|
+
const { acpSessionId } = opts;
|
|
276
|
+
|
|
127
277
|
// Initialize the per-session ring buffer before any update can fire.
|
|
128
278
|
this.eventBuffers.set(acpSessionId, []);
|
|
129
279
|
|
|
130
280
|
// Wrap the sender so every emitted message is mirrored into the buffer
|
|
131
281
|
// when it's an `acp_session_update`. The wrapper preserves the original
|
|
132
|
-
// call semantics
|
|
282
|
+
// call semantics: it forwards every message unchanged.
|
|
133
283
|
const wrappedSend = (msg: ServerMessage) => {
|
|
134
284
|
if (msg.type === "acp_session_update") {
|
|
135
285
|
this.appendToBuffer(acpSessionId, msg);
|
|
136
286
|
}
|
|
137
|
-
sendToVellum(msg);
|
|
287
|
+
opts.sendToVellum(msg);
|
|
138
288
|
};
|
|
139
289
|
|
|
140
290
|
const clientHandler = new VellumAcpClientHandler(
|
|
141
291
|
acpSessionId,
|
|
142
292
|
wrappedSend,
|
|
143
|
-
parentConversationId,
|
|
293
|
+
opts.parentConversationId,
|
|
144
294
|
);
|
|
145
295
|
|
|
146
296
|
const agentProcess = new AcpAgentProcess(
|
|
147
|
-
agentId,
|
|
148
|
-
agentConfig,
|
|
297
|
+
opts.agentId,
|
|
298
|
+
opts.agentConfig,
|
|
149
299
|
(_agent) => clientHandler,
|
|
150
300
|
);
|
|
151
301
|
|
|
152
|
-
// Reserve a slot in the map before any async work to enforce the
|
|
153
|
-
// concurrency limit even when multiple spawn() calls race.
|
|
154
302
|
const state: AcpSessionState = {
|
|
155
303
|
id: acpSessionId,
|
|
156
|
-
agentId,
|
|
157
|
-
acpSessionId: "", // placeholder until createSession resolves
|
|
158
|
-
parentConversationId,
|
|
304
|
+
agentId: opts.agentId,
|
|
305
|
+
acpSessionId: "", // placeholder until createSession/resume resolves
|
|
306
|
+
parentConversationId: opts.parentConversationId,
|
|
159
307
|
status: "initializing",
|
|
160
|
-
startedAt:
|
|
308
|
+
startedAt: opts.startedAt,
|
|
161
309
|
};
|
|
162
310
|
|
|
163
311
|
const entry: SessionEntry = {
|
|
@@ -166,54 +314,208 @@ export class AcpSessionManager {
|
|
|
166
314
|
clientHandler,
|
|
167
315
|
sendToVellum: wrappedSend,
|
|
168
316
|
currentPrompt: null,
|
|
169
|
-
parentConversationId,
|
|
170
|
-
cwd,
|
|
171
|
-
command: agentConfig
|
|
317
|
+
parentConversationId: opts.parentConversationId,
|
|
318
|
+
cwd: opts.cwd,
|
|
319
|
+
command: adapterCommandOf(opts.agentConfig),
|
|
172
320
|
};
|
|
173
321
|
|
|
174
322
|
this.sessions.set(acpSessionId, entry);
|
|
323
|
+
return entry;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Notifies connected clients that a session is live. Shared by spawn()
|
|
328
|
+
* and the resume path (resumed sessions reuse the spawned event so
|
|
329
|
+
* clients render them; a dedicated acp_session_resumed event is a
|
|
330
|
+
* possible follow-up, not in scope here).
|
|
331
|
+
*/
|
|
332
|
+
private sendSpawnedEvent(acpSessionId: string, entry: SessionEntry): void {
|
|
333
|
+
entry.sendToVellum({
|
|
334
|
+
type: "acp_session_spawned",
|
|
335
|
+
acpSessionId,
|
|
336
|
+
agent: entry.state.agentId,
|
|
337
|
+
parentConversationId: entry.parentConversationId,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Resumes a terminal-state session from its persisted
|
|
343
|
+
* `acp_session_history` row, reattaching to the agent's stored
|
|
344
|
+
* conversation via ACP `session/resume` (preferred: no history replay) or
|
|
345
|
+
* `session/load` (replayed history is suppressed; see
|
|
346
|
+
* VellumAcpClientHandler.beginReplaySuppression).
|
|
347
|
+
*
|
|
348
|
+
* The resumed session reuses the original vellum session id,
|
|
349
|
+
* parentConversationId, and startedAt, and re-seeds its ring buffer from
|
|
350
|
+
* the persisted event log so the terminal upsert after the resumed run
|
|
351
|
+
* merges new events into the original row instead of losing them.
|
|
352
|
+
*
|
|
353
|
+
* Throws with an actionable message when the row is missing, was recorded
|
|
354
|
+
* before resume support (no cwd), the agent cannot be resolved, or the
|
|
355
|
+
* agent advertises neither resume capability.
|
|
356
|
+
*/
|
|
357
|
+
async resumeFromHistory(
|
|
358
|
+
acpSessionId: string,
|
|
359
|
+
sendToVellum: (msg: ServerMessage) => void,
|
|
360
|
+
): Promise<void> {
|
|
361
|
+
if (
|
|
362
|
+
this.sessions.has(acpSessionId) ||
|
|
363
|
+
this.pendingResumes.has(acpSessionId)
|
|
364
|
+
) {
|
|
365
|
+
throw new Error(`ACP session "${acpSessionId}" is already active`);
|
|
366
|
+
}
|
|
367
|
+
this.assertCapacity();
|
|
368
|
+
|
|
369
|
+
const row = getDb()
|
|
370
|
+
.select()
|
|
371
|
+
.from(acpSessionHistory)
|
|
372
|
+
.where(eq(acpSessionHistory.id, acpSessionId))
|
|
373
|
+
.get();
|
|
374
|
+
if (!row) {
|
|
375
|
+
throw new AcpSessionNotFoundError(acpSessionId);
|
|
376
|
+
}
|
|
377
|
+
if (!row.cwd) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`ACP session "${acpSessionId}" was recorded before resume support ` +
|
|
380
|
+
`(no working directory persisted) and cannot be resumed. ` +
|
|
381
|
+
`Spawn a new session instead.`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (!row.acpSessionId) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`ACP session "${acpSessionId}" has no protocol session id ` +
|
|
387
|
+
`persisted and cannot be resumed. Spawn a new session instead.`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const resolved = resolveAcpAgent(row.agentId);
|
|
392
|
+
if (!resolved.ok) {
|
|
393
|
+
throw new Error(formatResolveFailure(row.agentId, resolved));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Everything up to here is synchronous. Reserve the id + concurrency
|
|
397
|
+
// slot BEFORE the first await so a concurrent resume of the same id
|
|
398
|
+
// (or a spawn racing the cap) fails the guards above instead of
|
|
399
|
+
// double-registering and leaking the first child process. The
|
|
400
|
+
// reservation holds the resume's promise until it settles so
|
|
401
|
+
// steerOrResume can await a concurrent caller's in-flight resume, and
|
|
402
|
+
// so the delete guards see the id as live while the row's terminal
|
|
403
|
+
// status still reflects the previous run.
|
|
404
|
+
const resumePromise = this.performResume(
|
|
405
|
+
acpSessionId,
|
|
406
|
+
row as ResumableHistoryRow,
|
|
407
|
+
resolved.agent,
|
|
408
|
+
sendToVellum,
|
|
409
|
+
);
|
|
410
|
+
this.pendingResumes.set(acpSessionId, resumePromise);
|
|
411
|
+
try {
|
|
412
|
+
await resumePromise;
|
|
413
|
+
} finally {
|
|
414
|
+
this.pendingResumes.delete(acpSessionId);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The async body of resumeFromHistory, split out so the caller can store
|
|
420
|
+
* its promise in `pendingResumes` synchronously before the first await.
|
|
421
|
+
* All guards and row validation have already passed.
|
|
422
|
+
*/
|
|
423
|
+
private async performResume(
|
|
424
|
+
acpSessionId: string,
|
|
425
|
+
row: ResumableHistoryRow,
|
|
426
|
+
agent: AcpAgentConfig,
|
|
427
|
+
sendToVellum: (msg: ServerMessage) => void,
|
|
428
|
+
): Promise<void> {
|
|
429
|
+
const agentConfig = await prepareAgentEnv(agent);
|
|
430
|
+
|
|
431
|
+
// The daemon may have shut down while prepareAgentEnv was pending.
|
|
432
|
+
// Registering now would spawn a child process on a disposed manager
|
|
433
|
+
// that nothing would ever kill.
|
|
434
|
+
if (this.disposed) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`ACP session manager is disposed; cannot resume session "${acpSessionId}"`,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const entry = this.registerSession({
|
|
441
|
+
acpSessionId,
|
|
442
|
+
agentId: row.agentId,
|
|
443
|
+
agentConfig,
|
|
444
|
+
parentConversationId: row.parentConversationId,
|
|
445
|
+
cwd: row.cwd,
|
|
446
|
+
startedAt: row.startedAt,
|
|
447
|
+
sendToVellum,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
log.info(
|
|
451
|
+
{ acpSessionId, agentId: row.agentId, cwd: row.cwd },
|
|
452
|
+
"ACP resume from history requested",
|
|
453
|
+
);
|
|
454
|
+
const { process: agentProcess, state } = entry;
|
|
455
|
+
|
|
456
|
+
// Re-seed the ring buffer from the persisted event log, routed through
|
|
457
|
+
// appendToBuffer so the count/byte caps still apply. The terminal
|
|
458
|
+
// upsert then persists the merged (old + new) log.
|
|
459
|
+
try {
|
|
460
|
+
const persisted = JSON.parse(row.eventLogJson) as unknown;
|
|
461
|
+
if (Array.isArray(persisted)) {
|
|
462
|
+
for (const update of persisted) {
|
|
463
|
+
this.appendToBuffer(acpSessionId, update as AcpSessionUpdate);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
} catch (err) {
|
|
467
|
+
log.warn(
|
|
468
|
+
{ acpSessionId, err },
|
|
469
|
+
"Failed to re-seed ACP event buffer from persisted history",
|
|
470
|
+
);
|
|
471
|
+
}
|
|
175
472
|
|
|
176
473
|
try {
|
|
177
|
-
log.info({ acpSessionId, agentId }, "ACP spawning child process");
|
|
178
|
-
agentProcess.spawn(cwd);
|
|
179
474
|
log.info(
|
|
180
|
-
{ acpSessionId, agentId },
|
|
181
|
-
"ACP
|
|
475
|
+
{ acpSessionId, agentId: row.agentId },
|
|
476
|
+
"ACP spawning child process for resume",
|
|
182
477
|
);
|
|
478
|
+
agentProcess.spawn(row.cwd);
|
|
183
479
|
await agentProcess.initialize();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
480
|
+
if (agentProcess.supportsSessionResume) {
|
|
481
|
+
// session/resume reattaches without replaying history.
|
|
482
|
+
await agentProcess.resumeSession(row.acpSessionId, row.cwd);
|
|
483
|
+
} else if (agentProcess.supportsLoadSession) {
|
|
484
|
+
// session/load replays the full history as session/update
|
|
485
|
+
// notifications before resolving; suppress forwarding so the
|
|
486
|
+
// conversation and ring buffer don't receive duplicates.
|
|
487
|
+
entry.clientHandler.beginReplaySuppression();
|
|
488
|
+
try {
|
|
489
|
+
await agentProcess.loadSession(row.acpSessionId, row.cwd);
|
|
490
|
+
} finally {
|
|
491
|
+
entry.clientHandler.endReplaySuppression();
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`ACP agent "${row.agentId}" does not support session resume`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
state.acpSessionId = row.acpSessionId;
|
|
187
499
|
state.status = "running";
|
|
188
500
|
log.info(
|
|
189
|
-
{
|
|
190
|
-
|
|
501
|
+
{
|
|
502
|
+
acpSessionId,
|
|
503
|
+
agentId: row.agentId,
|
|
504
|
+
protocolSessionId: row.acpSessionId,
|
|
505
|
+
},
|
|
506
|
+
"ACP session resumed",
|
|
191
507
|
);
|
|
192
508
|
} catch (err) {
|
|
193
|
-
log.error(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
509
|
+
log.error(
|
|
510
|
+
{ acpSessionId, agentId: row.agentId, err },
|
|
511
|
+
"ACP resume failed",
|
|
512
|
+
);
|
|
513
|
+
// No prompt has fired yet, so no permissions can be pending.
|
|
514
|
+
this.teardownSession(acpSessionId, entry);
|
|
198
515
|
throw err;
|
|
199
516
|
}
|
|
200
517
|
|
|
201
|
-
|
|
202
|
-
type: "acp_session_spawned",
|
|
203
|
-
acpSessionId,
|
|
204
|
-
agent: agentId,
|
|
205
|
-
parentConversationId,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Fire prompt in the background — don't await
|
|
209
|
-
entry.currentPrompt = this.firePromptInBackground(
|
|
210
|
-
acpSessionId,
|
|
211
|
-
entry,
|
|
212
|
-
state.acpSessionId,
|
|
213
|
-
task,
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
return { acpSessionId, protocolSessionId: state.acpSessionId };
|
|
518
|
+
this.sendSpawnedEvent(acpSessionId, entry);
|
|
217
519
|
}
|
|
218
520
|
|
|
219
521
|
/**
|
|
@@ -225,7 +527,7 @@ export class AcpSessionManager {
|
|
|
225
527
|
async steer(acpSessionId: string, instruction: string): Promise<void> {
|
|
226
528
|
const entry = this.sessions.get(acpSessionId);
|
|
227
529
|
if (!entry) {
|
|
228
|
-
throw new
|
|
530
|
+
throw new AcpSessionNotFoundError(acpSessionId);
|
|
229
531
|
}
|
|
230
532
|
|
|
231
533
|
if (entry.state.status !== "running") {
|
|
@@ -258,22 +560,123 @@ export class AcpSessionManager {
|
|
|
258
560
|
);
|
|
259
561
|
}
|
|
260
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Steers a session, transparently resuming it from persisted history
|
|
565
|
+
* first when it is no longer in memory (it completed, or the daemon
|
|
566
|
+
* restarted). The resume and the instruction prompt are atomic from the
|
|
567
|
+
* caller's perspective: a successfully resumed session immediately gets
|
|
568
|
+
* the instruction fired, so it never sits running-idle with no in-flight
|
|
569
|
+
* prompt (and therefore no teardown owner). If the post-resume steer
|
|
570
|
+
* fails, the freshly resumed session is closed (process killed, terminal
|
|
571
|
+
* row persisted, maps cleared) instead of being leaked.
|
|
572
|
+
*
|
|
573
|
+
* When a concurrent caller's resume of the same id is already in flight,
|
|
574
|
+
* this call awaits that resume and then retries the plain steer, so both
|
|
575
|
+
* callers' instructions land on the single resumed session.
|
|
576
|
+
*
|
|
577
|
+
* Error contract for transport callers (acp_steer tool, steer route):
|
|
578
|
+
* - `AcpSessionNotFoundError`: no in-memory session AND no history row.
|
|
579
|
+
* - `AcpResumeError`: the resume (own or a concurrent caller's awaited
|
|
580
|
+
* one, or the steer immediately after an own resume) failed; the
|
|
581
|
+
* message carries the actionable hint.
|
|
582
|
+
* - any other error: the plain steer on an in-memory session failed.
|
|
583
|
+
*/
|
|
584
|
+
async steerOrResume(
|
|
585
|
+
acpSessionId: string,
|
|
586
|
+
instruction: string,
|
|
587
|
+
sendToVellum: (msg: ServerMessage) => void,
|
|
588
|
+
): Promise<{ resumed: boolean }> {
|
|
589
|
+
try {
|
|
590
|
+
await this.steer(acpSessionId, instruction);
|
|
591
|
+
return { resumed: false };
|
|
592
|
+
} catch (err) {
|
|
593
|
+
// Fall through to the in-flight-resume handling both when the session
|
|
594
|
+
// is entirely unknown and when a concurrent resume has already
|
|
595
|
+
// registered its entry but is still initializing: steer rejects with a
|
|
596
|
+
// plain not-running error in that window, yet the resume reservation
|
|
597
|
+
// is live and the retry below will land once it settles.
|
|
598
|
+
if (
|
|
599
|
+
!(err instanceof AcpSessionNotFoundError) &&
|
|
600
|
+
!this.pendingResumes.has(acpSessionId)
|
|
601
|
+
) {
|
|
602
|
+
throw err;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Another caller's resume of this id may already be in flight (the
|
|
607
|
+
// session is not in memory yet, but its slot is reserved). Await that
|
|
608
|
+
// resume and retry the plain steer once instead of failing
|
|
609
|
+
// resumeFromHistory's already-active guard, which would surface a
|
|
610
|
+
// misleading resume error and drop this instruction.
|
|
611
|
+
const inFlightResume = this.pendingResumes.get(acpSessionId);
|
|
612
|
+
if (inFlightResume) {
|
|
613
|
+
try {
|
|
614
|
+
await inFlightResume;
|
|
615
|
+
} catch (err) {
|
|
616
|
+
throw new AcpResumeError(err);
|
|
617
|
+
}
|
|
618
|
+
// The resumed session is owned by the concurrent caller (its own
|
|
619
|
+
// post-resume steer handles teardown on failure), so a failure here
|
|
620
|
+
// propagates as a plain steer error without closing the session.
|
|
621
|
+
await this.steer(acpSessionId, instruction);
|
|
622
|
+
return { resumed: true };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
await this.resumeFromHistory(acpSessionId, sendToVellum);
|
|
627
|
+
} catch (err) {
|
|
628
|
+
// A missing history row keeps its not-found shape; everything else
|
|
629
|
+
// (legacy row without cwd, resolver failure, capability missing)
|
|
630
|
+
// is a resume failure with an actionable message.
|
|
631
|
+
if (err instanceof AcpSessionNotFoundError) throw err;
|
|
632
|
+
throw new AcpResumeError(err);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
await this.steer(acpSessionId, instruction);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
// Tear down the just-resumed session rather than leaving it
|
|
639
|
+
// running-idle with no prompt handler to own its cleanup.
|
|
640
|
+
try {
|
|
641
|
+
this.close(acpSessionId);
|
|
642
|
+
} catch (closeErr) {
|
|
643
|
+
log.warn(
|
|
644
|
+
{ acpSessionId, err: closeErr },
|
|
645
|
+
"Failed to close ACP session after post-resume steer failure",
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
throw new AcpResumeError(err);
|
|
649
|
+
}
|
|
650
|
+
return { resumed: true };
|
|
651
|
+
}
|
|
652
|
+
|
|
261
653
|
/**
|
|
262
654
|
* Cancels an ongoing prompt in the specified session.
|
|
263
655
|
*
|
|
264
|
-
*
|
|
265
|
-
* catch handler in `firePromptInBackground` performs the
|
|
266
|
-
* persistence + teardown
|
|
267
|
-
* preserves "cancelled" instead of overwriting with "failed".
|
|
656
|
+
* When a prompt is in flight, its `prompt()` call rejects in response
|
|
657
|
+
* and the catch handler in `firePromptInBackground` performs the
|
|
658
|
+
* terminal persistence + teardown; we just flip the status here so that
|
|
659
|
+
* handler preserves "cancelled" instead of overwriting with "failed".
|
|
660
|
+
*
|
|
661
|
+
* When NO prompt is in flight there is no handler to own cleanup, so
|
|
662
|
+
* cancel persists and tears down the session itself. (Sessions normally
|
|
663
|
+
* always have a prompt in flight, but a cancel can race the window in
|
|
664
|
+
* steer() between clearing the old prompt and firing the new one.)
|
|
268
665
|
*/
|
|
269
666
|
async cancel(acpSessionId: string): Promise<void> {
|
|
270
667
|
const entry = this.sessions.get(acpSessionId);
|
|
271
668
|
if (!entry) {
|
|
272
|
-
throw new
|
|
669
|
+
throw new AcpSessionNotFoundError(acpSessionId);
|
|
273
670
|
}
|
|
274
671
|
await entry.process.cancel(entry.state.acpSessionId);
|
|
275
672
|
entry.state.status = "cancelled";
|
|
276
673
|
entry.state.completedAt = Date.now();
|
|
674
|
+
// Re-check the map after the await: the in-flight prompt's handler may
|
|
675
|
+
// have already torn the session down while process.cancel was pending.
|
|
676
|
+
if (!entry.currentPrompt && this.sessions.get(acpSessionId) === entry) {
|
|
677
|
+
this.persistTerminal(acpSessionId, entry);
|
|
678
|
+
this.teardownSession(acpSessionId, entry);
|
|
679
|
+
}
|
|
277
680
|
}
|
|
278
681
|
|
|
279
682
|
/**
|
|
@@ -288,7 +691,7 @@ export class AcpSessionManager {
|
|
|
288
691
|
close(acpSessionId: string): void {
|
|
289
692
|
const entry = this.sessions.get(acpSessionId);
|
|
290
693
|
if (!entry) {
|
|
291
|
-
throw new
|
|
694
|
+
throw new AcpSessionNotFoundError(acpSessionId);
|
|
292
695
|
}
|
|
293
696
|
if (
|
|
294
697
|
entry.state.status === "running" ||
|
|
@@ -335,7 +738,7 @@ export class AcpSessionManager {
|
|
|
335
738
|
if (acpSessionId) {
|
|
336
739
|
const entry = this.sessions.get(acpSessionId);
|
|
337
740
|
if (!entry) {
|
|
338
|
-
throw new
|
|
741
|
+
throw new AcpSessionNotFoundError(acpSessionId);
|
|
339
742
|
}
|
|
340
743
|
return entry.state;
|
|
341
744
|
}
|
|
@@ -368,15 +771,18 @@ export class AcpSessionManager {
|
|
|
368
771
|
|
|
369
772
|
/**
|
|
370
773
|
* Persists the session's final state + buffered event log to
|
|
371
|
-
* `acp_session_history`, then frees the buffer entry.
|
|
372
|
-
*
|
|
373
|
-
*
|
|
774
|
+
* `acp_session_history`, then frees the buffer entry. Upserts on id:
|
|
775
|
+
* resumed runs reuse the original vellum session id, so their terminal
|
|
776
|
+
* write must update the existing row (status, event log, etc.) instead of
|
|
777
|
+
* being silently skipped. Best-effort: a DB failure is logged but does
|
|
778
|
+
* not propagate, since the session has already reached a terminal state
|
|
779
|
+
* and clients have been notified.
|
|
374
780
|
*/
|
|
375
781
|
private persistTerminal(acpSessionId: string, entry: SessionEntry): void {
|
|
376
782
|
const buffer = this.eventBuffers.get(acpSessionId) ?? [];
|
|
377
783
|
// Serialize only the wire-shaped updates — drop the byte-size accounting
|
|
378
784
|
// metadata so persisted rows match the protocol shape clients receive.
|
|
379
|
-
const
|
|
785
|
+
const eventLogJson = JSON.stringify(buffer.map((b) => b.update));
|
|
380
786
|
try {
|
|
381
787
|
getDb()
|
|
382
788
|
.insert(acpSessionHistory)
|
|
@@ -390,9 +796,20 @@ export class AcpSessionManager {
|
|
|
390
796
|
status: entry.state.status,
|
|
391
797
|
stopReason: entry.state.stopReason ?? null,
|
|
392
798
|
error: entry.state.error ?? null,
|
|
393
|
-
eventLogJson
|
|
799
|
+
eventLogJson,
|
|
800
|
+
cwd: entry.cwd,
|
|
801
|
+
})
|
|
802
|
+
.onConflictDoUpdate({
|
|
803
|
+
target: acpSessionHistory.id,
|
|
804
|
+
set: {
|
|
805
|
+
status: entry.state.status,
|
|
806
|
+
completedAt: entry.state.completedAt ?? null,
|
|
807
|
+
stopReason: entry.state.stopReason ?? null,
|
|
808
|
+
error: entry.state.error ?? null,
|
|
809
|
+
eventLogJson,
|
|
810
|
+
cwd: entry.cwd,
|
|
811
|
+
},
|
|
394
812
|
})
|
|
395
|
-
.onConflictDoNothing()
|
|
396
813
|
.run();
|
|
397
814
|
} catch (err) {
|
|
398
815
|
log.error(
|
|
@@ -451,10 +868,12 @@ export class AcpSessionManager {
|
|
|
451
868
|
const agentLabel = current.state.agentId;
|
|
452
869
|
const responseText = current.clientHandler.responseText;
|
|
453
870
|
const sessionId = current.state.acpSessionId;
|
|
454
|
-
const
|
|
455
|
-
current.command
|
|
456
|
-
|
|
457
|
-
|
|
871
|
+
const hint = claudeResumeHint(
|
|
872
|
+
current.command,
|
|
873
|
+
current.cwd,
|
|
874
|
+
sessionId,
|
|
875
|
+
);
|
|
876
|
+
const resumeHint = hint ? `\n\n${hint}` : "";
|
|
458
877
|
const notifyMessage = `[ACP agent "${agentLabel}" completed]\n\n${responseText}${resumeHint}`;
|
|
459
878
|
const parentConversation = findConversation(
|
|
460
879
|
current.parentConversationId,
|
|
@@ -517,9 +936,12 @@ export class AcpSessionManager {
|
|
|
517
936
|
}
|
|
518
937
|
|
|
519
938
|
/**
|
|
520
|
-
* Kills all processes on shutdown.
|
|
939
|
+
* Kills all processes on shutdown. Also flags the manager as disposed so
|
|
940
|
+
* resumes that are mid-await when shutdown happens abort before spawning
|
|
941
|
+
* a child process nothing would ever kill.
|
|
521
942
|
*/
|
|
522
943
|
dispose(): void {
|
|
944
|
+
this.disposed = true;
|
|
523
945
|
this.closeAll();
|
|
524
946
|
}
|
|
525
947
|
}
|