@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
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createAbortReason } from "../../util/abort-reasons.js";
|
|
4
|
+
import {
|
|
5
|
+
classifyWebSearchFailure,
|
|
6
|
+
logWebSearchBackendFailure,
|
|
7
|
+
WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
|
|
8
|
+
} from "./web-search-error.js";
|
|
9
|
+
|
|
10
|
+
describe("classifyWebSearchFailure", () => {
|
|
11
|
+
test("Anthropic 'unavailable' code is a backend failure with friendly copy", () => {
|
|
12
|
+
const result = classifyWebSearchFailure({
|
|
13
|
+
isError: true,
|
|
14
|
+
errorCode: "unavailable",
|
|
15
|
+
});
|
|
16
|
+
expect(result.category).toBe("backend_unavailable");
|
|
17
|
+
expect(result.isBackendFailure).toBe(true);
|
|
18
|
+
expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
19
|
+
expect(result.rawDetail).toContain("unavailable");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test.each(["internal_error", "overloaded_error"])(
|
|
23
|
+
"Anthropic '%s' code is a backend failure",
|
|
24
|
+
(errorCode) => {
|
|
25
|
+
const result = classifyWebSearchFailure({ isError: true, errorCode });
|
|
26
|
+
expect(result.category).toBe("backend_unavailable");
|
|
27
|
+
expect(result.isBackendFailure).toBe(true);
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
test("HTTP 503 is a backend failure", () => {
|
|
32
|
+
const result = classifyWebSearchFailure({ isError: true, statusCode: 503 });
|
|
33
|
+
expect(result.category).toBe("backend_unavailable");
|
|
34
|
+
expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("thrown TypeError('fetch failed') is a backend failure", () => {
|
|
38
|
+
const result = classifyWebSearchFailure({
|
|
39
|
+
isError: true,
|
|
40
|
+
error: new TypeError("fetch failed"),
|
|
41
|
+
});
|
|
42
|
+
expect(result.category).toBe("backend_unavailable");
|
|
43
|
+
expect(result.isBackendFailure).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("AbortError-shaped timeout is a backend failure", () => {
|
|
47
|
+
const err = new Error("The operation was aborted due to timeout");
|
|
48
|
+
err.name = "AbortError";
|
|
49
|
+
const result = classifyWebSearchFailure({ isError: true, error: err });
|
|
50
|
+
expect(result.category).toBe("backend_unavailable");
|
|
51
|
+
expect(result.isBackendFailure).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("user-initiated abort is not a backend failure", () => {
|
|
55
|
+
const err = new Error("The operation was aborted");
|
|
56
|
+
err.name = "AbortError";
|
|
57
|
+
Object.assign(err, {
|
|
58
|
+
reason: createAbortReason("user_cancel", "cancelGeneration"),
|
|
59
|
+
});
|
|
60
|
+
const result = classifyWebSearchFailure({ isError: true, error: err });
|
|
61
|
+
expect(result.isBackendFailure).toBe(false);
|
|
62
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("wrapped ProviderError carrying abortReason is not a backend failure", () => {
|
|
66
|
+
// A provider wrapper erases the AbortError name and re-words the message,
|
|
67
|
+
// but carries the tagged reason on `abortReason` (ProviderError shape).
|
|
68
|
+
const err = Object.assign(new Error("Request was aborted"), {
|
|
69
|
+
name: "ProviderError",
|
|
70
|
+
abortReason: createAbortReason("user_cancel", "cancelGeneration"),
|
|
71
|
+
});
|
|
72
|
+
const result = classifyWebSearchFailure({ isError: true, error: err });
|
|
73
|
+
expect(result.category).not.toBe("backend_unavailable");
|
|
74
|
+
expect(result.isBackendFailure).toBe(false);
|
|
75
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("tagged abort with a transport-shaped cause is not a backend failure", () => {
|
|
79
|
+
// A user cancellation wrapped as a ProviderError that ALSO carries a
|
|
80
|
+
// transport-shaped `cause` (ECONNRESET). The tagged abort guard must win
|
|
81
|
+
// over transport-retryability so this is not mislabeled a backend outage.
|
|
82
|
+
const err = Object.assign(new Error("Request was aborted"), {
|
|
83
|
+
name: "ProviderError",
|
|
84
|
+
cause: { code: "ECONNRESET" },
|
|
85
|
+
abortReason: createAbortReason("user_cancel", "cancelGeneration"),
|
|
86
|
+
});
|
|
87
|
+
const result = classifyWebSearchFailure({ isError: true, error: err });
|
|
88
|
+
expect(result.category).not.toBe("backend_unavailable");
|
|
89
|
+
expect(result.isBackendFailure).toBe(false);
|
|
90
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("explicit statusCode wins over a misleading error-body keyword", () => {
|
|
94
|
+
// The provider response body contains "aborted" (a keyword the error-body
|
|
95
|
+
// heuristic would sniff as a non-failure), but the authoritative HTTP 503
|
|
96
|
+
// must classify this as a backend failure.
|
|
97
|
+
const result = classifyWebSearchFailure({
|
|
98
|
+
isError: true,
|
|
99
|
+
statusCode: 503,
|
|
100
|
+
error: new Error("the upstream request was aborted unexpectedly"),
|
|
101
|
+
});
|
|
102
|
+
expect(result.category).toBe("backend_unavailable");
|
|
103
|
+
expect(result.isBackendFailure).toBe(true);
|
|
104
|
+
expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("ECONNRESET network error is a backend failure", () => {
|
|
108
|
+
const err = Object.assign(new Error("socket hang up"), {
|
|
109
|
+
code: "ECONNRESET",
|
|
110
|
+
});
|
|
111
|
+
const result = classifyWebSearchFailure({ isError: true, error: err });
|
|
112
|
+
expect(result.category).toBe("backend_unavailable");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("Anthropic 'too_many_requests' code is rate-limited with backend copy", () => {
|
|
116
|
+
const result = classifyWebSearchFailure({
|
|
117
|
+
isError: true,
|
|
118
|
+
errorCode: "too_many_requests",
|
|
119
|
+
});
|
|
120
|
+
expect(result.category).toBe("rate_limited");
|
|
121
|
+
expect(result.isBackendFailure).toBe(true);
|
|
122
|
+
expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("HTTP 429 is rate-limited with backend copy", () => {
|
|
126
|
+
const result = classifyWebSearchFailure({ isError: true, statusCode: 429 });
|
|
127
|
+
expect(result.category).toBe("rate_limited");
|
|
128
|
+
expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("'query_too_long' has a distinct, non-backend message", () => {
|
|
132
|
+
const result = classifyWebSearchFailure({
|
|
133
|
+
isError: true,
|
|
134
|
+
errorCode: "query_too_long",
|
|
135
|
+
});
|
|
136
|
+
expect(result.category).toBe("query_too_long");
|
|
137
|
+
expect(result.isBackendFailure).toBe(false);
|
|
138
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
139
|
+
expect(result.userMessage.length).toBeGreaterThan(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("'max_uses_exceeded' has a distinct, non-backend message", () => {
|
|
143
|
+
const result = classifyWebSearchFailure({
|
|
144
|
+
isError: true,
|
|
145
|
+
errorCode: "max_uses_exceeded",
|
|
146
|
+
});
|
|
147
|
+
expect(result.category).toBe("max_uses_exceeded");
|
|
148
|
+
expect(result.isBackendFailure).toBe(false);
|
|
149
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
150
|
+
expect(result.userMessage.length).toBeGreaterThan(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("'invalid_input' is unknown and not a backend failure", () => {
|
|
154
|
+
const result = classifyWebSearchFailure({
|
|
155
|
+
isError: true,
|
|
156
|
+
errorCode: "invalid_input",
|
|
157
|
+
});
|
|
158
|
+
expect(result.category).toBe("unknown");
|
|
159
|
+
expect(result.isBackendFailure).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("HTTP 401 is a config failure, not the backend copy", () => {
|
|
163
|
+
const result = classifyWebSearchFailure({ isError: true, statusCode: 401 });
|
|
164
|
+
expect(result.category).toBe("config");
|
|
165
|
+
expect(result.isBackendFailure).toBe(false);
|
|
166
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("HTTP 403 is a config failure", () => {
|
|
170
|
+
const result = classifyWebSearchFailure({ isError: true, statusCode: 403 });
|
|
171
|
+
expect(result.category).toBe("config");
|
|
172
|
+
expect(result.isBackendFailure).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("successful-but-empty result is no_results, not a failure", () => {
|
|
176
|
+
const result = classifyWebSearchFailure({
|
|
177
|
+
isError: false,
|
|
178
|
+
hasResults: false,
|
|
179
|
+
});
|
|
180
|
+
expect(result.category).toBe("no_results");
|
|
181
|
+
expect(result.isBackendFailure).toBe(false);
|
|
182
|
+
expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("rawDetail is truncated to 500 chars", () => {
|
|
186
|
+
const result = classifyWebSearchFailure({
|
|
187
|
+
isError: true,
|
|
188
|
+
error: new Error("x".repeat(1000)),
|
|
189
|
+
});
|
|
190
|
+
expect(result.rawDetail.length).toBeLessThanOrEqual(500 + 40);
|
|
191
|
+
expect(result.rawDetail).toContain("truncated");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("WEB_SEARCH_BACKEND_FAILURE_MESSAGE copy safety", () => {
|
|
196
|
+
test("offers retry / continue-without / paste", () => {
|
|
197
|
+
expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("try again");
|
|
198
|
+
expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("continue without");
|
|
199
|
+
expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("paste");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("contains no raw provider details, JSON, or exception names", () => {
|
|
203
|
+
for (const banned of [
|
|
204
|
+
"{",
|
|
205
|
+
"error_code",
|
|
206
|
+
"Anthropic",
|
|
207
|
+
"web_search_tool_result_error",
|
|
208
|
+
"TypeError",
|
|
209
|
+
"stack",
|
|
210
|
+
]) {
|
|
211
|
+
expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).not.toContain(banned);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("logWebSearchBackendFailure", () => {
|
|
217
|
+
test("captures the event and rawDetail without raw query text", () => {
|
|
218
|
+
const calls: unknown[][] = [];
|
|
219
|
+
const fakeLogger = {
|
|
220
|
+
warn: (...args: unknown[]) => {
|
|
221
|
+
calls.push(args);
|
|
222
|
+
},
|
|
223
|
+
} as unknown as Parameters<typeof logWebSearchBackendFailure>[0];
|
|
224
|
+
|
|
225
|
+
const secretQuery = "super secret user query text";
|
|
226
|
+
logWebSearchBackendFailure(fakeLogger, {
|
|
227
|
+
provider: "anthropic",
|
|
228
|
+
requestId: "req-1",
|
|
229
|
+
errorCategory: "backend_unavailable",
|
|
230
|
+
rawDetail: "errorCode=unavailable",
|
|
231
|
+
fallbackShown: true,
|
|
232
|
+
queryLength: secretQuery.length,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(calls).toHaveLength(1);
|
|
236
|
+
const [payload, msg] = calls[0] as [Record<string, unknown>, string];
|
|
237
|
+
expect(payload.event).toBe("web_search_backend_failure");
|
|
238
|
+
expect(payload.tool).toBe("web_search");
|
|
239
|
+
expect(payload.provider).toBe("anthropic");
|
|
240
|
+
expect(payload.rawDetail).toBe("errorCode=unavailable");
|
|
241
|
+
expect(payload.queryLength).toBe(secretQuery.length);
|
|
242
|
+
expect(msg).toBe("web_search backend failure");
|
|
243
|
+
|
|
244
|
+
// The raw query text must never appear anywhere in the logged payload.
|
|
245
|
+
const serialized = JSON.stringify(calls);
|
|
246
|
+
expect(serialized).not.toContain(secretQuery);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Single source of truth for classifying `web_search` provider/backend
|
|
2
|
+
// failures and the user-facing copy we surface for them (ATL-727).
|
|
3
|
+
//
|
|
4
|
+
// This is a pure leaf module: it has NO imports from `daemon/`, `agent/`,
|
|
5
|
+
// `apps/`, or any client/UI package. It may only import the logger and pino
|
|
6
|
+
// types (for the telemetry helper). Every web_search code path — native
|
|
7
|
+
// Anthropic handler, app-side providers, and the web client default — funnels
|
|
8
|
+
// failures through `classifyWebSearchFailure` so the same friendly message
|
|
9
|
+
// propagates to every client via `WebSearchMetadata.errorMessage`.
|
|
10
|
+
|
|
11
|
+
import type { Logger } from "pino";
|
|
12
|
+
|
|
13
|
+
import { isAbortReason } from "../../util/abort-reasons.js";
|
|
14
|
+
import { truncateForLog } from "../../util/logger.js";
|
|
15
|
+
import { isRetryableNetworkError } from "../../util/retry.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Canonical user-facing copy for a recoverable web_search backend failure.
|
|
19
|
+
* This is what propagates to every client via `WebSearchMetadata.errorMessage`.
|
|
20
|
+
*
|
|
21
|
+
* It names the search tool as the thing struggling, offers retry /
|
|
22
|
+
* continue-without-search / paste-details, does not blame the user, does not
|
|
23
|
+
* imply we can fix the provider, does not claim the whole internet or all
|
|
24
|
+
* tools are down, and contains no raw provider details, JSON, stack traces,
|
|
25
|
+
* or exception names.
|
|
26
|
+
*/
|
|
27
|
+
export const WEB_SEARCH_BACKEND_FAILURE_MESSAGE =
|
|
28
|
+
"Search is having trouble right now. You can try again in a moment, continue without web search, or paste the relevant details here and I'll use those.";
|
|
29
|
+
|
|
30
|
+
const QUERY_TOO_LONG_MESSAGE =
|
|
31
|
+
"That search query was too long. Try a shorter query.";
|
|
32
|
+
|
|
33
|
+
const MAX_USES_EXCEEDED_MESSAGE =
|
|
34
|
+
"I've hit the web-search limit for this turn. I can continue without more searches, or you can paste the details and I'll use those.";
|
|
35
|
+
|
|
36
|
+
const CONFIG_MESSAGE = "Web search isn't configured.";
|
|
37
|
+
|
|
38
|
+
export type WebSearchFailureCategory =
|
|
39
|
+
| "backend_unavailable"
|
|
40
|
+
| "rate_limited"
|
|
41
|
+
| "query_too_long"
|
|
42
|
+
| "max_uses_exceeded"
|
|
43
|
+
| "config"
|
|
44
|
+
| "no_results"
|
|
45
|
+
| "unknown";
|
|
46
|
+
|
|
47
|
+
export interface WebSearchFailureClassification {
|
|
48
|
+
category: WebSearchFailureCategory;
|
|
49
|
+
isBackendFailure: boolean;
|
|
50
|
+
userMessage: string;
|
|
51
|
+
rawDetail: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WebSearchFailureInput {
|
|
55
|
+
/** Anthropic `web_search_tool_result_error` code, when present. */
|
|
56
|
+
errorCode?: string;
|
|
57
|
+
/** Thrown error or rejected value from a fetch/provider call. */
|
|
58
|
+
error?: unknown;
|
|
59
|
+
/** HTTP status code from a provider response, when present. */
|
|
60
|
+
statusCode?: number;
|
|
61
|
+
/** Whether the tool result was flagged as an error. */
|
|
62
|
+
isError?: boolean;
|
|
63
|
+
/** Whether a successful call returned any results. */
|
|
64
|
+
hasResults?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Categories we consider a transient backend failure worth the friendly copy. */
|
|
68
|
+
function isBackendFailureCategory(category: WebSearchFailureCategory): boolean {
|
|
69
|
+
return category === "backend_unavailable" || category === "rate_limited";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function userMessageFor(category: WebSearchFailureCategory): string {
|
|
73
|
+
switch (category) {
|
|
74
|
+
case "backend_unavailable":
|
|
75
|
+
case "rate_limited":
|
|
76
|
+
return WEB_SEARCH_BACKEND_FAILURE_MESSAGE;
|
|
77
|
+
case "query_too_long":
|
|
78
|
+
return QUERY_TOO_LONG_MESSAGE;
|
|
79
|
+
case "max_uses_exceeded":
|
|
80
|
+
return MAX_USES_EXCEEDED_MESSAGE;
|
|
81
|
+
case "config":
|
|
82
|
+
return CONFIG_MESSAGE;
|
|
83
|
+
case "no_results":
|
|
84
|
+
case "unknown":
|
|
85
|
+
// Neutral passthrough: callers keep their existing behavior.
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Map an Anthropic `web_search_tool_result_error` code to a category. */
|
|
91
|
+
function categoryFromErrorCode(
|
|
92
|
+
errorCode: string,
|
|
93
|
+
): WebSearchFailureCategory | undefined {
|
|
94
|
+
switch (errorCode) {
|
|
95
|
+
case "unavailable":
|
|
96
|
+
case "internal_error":
|
|
97
|
+
case "overloaded_error":
|
|
98
|
+
return "backend_unavailable";
|
|
99
|
+
case "too_many_requests":
|
|
100
|
+
return "rate_limited";
|
|
101
|
+
case "query_too_long":
|
|
102
|
+
return "query_too_long";
|
|
103
|
+
case "max_uses_exceeded":
|
|
104
|
+
return "max_uses_exceeded";
|
|
105
|
+
case "invalid_input":
|
|
106
|
+
// Recoverable, but not a backend failure — let callers handle it.
|
|
107
|
+
return "unknown";
|
|
108
|
+
default:
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Map an HTTP status code to a category. */
|
|
114
|
+
function categoryFromStatusCode(
|
|
115
|
+
statusCode: number,
|
|
116
|
+
): WebSearchFailureCategory | undefined {
|
|
117
|
+
if (statusCode === 429) return "rate_limited";
|
|
118
|
+
if (statusCode === 401 || statusCode === 403) return "config";
|
|
119
|
+
if (statusCode >= 500) return "backend_unavailable";
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Classify a thrown error / rejected value. This module is web_search-only, so
|
|
125
|
+
* network-layer failures (fetch failed, connection reset/refused, DNS, and
|
|
126
|
+
* timeouts without an explicit user-abort reason) are treated as backend
|
|
127
|
+
* failures.
|
|
128
|
+
*/
|
|
129
|
+
function categoryFromError(error: unknown): WebSearchFailureCategory | undefined {
|
|
130
|
+
if (error == null) return undefined;
|
|
131
|
+
|
|
132
|
+
// A user-initiated abort (Stop/Esc, preemption, dispose) is not a failure.
|
|
133
|
+
// The tagged `AbortReason` may surface directly (`AbortSignal.throwIfAborted`
|
|
134
|
+
// throws `signal.reason` verbatim), via `error.reason`, or — when a provider
|
|
135
|
+
// wrapper erases the `AbortError` name — on `ProviderError.abortReason`. Check
|
|
136
|
+
// all three FIRST, before the transport-retryability and abort/timeout
|
|
137
|
+
// substring heuristics, so a tagged cancellation that ALSO carries a
|
|
138
|
+
// transport-shaped `cause` (e.g. ECONNRESET) short-circuits to "not a
|
|
139
|
+
// failure" instead of being mislabeled a backend outage. A bare
|
|
140
|
+
// AbortError/timeout with no tagged reason still falls through below.
|
|
141
|
+
if (
|
|
142
|
+
isAbortReason(error) ||
|
|
143
|
+
isAbortReason((error as { reason?: unknown }).reason) ||
|
|
144
|
+
isAbortReason((error as { abortReason?: unknown }).abortReason)
|
|
145
|
+
) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Retryable transport failures (ECONNRESET/ECONNREFUSED/ETIMEDOUT, socket
|
|
150
|
+
// hang-ups, including one level of `cause` chain) are backend failures.
|
|
151
|
+
if (isRetryableNetworkError(error)) return "backend_unavailable";
|
|
152
|
+
|
|
153
|
+
const name = typeof (error as { name?: unknown }).name === "string"
|
|
154
|
+
? (error as { name: string }).name
|
|
155
|
+
: "";
|
|
156
|
+
const haystack = `${name} ${(error as { message?: unknown }).message ?? ""}`
|
|
157
|
+
.toLowerCase();
|
|
158
|
+
|
|
159
|
+
// web_search-only: treat aborts/timeouts/DNS/fetch failures (the cases
|
|
160
|
+
// `isRetryableNetworkError` doesn't cover) as backend failures.
|
|
161
|
+
if (
|
|
162
|
+
name === "AbortError" ||
|
|
163
|
+
haystack.includes("abort") ||
|
|
164
|
+
haystack.includes("timeout") ||
|
|
165
|
+
haystack.includes("timed out") ||
|
|
166
|
+
haystack.includes("fetch failed") ||
|
|
167
|
+
haystack.includes("failed to fetch") ||
|
|
168
|
+
haystack.includes("enotfound")
|
|
169
|
+
) {
|
|
170
|
+
return "backend_unavailable";
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Build the internal-only raw detail string (never embedded in userMessage). */
|
|
176
|
+
function buildRawDetail(input: WebSearchFailureInput): string {
|
|
177
|
+
const parts: string[] = [];
|
|
178
|
+
if (input.errorCode) parts.push(`errorCode=${input.errorCode}`);
|
|
179
|
+
if (typeof input.statusCode === "number") {
|
|
180
|
+
parts.push(`statusCode=${input.statusCode}`);
|
|
181
|
+
}
|
|
182
|
+
if (input.error != null) {
|
|
183
|
+
const err = input.error as { message?: unknown };
|
|
184
|
+
const msg =
|
|
185
|
+
typeof err.message === "string" ? err.message : String(input.error);
|
|
186
|
+
if (msg) parts.push(msg);
|
|
187
|
+
}
|
|
188
|
+
return truncateForLog(parts.join(" "), 500);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Classify a web_search failure into a stable category, a user-facing message,
|
|
193
|
+
* and an internal-only raw detail. Never treats an empty-but-successful result
|
|
194
|
+
* as a failure.
|
|
195
|
+
*/
|
|
196
|
+
export function classifyWebSearchFailure(
|
|
197
|
+
input: WebSearchFailureInput,
|
|
198
|
+
): WebSearchFailureClassification {
|
|
199
|
+
const rawDetail = buildRawDetail(input);
|
|
200
|
+
|
|
201
|
+
// Success-passthrough: nothing went wrong, there were just no results.
|
|
202
|
+
if (!input.isError && input.error == null) {
|
|
203
|
+
return {
|
|
204
|
+
category: "no_results",
|
|
205
|
+
isBackendFailure: false,
|
|
206
|
+
userMessage: userMessageFor("no_results"),
|
|
207
|
+
rawDetail,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Resolution order: Anthropic `errorCode` → explicit HTTP `statusCode` →
|
|
212
|
+
// error-body/network heuristics. An explicit status code is authoritative
|
|
213
|
+
// over substring-sniffing the provider's response body (which can contain
|
|
214
|
+
// misleading keywords like "timeout"/"abort"). Tagged user-aborts carry no
|
|
215
|
+
// status code, so they still flow through `categoryFromError` and
|
|
216
|
+
// short-circuit to a non-failure.
|
|
217
|
+
const category =
|
|
218
|
+
(input.errorCode != null
|
|
219
|
+
? categoryFromErrorCode(input.errorCode)
|
|
220
|
+
: undefined) ??
|
|
221
|
+
(typeof input.statusCode === "number"
|
|
222
|
+
? categoryFromStatusCode(input.statusCode)
|
|
223
|
+
: undefined) ??
|
|
224
|
+
categoryFromError(input.error) ??
|
|
225
|
+
"unknown";
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
category,
|
|
229
|
+
isBackendFailure: isBackendFailureCategory(category),
|
|
230
|
+
userMessage: userMessageFor(category),
|
|
231
|
+
rawDetail,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface WebSearchBackendFailureMeta {
|
|
236
|
+
provider: string;
|
|
237
|
+
requestId?: string;
|
|
238
|
+
errorCategory: WebSearchFailureCategory;
|
|
239
|
+
rawDetail: string;
|
|
240
|
+
fallbackShown: boolean;
|
|
241
|
+
queryLength?: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Emit a structured warning for a web_search backend failure (ATL-727).
|
|
246
|
+
*
|
|
247
|
+
* Do NOT log raw query text — only `queryLength`. `rawDetail` is internal-only
|
|
248
|
+
* provider/HTTP context and must never be surfaced to users.
|
|
249
|
+
*/
|
|
250
|
+
export function logWebSearchBackendFailure(
|
|
251
|
+
log: Logger,
|
|
252
|
+
meta: WebSearchBackendFailureMeta,
|
|
253
|
+
): void {
|
|
254
|
+
log.warn(
|
|
255
|
+
{
|
|
256
|
+
event: "web_search_backend_failure",
|
|
257
|
+
tool: "web_search",
|
|
258
|
+
provider: meta.provider,
|
|
259
|
+
requestId: meta.requestId,
|
|
260
|
+
errorCategory: meta.errorCategory,
|
|
261
|
+
rawDetail: meta.rawDetail,
|
|
262
|
+
fallbackShown: meta.fallbackShown,
|
|
263
|
+
queryLength: meta.queryLength,
|
|
264
|
+
},
|
|
265
|
+
"web_search backend failure",
|
|
266
|
+
);
|
|
267
|
+
}
|