@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,17 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the user plugin loader
|
|
2
|
+
* Tests for the user plugin loader.
|
|
3
3
|
*
|
|
4
4
|
* Redirects `getWorkspaceDir()` into a per-test temp directory via
|
|
5
5
|
* `VELLUM_WORKSPACE_DIR` so `loadUserPlugins()` walks an isolated tree
|
|
6
6
|
* that we populate on demand.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* The loader's own responsibility is directory discovery and dispatch: it
|
|
9
|
+
* scans `<workspaceDir>/plugins/*` and hands every subdirectory carrying a
|
|
10
|
+
* `package.json` to `loadExternalPlugin`. The plugin-build mechanics
|
|
11
|
+
* (manifest parsing, hook/tool wiring, the per-plugin timeout, error
|
|
12
|
+
* isolation) are covered directly in `external-plugin-loader.test.ts`; here
|
|
13
|
+
* we assert the discovery contract:
|
|
14
|
+
*
|
|
15
|
+
* - A directory with a `package.json` is loaded and registered.
|
|
16
|
+
* - A directory without a `package.json` is skipped silently.
|
|
17
|
+
* - A missing `getWorkspaceDir()/plugins/` directory is a no-op (zero
|
|
18
|
+
* installed user plugins is the default shape of a fresh daemon).
|
|
19
|
+
* - One failing plugin does not prevent a sibling from registering.
|
|
15
20
|
*/
|
|
16
21
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
17
22
|
import { tmpdir } from "node:os";
|
|
@@ -26,7 +31,6 @@ import { loadUserPlugins } from "../plugins/user-loader.js";
|
|
|
26
31
|
|
|
27
32
|
// Isolate every run under its own tempdir so parallel test files (and
|
|
28
33
|
// repeated runs of this file) cannot collide on `<workspaceDir>/plugins/`.
|
|
29
|
-
// Each describe-scope gets a fresh subdirectory.
|
|
30
34
|
const TEST_WORKSPACE_DIR = join(
|
|
31
35
|
tmpdir(),
|
|
32
36
|
`vellum-user-plugin-loader-test-${process.pid}-${Date.now()}`,
|
|
@@ -37,30 +41,26 @@ process.env.VELLUM_WORKSPACE_DIR = TEST_WORKSPACE_DIR;
|
|
|
37
41
|
const PLUGINS_DIR = join(TEST_WORKSPACE_DIR, "plugins");
|
|
38
42
|
|
|
39
43
|
/**
|
|
40
|
-
* Write a
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* into the repo's registry module.
|
|
44
|
-
*
|
|
45
|
-
* `relativeRegistryImport` points from the synthetic plugin file at
|
|
46
|
-
* `<TEST_WORKSPACE_DIR>/plugins/<name>/register.ts` to the real
|
|
47
|
-
* registry source at `<repo>/assistant/src/plugins/registry.ts`. Using a
|
|
48
|
-
* relative path (rather than a project-root alias) keeps the test hermetic
|
|
49
|
-
* and matches how an on-disk user plugin would actually import the
|
|
50
|
-
* registry's public API in a real install.
|
|
44
|
+
* Write a directory-convention plugin: a `package.json` manifest plus any
|
|
45
|
+
* surface files (`hooks/<name>.ts`, `tools/<name>.ts`) whose default export
|
|
46
|
+
* the loader wires up.
|
|
51
47
|
*/
|
|
52
|
-
function writePlugin(
|
|
48
|
+
function writePlugin(
|
|
49
|
+
name: string,
|
|
50
|
+
pkg: Record<string, unknown>,
|
|
51
|
+
files: Record<string, string> = {},
|
|
52
|
+
): void {
|
|
53
53
|
const pluginDir = join(PLUGINS_DIR, name);
|
|
54
54
|
mkdirSync(pluginDir, { recursive: true });
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
writeFileSync(join(pluginDir, "package.json"), JSON.stringify(pkg, null, 2));
|
|
56
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
57
|
+
const parts = rel.split("/");
|
|
58
|
+
parts.pop();
|
|
59
|
+
if (parts.length > 0) {
|
|
60
|
+
mkdirSync(join(pluginDir, ...parts), { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
writeFileSync(join(pluginDir, rel), body);
|
|
63
|
+
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function clearPluginsDir(): void {
|
|
@@ -73,17 +73,14 @@ describe("user plugin loader", () => {
|
|
|
73
73
|
clearPluginsDir();
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("loads a
|
|
76
|
+
test("loads a plugin via its package.json manifest and registers it", async () => {
|
|
77
77
|
writePlugin(
|
|
78
78
|
"my-plugin",
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
`,
|
|
79
|
+
{ name: "my-plugin", version: "0.1.0" },
|
|
80
|
+
{
|
|
81
|
+
"hooks/init.ts":
|
|
82
|
+
"export default async function init(_ctx: unknown): Promise<void> {}\n",
|
|
83
|
+
},
|
|
87
84
|
);
|
|
88
85
|
|
|
89
86
|
await loadUserPlugins();
|
|
@@ -91,34 +88,22 @@ registerPlugin({
|
|
|
91
88
|
const registered = getRegisteredPlugins();
|
|
92
89
|
expect(registered).toHaveLength(1);
|
|
93
90
|
expect(registered[0]?.manifest.name).toBe("my-plugin");
|
|
91
|
+
expect(typeof registered[0]?.hooks?.init).toBe("function");
|
|
94
92
|
});
|
|
95
93
|
|
|
96
94
|
test("per-plugin failure is isolated: other plugins still load", async () => {
|
|
97
|
-
// Plugin A
|
|
98
|
-
//
|
|
99
|
-
// the entire user-plugin surface or crash the daemon.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
writePlugin(
|
|
107
|
-
"good-plugin",
|
|
108
|
-
`
|
|
109
|
-
registerPlugin({
|
|
110
|
-
manifest: {
|
|
111
|
-
name: "good-plugin",
|
|
112
|
-
version: "0.0.1",
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
`,
|
|
116
|
-
);
|
|
95
|
+
// Plugin A has a malformed package.json; the loader must isolate the
|
|
96
|
+
// failure and still register the healthy sibling — one bad user plugin
|
|
97
|
+
// cannot brick the entire user-plugin surface or crash the daemon.
|
|
98
|
+
const brokenDir = join(PLUGINS_DIR, "broken-plugin");
|
|
99
|
+
mkdirSync(brokenDir, { recursive: true });
|
|
100
|
+
writeFileSync(join(brokenDir, "package.json"), "{ not valid json");
|
|
101
|
+
|
|
102
|
+
writePlugin("good-plugin", { name: "good-plugin", version: "0.1.0" });
|
|
117
103
|
|
|
118
104
|
await loadUserPlugins();
|
|
119
105
|
|
|
120
|
-
const
|
|
121
|
-
const names = registered.map((p) => p.manifest.name);
|
|
106
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
122
107
|
// Order is not guaranteed (filesystem-dependent) — assert membership.
|
|
123
108
|
expect(names).toContain("good-plugin");
|
|
124
109
|
expect(names).not.toContain("broken-plugin");
|
|
@@ -132,92 +117,9 @@ registerPlugin({
|
|
|
132
117
|
expect(getRegisteredPlugins()).toHaveLength(0);
|
|
133
118
|
});
|
|
134
119
|
|
|
135
|
-
test("
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// must bound the import with a timeout so the hang is isolated the same
|
|
139
|
-
// way a thrown error would be. A neighboring well-behaved plugin must
|
|
140
|
-
// still load.
|
|
141
|
-
writePlugin(
|
|
142
|
-
"hanging-plugin",
|
|
143
|
-
`
|
|
144
|
-
await new Promise(() => {
|
|
145
|
-
// Intentionally never resolves — simulates a plugin whose top-level
|
|
146
|
-
// await blocks forever.
|
|
147
|
-
});
|
|
148
|
-
registerPlugin({
|
|
149
|
-
manifest: {
|
|
150
|
-
name: "hanging-plugin",
|
|
151
|
-
version: "0.0.1",
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
`,
|
|
155
|
-
);
|
|
156
|
-
writePlugin(
|
|
157
|
-
"healthy-plugin",
|
|
158
|
-
`
|
|
159
|
-
registerPlugin({
|
|
160
|
-
manifest: {
|
|
161
|
-
name: "healthy-plugin",
|
|
162
|
-
version: "0.0.1",
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
`,
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Use a short test-only timeout so the suite does not wait the full
|
|
169
|
-
// production 10s for the hung-plugin path.
|
|
170
|
-
await loadUserPlugins({ importTimeoutMs: 250 });
|
|
171
|
-
|
|
172
|
-
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
173
|
-
expect(names).toContain("healthy-plugin");
|
|
174
|
-
expect(names).not.toContain("hanging-plugin");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("plugin whose top-level await resolves AFTER the timeout cannot register late", async () => {
|
|
178
|
-
// Codex/Devin P1 regression: racing `import(moduleUrl)` against a timeout
|
|
179
|
-
// only stops the loader from awaiting the module — it does NOT cancel
|
|
180
|
-
// module evaluation. A plugin whose top-level await eventually resolves
|
|
181
|
-
// continues running in the background and would otherwise call
|
|
182
|
-
// `registerPlugin()` after `loadUserPlugins()` has returned (and after
|
|
183
|
-
// `bootstrapPlugins()` has potentially already walked the registry),
|
|
184
|
-
// leaving the plugin visible to `getMiddlewaresFor()` / `getInjectors()`
|
|
185
|
-
// with its `init()` hook never invoked.
|
|
186
|
-
//
|
|
187
|
-
// The `closeRegistration()` latch must reject that late arrival so the
|
|
188
|
-
// registry stays consistent with the bootstrap invariant.
|
|
189
|
-
writePlugin(
|
|
190
|
-
"slow-late-plugin",
|
|
191
|
-
`
|
|
192
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
193
|
-
registerPlugin({
|
|
194
|
-
manifest: {
|
|
195
|
-
name: "slow-late-plugin",
|
|
196
|
-
version: "0.0.1",
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
`,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
// Time out well before the plugin's top-level await resolves. The loader
|
|
203
|
-
// returns immediately; the abandoned import keeps evaluating in the
|
|
204
|
-
// background.
|
|
205
|
-
await loadUserPlugins({ importTimeoutMs: 25 });
|
|
206
|
-
|
|
207
|
-
// Wait long enough for the abandoned import's top-level await to resolve
|
|
208
|
-
// and try to `registerPlugin()`. The closed-registration latch must
|
|
209
|
-
// reject the call; the `.catch(() => {})` on the abandoned import must
|
|
210
|
-
// swallow the resulting rejection so the test does not see an
|
|
211
|
-
// unhandled-rejection crash.
|
|
212
|
-
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
213
|
-
|
|
214
|
-
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
215
|
-
expect(names).not.toContain("slow-late-plugin");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("subdirectory without register.{ts,js} is silently skipped", async () => {
|
|
219
|
-
// Populate a directory that looks like a plugin but lacks a register
|
|
220
|
-
// file. The loader must skip it without throwing.
|
|
120
|
+
test("subdirectory without package.json is silently skipped", async () => {
|
|
121
|
+
// Populate a directory that looks like a plugin but lacks a manifest.
|
|
122
|
+
// The loader must skip it without throwing.
|
|
221
123
|
const stubDir = join(PLUGINS_DIR, "not-a-plugin");
|
|
222
124
|
mkdirSync(stubDir, { recursive: true });
|
|
223
125
|
writeFileSync(join(stubDir, "README.md"), "# not actually a plugin\n");
|
|
@@ -226,146 +128,12 @@ registerPlugin({
|
|
|
226
128
|
expect(getRegisteredPlugins()).toHaveLength(0);
|
|
227
129
|
});
|
|
228
130
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
* Write a directory-convention plugin (package.json + optional
|
|
232
|
-
* hooks/tools default exports). Mirrors `writePlugin()` above but
|
|
233
|
-
* targets the new experimental loader path.
|
|
234
|
-
*/
|
|
235
|
-
function writeExperimentalPlugin(
|
|
236
|
-
name: string,
|
|
237
|
-
pkg: Record<string, unknown>,
|
|
238
|
-
files: Record<string, string> = {},
|
|
239
|
-
): void {
|
|
240
|
-
const pluginDir = join(PLUGINS_DIR, name);
|
|
241
|
-
mkdirSync(pluginDir, { recursive: true });
|
|
242
|
-
writeFileSync(
|
|
243
|
-
join(pluginDir, "package.json"),
|
|
244
|
-
JSON.stringify(pkg, null, 2),
|
|
245
|
-
);
|
|
246
|
-
for (const [rel, body] of Object.entries(files)) {
|
|
247
|
-
const parts = rel.split("/");
|
|
248
|
-
parts.pop();
|
|
249
|
-
if (parts.length > 0) {
|
|
250
|
-
mkdirSync(join(pluginDir, ...parts), { recursive: true });
|
|
251
|
-
}
|
|
252
|
-
writeFileSync(join(pluginDir, rel), body);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
test("loads a plugin via the package.json branch and registers it", async () => {
|
|
257
|
-
writeExperimentalPlugin(
|
|
258
|
-
"experimental-one",
|
|
259
|
-
{ name: "experimental-one", version: "0.1.0" },
|
|
260
|
-
{
|
|
261
|
-
"hooks/init.ts":
|
|
262
|
-
"export default async function init(_ctx: unknown): Promise<void> {}\n",
|
|
263
|
-
},
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
await loadUserPlugins();
|
|
267
|
-
|
|
268
|
-
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
269
|
-
expect(names).toContain("experimental-one");
|
|
270
|
-
const registered = getRegisteredPlugins().find(
|
|
271
|
-
(p) => p.manifest.name === "experimental-one",
|
|
272
|
-
);
|
|
273
|
-
expect(typeof registered?.hooks?.init).toBe("function");
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("strips npm scope from package.json name", async () => {
|
|
277
|
-
writeExperimentalPlugin("scoped", {
|
|
278
|
-
name: "@vellumai/cool-plugin",
|
|
279
|
-
version: "0.1.0",
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
await loadUserPlugins();
|
|
131
|
+
test("strips npm scope from package.json name", async () => {
|
|
132
|
+
writePlugin("scoped", { name: "@vellumai/cool-plugin", version: "0.1.0" });
|
|
283
133
|
|
|
284
|
-
|
|
285
|
-
expect(names).toContain("cool-plugin");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("a broken experimental plugin is logged and skipped without affecting others", async () => {
|
|
289
|
-
// Plugin A has a malformed package.json; Plugin B is a healthy
|
|
290
|
-
// legacy register.ts. The loader must isolate A's failure and still
|
|
291
|
-
// register B — same per-plugin contract as the legacy path.
|
|
292
|
-
const brokenDir = join(PLUGINS_DIR, "broken-experimental");
|
|
293
|
-
mkdirSync(brokenDir, { recursive: true });
|
|
294
|
-
writeFileSync(join(brokenDir, "package.json"), "{ not valid json");
|
|
295
|
-
|
|
296
|
-
writePlugin(
|
|
297
|
-
"healthy-legacy",
|
|
298
|
-
`
|
|
299
|
-
registerPlugin({
|
|
300
|
-
manifest: {
|
|
301
|
-
name: "healthy-legacy",
|
|
302
|
-
version: "0.0.1",
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
`,
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
await loadUserPlugins();
|
|
309
|
-
|
|
310
|
-
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
311
|
-
expect(names).toContain("healthy-legacy");
|
|
312
|
-
expect(names).not.toContain("broken-experimental");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test("experimental and legacy plugins coexist in one workspace", async () => {
|
|
316
|
-
writeExperimentalPlugin("new-style", {
|
|
317
|
-
name: "new-style",
|
|
318
|
-
version: "0.1.0",
|
|
319
|
-
});
|
|
320
|
-
writePlugin(
|
|
321
|
-
"old-style",
|
|
322
|
-
`
|
|
323
|
-
registerPlugin({
|
|
324
|
-
manifest: {
|
|
325
|
-
name: "old-style",
|
|
326
|
-
version: "0.0.1",
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
`,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
await loadUserPlugins();
|
|
333
|
-
|
|
334
|
-
const names = getRegisteredPlugins()
|
|
335
|
-
.map((p) => p.manifest.name)
|
|
336
|
-
.sort();
|
|
337
|
-
expect(names).toEqual(["new-style", "old-style"]);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test("package.json branch wins over a stale register.ts in the same dir", async () => {
|
|
341
|
-
// A migrated plugin may keep its old register.ts on disk while it
|
|
342
|
-
// adopts the new convention. The package.json gate takes the
|
|
343
|
-
// experimental path and the legacy register.ts is never imported,
|
|
344
|
-
// so the plugin must register exactly once.
|
|
345
|
-
const dir = join(PLUGINS_DIR, "migrated");
|
|
346
|
-
mkdirSync(dir, { recursive: true });
|
|
347
|
-
writeFileSync(
|
|
348
|
-
join(dir, "package.json"),
|
|
349
|
-
JSON.stringify({ name: "migrated", version: "0.1.0" }),
|
|
350
|
-
);
|
|
351
|
-
// A register.ts that would double-register if both paths fired.
|
|
352
|
-
const registryPath = join(
|
|
353
|
-
import.meta.dir,
|
|
354
|
-
"..",
|
|
355
|
-
"plugins",
|
|
356
|
-
"registry.ts",
|
|
357
|
-
);
|
|
358
|
-
writeFileSync(
|
|
359
|
-
join(dir, "register.ts"),
|
|
360
|
-
`import { registerPlugin } from ${JSON.stringify(registryPath)};
|
|
361
|
-
registerPlugin({ manifest: { name: "migrated", version: "0.0.1", requires: { pluginRuntime: "v1" } } });
|
|
362
|
-
`,
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
await loadUserPlugins();
|
|
134
|
+
await loadUserPlugins();
|
|
366
135
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
});
|
|
136
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
137
|
+
expect(names).toContain("cool-plugin");
|
|
370
138
|
});
|
|
371
139
|
});
|
|
@@ -96,10 +96,10 @@ function makePersistingStreamingSession(
|
|
|
96
96
|
|
|
97
97
|
let turnChannelContext: TurnChannelContext | null = null;
|
|
98
98
|
let turnInterfaceContext: TurnInterfaceContext | null = null;
|
|
99
|
+
let processing = false;
|
|
99
100
|
const session = {
|
|
100
101
|
conversationId,
|
|
101
102
|
messages: [],
|
|
102
|
-
processing: false,
|
|
103
103
|
abortController: null,
|
|
104
104
|
currentRequestId: undefined,
|
|
105
105
|
queue: {} as never,
|
|
@@ -108,7 +108,10 @@ function makePersistingStreamingSession(
|
|
|
108
108
|
scopeId: "default",
|
|
109
109
|
includeDefaultFallback: false,
|
|
110
110
|
},
|
|
111
|
-
isProcessing: () =>
|
|
111
|
+
isProcessing: () => processing,
|
|
112
|
+
setProcessing: (value: boolean) => {
|
|
113
|
+
processing = value;
|
|
114
|
+
},
|
|
112
115
|
persistUserMessage: async (
|
|
113
116
|
...args: Parameters<Conversation["persistUserMessage"]>
|
|
114
117
|
) => persistUserMessageImpl(session, ...args),
|
|
@@ -138,7 +141,7 @@ function makePersistingStreamingSession(
|
|
|
138
141
|
for (const event of events) {
|
|
139
142
|
onEvent(event);
|
|
140
143
|
}
|
|
141
|
-
|
|
144
|
+
processing = false;
|
|
142
145
|
session.abortController = null;
|
|
143
146
|
session.currentRequestId = undefined;
|
|
144
147
|
},
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// End-to-end integration coverage for the centralized web_search
|
|
2
|
+
// backend-failure normalization layer (ATL-727).
|
|
3
|
+
//
|
|
4
|
+
// PRs 1-4 built the layer in three places:
|
|
5
|
+
// - `tools/network/web-search-error.ts` — `classifyWebSearchFailure` +
|
|
6
|
+
// `WEB_SEARCH_BACKEND_FAILURE_MESSAGE` + the structured
|
|
7
|
+
// `web_search_backend_failure` log helper.
|
|
8
|
+
// - `daemon/conversation-agent-loop-handlers.ts` — the native
|
|
9
|
+
// `server_tool_complete` handler maps Anthropic backend failures to the
|
|
10
|
+
// friendly copy, dedups per turn (`webSearchBackendFailureNotified`), and
|
|
11
|
+
// gates telemetry on `classification.isBackendFailure`.
|
|
12
|
+
// - `tools/network/web-search.ts` — the app-side `backendFailureResult`
|
|
13
|
+
// helper routes 5xx/network/429 to the same copy.
|
|
14
|
+
//
|
|
15
|
+
// The single-layer native-handler invariants (friendly copy + isError + empty
|
|
16
|
+
// results, raw-detail-logged-not-shown, per-turn dedup, successful-empty
|
|
17
|
+
// search) are owned by `native-web-search.test.ts`. This file locks the
|
|
18
|
+
// cross-cutting acceptance criteria that file does not cover:
|
|
19
|
+
// - honest continuation (the failure is a recoverable tool_result, not a
|
|
20
|
+
// thrown provider error; the search is never marked successful),
|
|
21
|
+
// - web_fetch DNS failures are NOT conflated with the search backend copy.
|
|
22
|
+
//
|
|
23
|
+
// It genuinely reuses the shared handler harness in
|
|
24
|
+
// `helpers/native-web-search-harness.ts` (the same harness driven by
|
|
25
|
+
// `native-web-search.test.ts`); the web_fetch invariant is exercised through
|
|
26
|
+
// the same `handleToolResult` path an app-executed fetch tool drives.
|
|
27
|
+
|
|
28
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
29
|
+
|
|
30
|
+
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Mock the daemon collaborators the handler module imports at load time so the
|
|
34
|
+
// handler can be driven in isolation (mirrors native-web-search.test.ts).
|
|
35
|
+
// `mock.module()` is file-scoped, so the shared harness cannot install these.
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
mock.module("../config/loader.js", () => ({
|
|
39
|
+
getConfig: () => ({
|
|
40
|
+
skills: {
|
|
41
|
+
entries: {},
|
|
42
|
+
load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
|
|
43
|
+
install: { nodeManager: "npm" },
|
|
44
|
+
allowBundled: null,
|
|
45
|
+
remoteProviders: {
|
|
46
|
+
skillssh: { enabled: true },
|
|
47
|
+
clawhub: { enabled: true },
|
|
48
|
+
},
|
|
49
|
+
remotePolicy: {
|
|
50
|
+
blockSuspicious: true,
|
|
51
|
+
blockMalware: true,
|
|
52
|
+
maxSkillsShRisk: "medium",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
loadConfig: () => ({}),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
60
|
+
addMessage: () => ({ id: "mock-msg-id" }),
|
|
61
|
+
getMessageById: () => null,
|
|
62
|
+
updateMessageContent: () => {},
|
|
63
|
+
provenanceFromTrustContext: () => ({}),
|
|
64
|
+
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
68
|
+
recordRequestLog: () => {},
|
|
69
|
+
backfillMessageIdOnLogs: () => {},
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// Import after mocking.
|
|
73
|
+
import {
|
|
74
|
+
createEventHandlerState,
|
|
75
|
+
dispatchAgentEvent,
|
|
76
|
+
type EventHandlerState,
|
|
77
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
78
|
+
import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../tools/network/web-search-error.js";
|
|
79
|
+
import {
|
|
80
|
+
backendFailureLogs,
|
|
81
|
+
completeNativeWebSearch,
|
|
82
|
+
createHandlerDeps,
|
|
83
|
+
lastToolResult,
|
|
84
|
+
} from "./helpers/native-web-search-harness.js";
|
|
85
|
+
|
|
86
|
+
describe("web_search backend-failure end-to-end (ATL-727)", () => {
|
|
87
|
+
let state: EventHandlerState;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
state = createEventHandlerState();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("backend failure stays an honest, recoverable tool result (not a thrown provider error, never marked successful)", async () => {
|
|
94
|
+
const { deps, events } = createHandlerDeps("req-honesty");
|
|
95
|
+
|
|
96
|
+
// A recoverable backend failure flows as a tool_result, so dispatch must
|
|
97
|
+
// resolve without throwing.
|
|
98
|
+
await expect(
|
|
99
|
+
completeNativeWebSearch(state, deps, "tu_honesty", {
|
|
100
|
+
isError: true,
|
|
101
|
+
errorCode: "unavailable",
|
|
102
|
+
}),
|
|
103
|
+
).resolves.toBeUndefined();
|
|
104
|
+
|
|
105
|
+
const result = lastToolResult(events);
|
|
106
|
+
// The search is never silently upgraded to a success.
|
|
107
|
+
expect(result?.isError).toBe(true);
|
|
108
|
+
expect(result?.activityMetadata?.webSearch?.resultCount).toBe(0);
|
|
109
|
+
expect(result?.activityMetadata?.webSearch?.results).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("web_fetch DNS failure is NOT conflated with the web_search backend copy", async () => {
|
|
113
|
+
// The normalization layer keys exclusively on web_search (the native
|
|
114
|
+
// `server_tool_complete` handler and `web-search.ts`). It never inspects
|
|
115
|
+
// `webFetch` metadata. handleToolResult forwards an app-executed tool's
|
|
116
|
+
// `activityMetadata` verbatim, so a web_fetch DNS failure must reach the
|
|
117
|
+
// client with its own copy and acquire NO `webSearch` metadata. Drive that
|
|
118
|
+
// path directly with a DNS-failure fetch result for grimgoods.io.
|
|
119
|
+
const { deps, events, warnings } = createHandlerDeps("req-web-fetch");
|
|
120
|
+
|
|
121
|
+
const dnsError =
|
|
122
|
+
'Error: Unable to resolve host "grimgoods.io" (DNS lookup failed)';
|
|
123
|
+
const fetchMetadata: ToolActivityMetadata = {
|
|
124
|
+
webFetch: {
|
|
125
|
+
url: "https://grimgoods.io",
|
|
126
|
+
finalUrl: "https://grimgoods.io",
|
|
127
|
+
status: 0,
|
|
128
|
+
byteCount: 0,
|
|
129
|
+
charCount: 0,
|
|
130
|
+
truncated: false,
|
|
131
|
+
domain: "grimgoods.io",
|
|
132
|
+
redirectCount: 0,
|
|
133
|
+
durationMs: 12,
|
|
134
|
+
errorMessage: dnsError,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
await dispatchAgentEvent(state, deps, {
|
|
139
|
+
type: "tool_use",
|
|
140
|
+
id: "tu_fetch",
|
|
141
|
+
name: "web_fetch",
|
|
142
|
+
input: { url: "https://grimgoods.io" },
|
|
143
|
+
});
|
|
144
|
+
await dispatchAgentEvent(state, deps, {
|
|
145
|
+
type: "tool_result",
|
|
146
|
+
toolUseId: "tu_fetch",
|
|
147
|
+
content: dnsError,
|
|
148
|
+
isError: true,
|
|
149
|
+
activityMetadata: fetchMetadata,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const result = lastToolResult(events);
|
|
153
|
+
expect(result?.isError).toBe(true);
|
|
154
|
+
|
|
155
|
+
// The DNS error keeps its own webFetch copy untouched...
|
|
156
|
+
expect(result?.activityMetadata?.webFetch?.errorMessage).toBe(dnsError);
|
|
157
|
+
// ...and is never rewritten to the search backend copy.
|
|
158
|
+
expect(result?.activityMetadata?.webFetch?.errorMessage).not.toBe(
|
|
159
|
+
WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
|
|
160
|
+
);
|
|
161
|
+
// No webSearch metadata is fabricated for a fetch failure.
|
|
162
|
+
expect(result?.activityMetadata?.webSearch).toBeUndefined();
|
|
163
|
+
// And the web_search backend-failure telemetry never fires for a fetch.
|
|
164
|
+
expect(backendFailureLogs(warnings)).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
});
|