@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { isPlaceholderSentinelText } from "../../placeholder-sentinels.js";
|
|
3
4
|
import {
|
|
5
|
+
EMPTY_ASSISTANT_TURN_PLACEHOLDER,
|
|
4
6
|
OpenAIChatCompletionsProvider,
|
|
5
7
|
type OpenAIChatCompletionsProviderOptions,
|
|
6
8
|
} from "../chat-completions-provider.js";
|
|
@@ -348,6 +350,116 @@ describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
|
|
|
348
350
|
expect(assistantMsg.reasoning_content).toBeUndefined();
|
|
349
351
|
});
|
|
350
352
|
|
|
353
|
+
test("backfills placeholder content for a reasoning-only assistant turn when enabled", async () => {
|
|
354
|
+
const { provider, requests } = stubProvider(
|
|
355
|
+
[
|
|
356
|
+
{
|
|
357
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
358
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
{
|
|
362
|
+
assistantReasoningField: "reasoning",
|
|
363
|
+
backfillEmptyAssistantContent: true,
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
await provider.sendMessage([
|
|
368
|
+
{ role: "user", content: [{ type: "text", text: "question" }] },
|
|
369
|
+
{
|
|
370
|
+
role: "assistant",
|
|
371
|
+
content: [
|
|
372
|
+
{
|
|
373
|
+
type: "thinking",
|
|
374
|
+
thinking: "truncated chain of thought",
|
|
375
|
+
signature: "",
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
const params = requests[0] as {
|
|
382
|
+
messages: Array<{
|
|
383
|
+
role: string;
|
|
384
|
+
content: string | null;
|
|
385
|
+
reasoning?: string;
|
|
386
|
+
tool_calls?: unknown;
|
|
387
|
+
}>;
|
|
388
|
+
};
|
|
389
|
+
const assistantMsg = params.messages.find((m) => m.role === "assistant")!;
|
|
390
|
+
// content or tool_calls must be set; reasoning alone does not satisfy it.
|
|
391
|
+
expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
|
|
392
|
+
expect(assistantMsg.tool_calls).toBeUndefined();
|
|
393
|
+
expect(assistantMsg.reasoning).toBe("truncated chain of thought");
|
|
394
|
+
// The placeholder is a recognized sentinel, so it is stripped from
|
|
395
|
+
// persisted/rendered history if a model echoes it back, and it carries no
|
|
396
|
+
// control characters that a strict OpenAI-compatible backend might reject.
|
|
397
|
+
expect(isPlaceholderSentinelText(EMPTY_ASSISTANT_TURN_PLACEHOLDER)).toBe(
|
|
398
|
+
true,
|
|
399
|
+
);
|
|
400
|
+
expect(EMPTY_ASSISTANT_TURN_PLACEHOLDER).not.toContain("\x00");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("leaves reasoning-only assistant content null when backfill is disabled", async () => {
|
|
404
|
+
const { provider, requests } = stubProvider(
|
|
405
|
+
[
|
|
406
|
+
{
|
|
407
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
408
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
{ assistantReasoningField: "reasoning_content" },
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
await provider.sendMessage([
|
|
415
|
+
{ role: "user", content: [{ type: "text", text: "question" }] },
|
|
416
|
+
{
|
|
417
|
+
role: "assistant",
|
|
418
|
+
content: [
|
|
419
|
+
{
|
|
420
|
+
type: "thinking",
|
|
421
|
+
thinking: "truncated chain of thought",
|
|
422
|
+
signature: "",
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
const params = requests[0] as {
|
|
429
|
+
messages: Array<{ role: string; content: string | null }>;
|
|
430
|
+
};
|
|
431
|
+
const assistantMsg = params.messages.find((m) => m.role === "assistant")!;
|
|
432
|
+
// Backfill defaults off, so providers that tolerate null assistant content
|
|
433
|
+
// (e.g. OpenAI proper) are unaffected by the OpenRouter-specific guard.
|
|
434
|
+
expect(assistantMsg.content).toBeNull();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("does not backfill content when tool calls are present", async () => {
|
|
438
|
+
const { provider, requests } = stubProvider([
|
|
439
|
+
{
|
|
440
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
441
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
442
|
+
},
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
await provider.sendMessage([
|
|
446
|
+
{
|
|
447
|
+
role: "assistant",
|
|
448
|
+
content: [
|
|
449
|
+
{ type: "tool_use", id: "call_1", name: "search", input: { q: "x" } },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
const params = requests[0] as {
|
|
455
|
+
messages: Array<{ role: string; content: string | null }>;
|
|
456
|
+
};
|
|
457
|
+
// Tool-call-only assistant messages keep null content (preferred by
|
|
458
|
+
// Anthropic-proxy/Bedrock backends); the placeholder is only for the
|
|
459
|
+
// neither-content-nor-tool_calls case.
|
|
460
|
+
expect(params.messages[0].content).toBeNull();
|
|
461
|
+
});
|
|
462
|
+
|
|
351
463
|
test("skips Anthropic-originated thinking blocks (with signatures)", async () => {
|
|
352
464
|
const { provider, requests } = stubProvider(
|
|
353
465
|
[
|
|
@@ -4,6 +4,7 @@ import { isAbortReason } from "../../util/abort-reasons.js";
|
|
|
4
4
|
import { ProviderError } from "../../util/errors.js";
|
|
5
5
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
6
6
|
import { escapeXmlAttr } from "../../util/xml.js";
|
|
7
|
+
import { PLACEHOLDER_EMPTY_TURN } from "../placeholder-sentinels.js";
|
|
7
8
|
import { createStreamTimeout } from "../stream-timeout.js";
|
|
8
9
|
import type {
|
|
9
10
|
ContentBlock,
|
|
@@ -100,6 +101,26 @@ export function extractApiErrorDetail(
|
|
|
100
101
|
* OpenRouter's `error.metadata.raw` strings, which are typically <1KB. */
|
|
101
102
|
const MAX_API_ERROR_DETAIL_CHARS = 2000;
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Fallback `content` for an assistant turn that has neither visible text nor
|
|
106
|
+
* tool calls (e.g. a reasoning-only turn truncated at the output-token limit).
|
|
107
|
+
*
|
|
108
|
+
* The OpenAI chat-completions schema requires an assistant message to carry
|
|
109
|
+
* `content` or `tool_calls`. OpenAI itself tolerates `content: null`/`""` here,
|
|
110
|
+
* but strict OpenAI-compatible backends do not: DeepSeek via OpenRouter rejects
|
|
111
|
+
* the request with `Invalid assistant message: content or tool_calls must be
|
|
112
|
+
* set`, and vLLM-style validators coerce empty-string content back to null and
|
|
113
|
+
* reject it the same way. The placeholder must therefore be a non-empty string.
|
|
114
|
+
*
|
|
115
|
+
* We reuse the shared empty-turn sentinel so that
|
|
116
|
+
* `isPlaceholderSentinelText`/`cleanAssistantContent` strip it from persisted
|
|
117
|
+
* and rendered history if a model ever echoes it back. The null-byte prefix is
|
|
118
|
+
* dropped because some OpenAI-compatible backends reject control characters in
|
|
119
|
+
* message content; the bare form is still recognized by
|
|
120
|
+
* `isPlaceholderSentinelText`.
|
|
121
|
+
*/
|
|
122
|
+
export const EMPTY_ASSISTANT_TURN_PLACEHOLDER = PLACEHOLDER_EMPTY_TURN.slice(1);
|
|
123
|
+
|
|
103
124
|
/**
|
|
104
125
|
* Read the first matching header from an SDK error's headers object,
|
|
105
126
|
* tolerating both Map-like (`Headers.get()`) and plain-object shapes.
|
|
@@ -153,6 +174,13 @@ export interface OpenAIChatCompletionsProviderOptions {
|
|
|
153
174
|
* DeepSeek/Fireworks use `"reasoning_content"`; OpenRouter uses `"reasoning"`.
|
|
154
175
|
* When unset, thinking blocks are dropped from outbound assistant messages. */
|
|
155
176
|
assistantReasoningField?: "reasoning" | "reasoning_content";
|
|
177
|
+
/** Backfill a non-empty placeholder for assistant turns that would otherwise
|
|
178
|
+
* serialize with neither `content` nor `tool_calls` (e.g. reasoning-only
|
|
179
|
+
* turns). Off by default; enabled for OpenRouter, whose downstream providers
|
|
180
|
+
* (e.g. DeepSeek) reject such messages with `Invalid assistant message:
|
|
181
|
+
* content or tool_calls must be set`. See {@link
|
|
182
|
+
* EMPTY_ASSISTANT_TURN_PLACEHOLDER}. */
|
|
183
|
+
backfillEmptyAssistantContent?: boolean;
|
|
156
184
|
}
|
|
157
185
|
|
|
158
186
|
/** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
|
|
@@ -228,6 +256,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
228
256
|
| "reasoning"
|
|
229
257
|
| "reasoning_content"
|
|
230
258
|
| undefined;
|
|
259
|
+
private backfillEmptyAssistantContent: boolean;
|
|
231
260
|
|
|
232
261
|
constructor(
|
|
233
262
|
apiKey: string,
|
|
@@ -251,6 +280,8 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
251
280
|
this.requestHeaders = options.requestHeaders ?? {};
|
|
252
281
|
this.parseThinkTags = options.parseThinkTags ?? false;
|
|
253
282
|
this.assistantReasoningField = options.assistantReasoningField;
|
|
283
|
+
this.backfillEmptyAssistantContent =
|
|
284
|
+
options.backfillEmptyAssistantContent ?? false;
|
|
254
285
|
}
|
|
255
286
|
|
|
256
287
|
async sendMessage(
|
|
@@ -794,6 +825,19 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
794
825
|
result.tool_calls = toolCalls;
|
|
795
826
|
}
|
|
796
827
|
|
|
828
|
+
// An assistant message must carry `content` or `tool_calls`. A turn with
|
|
829
|
+
// neither (e.g. reasoning-only) would serialize to null/empty content with
|
|
830
|
+
// no tool calls, which strict OpenAI-compatible backends reject. Reasoning
|
|
831
|
+
// lives in a separate field and does not satisfy this constraint. Scoped to
|
|
832
|
+
// providers that need it (OpenRouter) via `backfillEmptyAssistantContent`.
|
|
833
|
+
if (
|
|
834
|
+
this.backfillEmptyAssistantContent &&
|
|
835
|
+
!result.tool_calls &&
|
|
836
|
+
(result.content === null || result.content === "")
|
|
837
|
+
) {
|
|
838
|
+
result.content = EMPTY_ASSISTANT_TURN_PLACEHOLDER;
|
|
839
|
+
}
|
|
840
|
+
|
|
797
841
|
return result;
|
|
798
842
|
}
|
|
799
843
|
|
|
@@ -122,6 +122,7 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
122
122
|
streamTimeoutMs: options.streamTimeoutMs,
|
|
123
123
|
requestHeaders: OPENROUTER_APP_ATTRIBUTION_HEADERS,
|
|
124
124
|
assistantReasoningField: "reasoning",
|
|
125
|
+
backfillEmptyAssistantContent: true,
|
|
125
126
|
});
|
|
126
127
|
this.openRouterApiKey = apiKey;
|
|
127
128
|
this.defaultModel = model;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Internal placeholder sentinels injected as assistant-message content when a
|
|
2
|
+
// turn would otherwise serialize with neither text nor tool calls. Provider
|
|
3
|
+
// request bodies must keep a non-empty content slot (Anthropic to preserve
|
|
4
|
+
// role alternation; strict OpenAI-compatible backends to satisfy the
|
|
5
|
+
// "content or tool_calls must be set" constraint), but these markers must
|
|
6
|
+
// never be persisted or rendered to users.
|
|
7
|
+
//
|
|
8
|
+
// The null-byte prefix makes the prefixed form impossible to produce via
|
|
9
|
+
// normal model output or user input, preventing false positives. Some
|
|
10
|
+
// OpenAI-compatible backends reject control characters in message content, so
|
|
11
|
+
// the OpenAI path emits the bare (prefix-stripped) form, which
|
|
12
|
+
// `isPlaceholderSentinelText` still recognizes.
|
|
13
|
+
export const PLACEHOLDER_EMPTY_TURN =
|
|
14
|
+
"\x00__PLACEHOLDER__[empty assistant turn]";
|
|
15
|
+
export const PLACEHOLDER_BLOCKS_OMITTED =
|
|
16
|
+
"\x00__PLACEHOLDER__[internal blocks omitted]";
|
|
17
|
+
|
|
18
|
+
// Compared against the payload with any leading `\x00` stripped, so the check
|
|
19
|
+
// matches both the prefixed sentinel we emit and any bare variant that lost
|
|
20
|
+
// the null byte in transit (e.g. the model echoing the text back without
|
|
21
|
+
// reproducing the control character).
|
|
22
|
+
const PLACEHOLDER_SENTINEL_BARE: ReadonlySet<string> = new Set([
|
|
23
|
+
PLACEHOLDER_EMPTY_TURN.slice(1),
|
|
24
|
+
PLACEHOLDER_BLOCKS_OMITTED.slice(1),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* True when the text is one of the internal alternation-preserving sentinels,
|
|
29
|
+
* with or without the null-byte prefix. These must never be persisted or
|
|
30
|
+
* rendered to users — they exist only in outbound provider request bodies.
|
|
31
|
+
*/
|
|
32
|
+
export function isPlaceholderSentinelText(text: string): boolean {
|
|
33
|
+
const normalized = text.startsWith("\x00") ? text.slice(1) : text;
|
|
34
|
+
return PLACEHOLDER_SENTINEL_BARE.has(normalized);
|
|
35
|
+
}
|
|
@@ -136,6 +136,10 @@ import {
|
|
|
136
136
|
const runResult = (history: Message[]): AgentLoopRunResult => ({
|
|
137
137
|
history,
|
|
138
138
|
exitReason: null,
|
|
139
|
+
appendedNewMessages: true,
|
|
140
|
+
// The wake path slices its own new-message boundary off the returned
|
|
141
|
+
// history (it never destructures `newMessages`), so this is type-only.
|
|
142
|
+
newMessages: [],
|
|
139
143
|
});
|
|
140
144
|
|
|
141
145
|
interface MockTarget extends WakeTarget {
|
|
@@ -428,7 +432,7 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
428
432
|
expect(target.runCalls).toHaveLength(0);
|
|
429
433
|
});
|
|
430
434
|
|
|
431
|
-
test("
|
|
435
|
+
test("builds a guardian turn context for explicit local-owner cleanup-mode wakes", async () => {
|
|
432
436
|
mockDiskPressureStatus = {
|
|
433
437
|
enabled: true,
|
|
434
438
|
state: "critical",
|
|
@@ -459,11 +463,11 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
459
463
|
|
|
460
464
|
expect(result).toEqual({ invoked: true, producedToolCalls: false });
|
|
461
465
|
expect(target.runCalls).toHaveLength(1);
|
|
462
|
-
expect(target.runCalls[0]!.turnContext).
|
|
466
|
+
expect(target.runCalls[0]!.turnContext).toEqual({
|
|
467
|
+
requestId: "wake:local-cleanup",
|
|
463
468
|
conversationId: target.conversationId,
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
},
|
|
469
|
+
turnIndex: 0,
|
|
470
|
+
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
467
471
|
});
|
|
468
472
|
});
|
|
469
473
|
|
|
@@ -1077,7 +1081,7 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
1077
1081
|
expect(target.drainQueueCalls).toBe(1);
|
|
1078
1082
|
// Critical ordering invariant: drain runs after processing=false.
|
|
1079
1083
|
// If drain ran while processing was still true,
|
|
1080
|
-
// `enqueueMessage`'s `if (!ctx.
|
|
1084
|
+
// `enqueueMessage`'s `if (!ctx.isProcessing()) return ...` gate would
|
|
1081
1085
|
// see processing=true and the drained item would itself just
|
|
1082
1086
|
// re-enqueue — no progress. Snapshot the live flag *inside* drain
|
|
1083
1087
|
// (rather than inferring from toggle order) so a future regression
|
|
@@ -50,7 +50,7 @@ let surfaceCalls: Array<{
|
|
|
50
50
|
surfaceId: string;
|
|
51
51
|
}> = [];
|
|
52
52
|
|
|
53
|
-
mock.module("../../daemon/conversation-
|
|
53
|
+
mock.module("../../daemon/conversation-registry.js", () => ({
|
|
54
54
|
findConversation: (_conversationId: string) => {
|
|
55
55
|
return mockConversation ?? undefined;
|
|
56
56
|
},
|
|
@@ -140,7 +140,7 @@ export interface WakeTarget {
|
|
|
140
140
|
* The wake invokes this in its `finally` block AFTER
|
|
141
141
|
* `markProcessing(false)`. Order matters: if drain ran while
|
|
142
142
|
* processing was still true, `enqueueMessage`'s gate
|
|
143
|
-
* (`if (!ctx.
|
|
143
|
+
* (`if (!ctx.isProcessing()) return ...`) would still see processing=true
|
|
144
144
|
* and the drain itself would be a no-op against any racy late sends.
|
|
145
145
|
* Running drain after processing is released matches the canonical
|
|
146
146
|
* user-turn finally path in `conversation-agent-loop.ts`.
|
|
@@ -432,9 +432,6 @@ function buildWakeTurnContext(
|
|
|
432
432
|
sourceChannel: opts.sourceChannel ?? "vellum",
|
|
433
433
|
trustClass: "guardian",
|
|
434
434
|
} satisfies TrustContext),
|
|
435
|
-
injectionInputs: {
|
|
436
|
-
diskPressureContext: { cleanupModeActive: true },
|
|
437
|
-
},
|
|
438
435
|
};
|
|
439
436
|
}
|
|
440
437
|
|
|
@@ -963,7 +960,7 @@ export async function wakeAgentForOpportunity(
|
|
|
963
960
|
|
|
964
961
|
// Run completed cleanly. The canonical user-turn pattern
|
|
965
962
|
// (conversation-agent-loop.ts:1860, 2106-2126) updates
|
|
966
|
-
// `ctx.messages` first, then
|
|
963
|
+
// `ctx.messages` first, then clears the flag via `ctx.setProcessing(false)`, then
|
|
967
964
|
// calls `ctx.drainQueue(...)`. We mirror that order so a message
|
|
968
965
|
// queued during the wake dequeues against an already-updated
|
|
969
966
|
// history — otherwise `drainSingleMessage` reads `ctx.messages`
|
|
@@ -43,7 +43,7 @@ import { appendEventToStream } from "../signals/event-stream.js";
|
|
|
43
43
|
import { getLogger } from "../util/logger.js";
|
|
44
44
|
import type { AssistantEvent } from "./assistant-event.js";
|
|
45
45
|
import { buildAssistantEvent } from "./assistant-event.js";
|
|
46
|
-
import { stampAndBuffer } from "./
|
|
46
|
+
import { stampAndBuffer } from "./assistant-stream-state.js";
|
|
47
47
|
|
|
48
48
|
const log = getLogger("assistant-event-hub");
|
|
49
49
|
|
|
@@ -64,6 +64,13 @@ export interface AssistantEventSubscription {
|
|
|
64
64
|
dispose(): void;
|
|
65
65
|
/** True until `dispose()` has been called. */
|
|
66
66
|
readonly active: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Per-connection identifier, unique within the hub instance. Distinguishes
|
|
69
|
+
* connections that share a `clientId` (e.g. an old connection and the new
|
|
70
|
+
* one that replaced it on reconnect) so subscribe / dispose / shed log
|
|
71
|
+
* lines can be attributed to a specific connection.
|
|
72
|
+
*/
|
|
73
|
+
readonly connectionId: string;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
// ── Subscriber entries (discriminated union) ─────────────────────────────────
|
|
@@ -75,6 +82,13 @@ interface BaseSubscriberEntry {
|
|
|
75
82
|
onEvict: () => void;
|
|
76
83
|
connectedAt: Date;
|
|
77
84
|
lastActiveAt: Date;
|
|
85
|
+
/**
|
|
86
|
+
* Per-connection identifier, unique within the hub instance. Two entries
|
|
87
|
+
* with the same `clientId` (old vs reconnected connection) get distinct
|
|
88
|
+
* connection ids, making them traceable across subscribe / dispose / shed
|
|
89
|
+
* logs.
|
|
90
|
+
*/
|
|
91
|
+
connectionId: string;
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
interface ClientEntry extends BaseSubscriberEntry {
|
|
@@ -106,10 +120,15 @@ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
|
|
|
106
120
|
? Omit<T, K>
|
|
107
121
|
: never;
|
|
108
122
|
|
|
109
|
-
/** Input shape for `subscribe()` — hub fills `active`, `connectedAt`, `lastActiveAt` and defaults `filter`/`onEvict`. */
|
|
123
|
+
/** Input shape for `subscribe()` — hub fills `active`, `connectedAt`, `lastActiveAt`, `connectionId` and defaults `filter`/`onEvict`. */
|
|
110
124
|
type SubscriberInput = DistributiveOmit<
|
|
111
125
|
SubscriberEntry,
|
|
112
|
-
|
|
126
|
+
| "active"
|
|
127
|
+
| "connectedAt"
|
|
128
|
+
| "lastActiveAt"
|
|
129
|
+
| "filter"
|
|
130
|
+
| "onEvict"
|
|
131
|
+
| "connectionId"
|
|
113
132
|
> & {
|
|
114
133
|
filter?: AssistantEventFilter;
|
|
115
134
|
onEvict?: () => void;
|
|
@@ -132,6 +151,8 @@ type SubscriberInput = DistributiveOmit<
|
|
|
132
151
|
export class AssistantEventHub {
|
|
133
152
|
private readonly subscribers = new Set<SubscriberEntry>();
|
|
134
153
|
private readonly maxSubscribers: number;
|
|
154
|
+
/** Monotonic source for per-connection ids, scoped to this hub. */
|
|
155
|
+
private connectionCounter = 0;
|
|
135
156
|
|
|
136
157
|
constructor(options?: { maxSubscribers?: number }) {
|
|
137
158
|
this.maxSubscribers = options?.maxSubscribers ?? Infinity;
|
|
@@ -173,7 +194,11 @@ export class AssistantEventHub {
|
|
|
173
194
|
}
|
|
174
195
|
if (stale.length > 0) {
|
|
175
196
|
log.info(
|
|
176
|
-
{
|
|
197
|
+
{
|
|
198
|
+
clientId: subscriber.clientId,
|
|
199
|
+
count: stale.length,
|
|
200
|
+
disposedConnectionIds: stale.map((entry) => entry.connectionId),
|
|
201
|
+
},
|
|
177
202
|
"disposed stale subscribers for reconnecting client",
|
|
178
203
|
);
|
|
179
204
|
}
|
|
@@ -196,6 +221,7 @@ export class AssistantEventHub {
|
|
|
196
221
|
}
|
|
197
222
|
|
|
198
223
|
const now = new Date();
|
|
224
|
+
const connectionId = `conn-${++this.connectionCounter}`;
|
|
199
225
|
const entry: SubscriberEntry = {
|
|
200
226
|
...subscriber,
|
|
201
227
|
filter: subscriber.filter ?? {},
|
|
@@ -203,6 +229,7 @@ export class AssistantEventHub {
|
|
|
203
229
|
active: true,
|
|
204
230
|
connectedAt: now,
|
|
205
231
|
lastActiveAt: now,
|
|
232
|
+
connectionId,
|
|
206
233
|
} as SubscriberEntry;
|
|
207
234
|
|
|
208
235
|
if (entry.type === "client") {
|
|
@@ -211,11 +238,12 @@ export class AssistantEventHub {
|
|
|
211
238
|
clientId: entry.clientId,
|
|
212
239
|
interfaceId: entry.interfaceId,
|
|
213
240
|
capabilities: entry.capabilities,
|
|
241
|
+
connectionId,
|
|
214
242
|
},
|
|
215
243
|
"subscriber registered (client)",
|
|
216
244
|
);
|
|
217
245
|
} else {
|
|
218
|
-
log.info("subscriber registered (process)");
|
|
246
|
+
log.info({ connectionId }, "subscriber registered (process)");
|
|
219
247
|
}
|
|
220
248
|
|
|
221
249
|
this.subscribers.add(entry);
|
|
@@ -230,17 +258,19 @@ export class AssistantEventHub {
|
|
|
230
258
|
{
|
|
231
259
|
clientId: entry.clientId,
|
|
232
260
|
interfaceId: entry.interfaceId,
|
|
261
|
+
connectionId,
|
|
233
262
|
},
|
|
234
263
|
"subscriber unregistered (client)",
|
|
235
264
|
);
|
|
236
265
|
} else {
|
|
237
|
-
log.info("subscriber unregistered (process)");
|
|
266
|
+
log.info({ connectionId }, "subscriber unregistered (process)");
|
|
238
267
|
}
|
|
239
268
|
}
|
|
240
269
|
},
|
|
241
270
|
get active() {
|
|
242
271
|
return entry.active;
|
|
243
272
|
},
|
|
273
|
+
connectionId,
|
|
244
274
|
};
|
|
245
275
|
}
|
|
246
276
|
|
|
@@ -678,7 +708,7 @@ async function createCanonicalRequestForConfirmation(
|
|
|
678
708
|
{ DAEMON_INTERNAL_ASSISTANT_ID },
|
|
679
709
|
{ bridgeConfirmationRequestToGuardian },
|
|
680
710
|
] = await Promise.all([
|
|
681
|
-
import("../daemon/conversation-
|
|
711
|
+
import("../daemon/conversation-registry.js"),
|
|
682
712
|
import("../memory/canonical-guardian-store.js"),
|
|
683
713
|
import("../security/secret-scanner.js"),
|
|
684
714
|
import("../tools/tool-input-summary.js"),
|