@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,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the default `empty-response` plugin's `stop` hook.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - The default hook's binary decision for the canonical cases: empty turn
|
|
6
|
+
* after a prior tool-use turn → continue (with the canonical nudge text);
|
|
7
|
+
* visible text → stop; prior turn already delivered visible text → stop;
|
|
8
|
+
* first model call with no prior turn → stop; provider refusal → continue
|
|
9
|
+
* (with the refusal-specific nudge text); refusal-but-recovered → stop.
|
|
10
|
+
* - The hook scopes its prior-turn signals to the current response cycle (the
|
|
11
|
+
* messages after the last genuine user prompt), so visible text from the
|
|
12
|
+
* inbound conversation does not suppress the nudge.
|
|
13
|
+
* - When the hook continues, it appends the nudge as a `user` message to
|
|
14
|
+
* `messages`.
|
|
15
|
+
* - End-to-end through `runHook` + the registry: registering the default
|
|
16
|
+
* plugin makes the hook fire, and a later-registered user hook chains after
|
|
17
|
+
* it and can read/override the decision.
|
|
18
|
+
*
|
|
19
|
+
* The loop's actual side-effects (retry-budget cap, history splice, log
|
|
20
|
+
* emission) live in `agent/loop.ts` and are covered by integration tests in
|
|
21
|
+
* `conversation-agent-loop.test.ts` / `agent-loop.test.ts`. This file isolates
|
|
22
|
+
* the hook.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
26
|
+
|
|
27
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
28
|
+
import type { PluginLogger, StopContext } from "../plugin-api/types.js";
|
|
29
|
+
import {
|
|
30
|
+
NUDGE_TEXT,
|
|
31
|
+
REFUSAL_NUDGE_TEXT,
|
|
32
|
+
} from "../plugins/defaults/empty-response/hooks/stop.js";
|
|
33
|
+
import stop from "../plugins/defaults/empty-response/hooks/stop.js";
|
|
34
|
+
import { defaultEmptyResponsePlugin } from "../plugins/defaults/empty-response/register.js";
|
|
35
|
+
import { runHook } from "../plugins/pipeline.js";
|
|
36
|
+
import {
|
|
37
|
+
registerPlugin,
|
|
38
|
+
resetPluginRegistryForTests,
|
|
39
|
+
} from "../plugins/registry.js";
|
|
40
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
41
|
+
|
|
42
|
+
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const noopLogger: PluginLogger = {
|
|
45
|
+
info: () => {},
|
|
46
|
+
warn: () => {},
|
|
47
|
+
error: () => {},
|
|
48
|
+
debug: () => {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const emptyTextBlock: ContentBlock = { type: "text", text: " " };
|
|
52
|
+
|
|
53
|
+
/** A prior assistant turn that issued a tool call but no visible text. */
|
|
54
|
+
const priorToolUseTurn: Message = {
|
|
55
|
+
role: "assistant",
|
|
56
|
+
content: [{ type: "tool_use", id: "tu_1", name: "read_file", input: {} }],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** A prior assistant turn that delivered visible text to the user. */
|
|
60
|
+
const priorVisibleTextTurn: Message = {
|
|
61
|
+
role: "assistant",
|
|
62
|
+
content: [{ type: "text", text: "here is what I found earlier" }],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** A genuine user prompt — the boundary that opens a new response cycle. */
|
|
66
|
+
function userPrompt(text: string): Message {
|
|
67
|
+
return { role: "user", content: [{ type: "text", text }] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function makeCtx(overrides: Partial<StopContext> = {}): StopContext {
|
|
71
|
+
return {
|
|
72
|
+
conversationId: "conv-stop",
|
|
73
|
+
messages: [],
|
|
74
|
+
responseContent: [],
|
|
75
|
+
stopReason: null,
|
|
76
|
+
decision: "stop",
|
|
77
|
+
logger: noopLogger,
|
|
78
|
+
...overrides,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Default decisions ───────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
describe("empty-response stop hook — default decisions", () => {
|
|
85
|
+
test("empty turn after a prior tool-use turn → continue with canonical nudge", async () => {
|
|
86
|
+
// GIVEN a run that already issued a tool call, then returned an empty
|
|
87
|
+
// (whitespace-only) assistant turn with no visible text.
|
|
88
|
+
const ctx = makeCtx({
|
|
89
|
+
messages: [priorToolUseTurn],
|
|
90
|
+
responseContent: [emptyTextBlock],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// WHEN the default stop hook runs.
|
|
94
|
+
await stop(ctx);
|
|
95
|
+
|
|
96
|
+
// THEN it asks the loop to continue and appends the canonical nudge.
|
|
97
|
+
expect(ctx.decision).toBe("continue");
|
|
98
|
+
expect(ctx.messages.at(-1)).toEqual({
|
|
99
|
+
role: "user",
|
|
100
|
+
content: [{ type: "text", text: NUDGE_TEXT }],
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("turn contains visible text → stop", async () => {
|
|
105
|
+
// GIVEN a turn that delivered visible text.
|
|
106
|
+
const ctx = makeCtx({
|
|
107
|
+
messages: [priorToolUseTurn],
|
|
108
|
+
responseContent: [{ type: "text", text: "here is a summary" }],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// WHEN the hook runs.
|
|
112
|
+
await stop(ctx);
|
|
113
|
+
|
|
114
|
+
// THEN the decision stays at stop and nothing is appended.
|
|
115
|
+
expect(ctx.decision).toBe("stop");
|
|
116
|
+
expect(ctx.messages).toEqual([priorToolUseTurn]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("prior turn already delivered visible text → stop", async () => {
|
|
120
|
+
// GIVEN the model said its piece in an earlier turn this run, then ended
|
|
121
|
+
// with a side-effect tool and returned empty. Nudging would force a
|
|
122
|
+
// verbatim re-send of text the user already saw.
|
|
123
|
+
const ctx = makeCtx({
|
|
124
|
+
messages: [userPrompt("do X"), priorVisibleTextTurn, priorToolUseTurn],
|
|
125
|
+
responseContent: [],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// WHEN the hook runs.
|
|
129
|
+
await stop(ctx);
|
|
130
|
+
|
|
131
|
+
// THEN the decision stays at stop.
|
|
132
|
+
expect(ctx.decision).toBe("stop");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("visible text before the last user prompt is ignored → continue", async () => {
|
|
136
|
+
// GIVEN the inbound conversation already contains an assistant turn with
|
|
137
|
+
// visible text, but it precedes this run's user prompt. The current cycle
|
|
138
|
+
// (after that prompt) holds only a tool-use turn, so the earlier text
|
|
139
|
+
// belongs to the prior conversation and must not suppress the nudge.
|
|
140
|
+
const ctx = makeCtx({
|
|
141
|
+
messages: [
|
|
142
|
+
priorVisibleTextTurn,
|
|
143
|
+
userPrompt("do the next thing"),
|
|
144
|
+
priorToolUseTurn,
|
|
145
|
+
],
|
|
146
|
+
responseContent: [],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// WHEN the hook runs.
|
|
150
|
+
await stop(ctx);
|
|
151
|
+
|
|
152
|
+
// THEN it continues — the cycle scope sees only the tool-use turn, not the
|
|
153
|
+
// inbound conversation's visible text.
|
|
154
|
+
expect(ctx.decision).toBe("continue");
|
|
155
|
+
expect(ctx.messages.at(-1)).toEqual({
|
|
156
|
+
role: "user",
|
|
157
|
+
content: [{ type: "text", text: NUDGE_TEXT }],
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("first model call with no prior turn → stop", async () => {
|
|
162
|
+
// GIVEN an empty first assistant response with no prior turn this run and
|
|
163
|
+
// no refusal — not the pattern the organic-empty-turn nudge guards against.
|
|
164
|
+
const ctx = makeCtx({ messages: [], responseContent: [] });
|
|
165
|
+
|
|
166
|
+
// WHEN the hook runs.
|
|
167
|
+
await stop(ctx);
|
|
168
|
+
|
|
169
|
+
// THEN the decision stays at stop.
|
|
170
|
+
expect(ctx.decision).toBe("stop");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ─── Refusal stop ──────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
test("refusal on the first call with no content → continue with refusal nudge", async () => {
|
|
176
|
+
// GIVEN the canonical failure mode: the provider's safety classifier zeros
|
|
177
|
+
// the response on the very first model call, returning `stopReason:
|
|
178
|
+
// "refusal"` and no visible text.
|
|
179
|
+
const ctx = makeCtx({
|
|
180
|
+
messages: [],
|
|
181
|
+
responseContent: [],
|
|
182
|
+
stopReason: "refusal",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// WHEN the hook runs.
|
|
186
|
+
await stop(ctx);
|
|
187
|
+
|
|
188
|
+
// THEN it continues with the refusal-specific nudge, even with no prior turn.
|
|
189
|
+
expect(ctx.decision).toBe("continue");
|
|
190
|
+
expect(ctx.messages.at(-1)).toEqual({
|
|
191
|
+
role: "user",
|
|
192
|
+
content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("refusal with a thinking-only block still continues", async () => {
|
|
197
|
+
// GIVEN a refusal whose only content is a thinking block — the user sees
|
|
198
|
+
// nothing.
|
|
199
|
+
const ctx = makeCtx({
|
|
200
|
+
messages: [],
|
|
201
|
+
responseContent: [
|
|
202
|
+
{ type: "thinking", thinking: "...", signature: "sig" },
|
|
203
|
+
],
|
|
204
|
+
stopReason: "refusal",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// WHEN the hook runs.
|
|
208
|
+
await stop(ctx);
|
|
209
|
+
|
|
210
|
+
// THEN it continues with the refusal-specific nudge.
|
|
211
|
+
expect(ctx.decision).toBe("continue");
|
|
212
|
+
expect(ctx.messages.at(-1)).toEqual({
|
|
213
|
+
role: "user",
|
|
214
|
+
content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("refusal but visible text present → stop (model recovered)", async () => {
|
|
219
|
+
// GIVEN a refusal that still delivered some visible text before refusing —
|
|
220
|
+
// the user has something to see.
|
|
221
|
+
const ctx = makeCtx({
|
|
222
|
+
responseContent: [{ type: "text", text: "partial answer" }],
|
|
223
|
+
stopReason: "refusal",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// WHEN the hook runs.
|
|
227
|
+
await stop(ctx);
|
|
228
|
+
|
|
229
|
+
// THEN the decision stays at stop.
|
|
230
|
+
expect(ctx.decision).toBe("stop");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("refusal beats the post-tool-empty nudge wording", async () => {
|
|
234
|
+
// GIVEN conditions that would trip both the refusal branch and the
|
|
235
|
+
// post-tool-empty branch.
|
|
236
|
+
const ctx = makeCtx({
|
|
237
|
+
messages: [priorToolUseTurn],
|
|
238
|
+
responseContent: [],
|
|
239
|
+
stopReason: "refusal",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// WHEN the hook runs.
|
|
243
|
+
await stop(ctx);
|
|
244
|
+
|
|
245
|
+
// THEN refusal wins because its wording is more accurate.
|
|
246
|
+
expect(ctx.decision).toBe("continue");
|
|
247
|
+
expect(ctx.messages.at(-1)).toEqual({
|
|
248
|
+
role: "user",
|
|
249
|
+
content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ─── Via runHook + registry ──────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
describe("empty-response stop hook — via runHook", () => {
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
resetPluginRegistryForTests();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("registering the default plugin makes the hook continue on empty-after-tools", async () => {
|
|
262
|
+
// GIVEN the default empty-response plugin is registered.
|
|
263
|
+
registerPlugin(defaultEmptyResponsePlugin);
|
|
264
|
+
|
|
265
|
+
// WHEN the stop chain runs over an empty-after-tools context.
|
|
266
|
+
const result = await runHook<StopContext>(
|
|
267
|
+
HOOKS.STOP,
|
|
268
|
+
makeCtx({ messages: [priorToolUseTurn], responseContent: [] }),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// THEN the chain settles on continue with the canonical nudge appended.
|
|
272
|
+
expect(result.decision).toBe("continue");
|
|
273
|
+
expect(result.messages.at(-1)).toEqual({
|
|
274
|
+
role: "user",
|
|
275
|
+
content: [{ type: "text", text: NUDGE_TEXT }],
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("a later-registered user hook can override the default decision", async () => {
|
|
280
|
+
// GIVEN the default plugin registers first, then a user plugin whose hook
|
|
281
|
+
// observes the default's decision and forces a stop.
|
|
282
|
+
let observedDecision: string | null = null;
|
|
283
|
+
registerPlugin(defaultEmptyResponsePlugin);
|
|
284
|
+
registerPlugin({
|
|
285
|
+
manifest: { name: "force-stop", version: "0.0.1" },
|
|
286
|
+
hooks: {
|
|
287
|
+
stop: async (ctx: StopContext) => {
|
|
288
|
+
observedDecision = ctx.decision;
|
|
289
|
+
ctx.decision = "stop";
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// WHEN the chain runs over an empty-after-tools context.
|
|
295
|
+
const result = await runHook<StopContext>(
|
|
296
|
+
HOOKS.STOP,
|
|
297
|
+
makeCtx({ messages: [priorToolUseTurn], responseContent: [] }),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// THEN the user hook saw the default's continue, and its override wins.
|
|
301
|
+
expect(observedDecision as string | null).toBe("continue");
|
|
302
|
+
expect(result.decision).toBe("stop");
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// Mirrors `src/config/feature-flag-cache.ts`. Duplicated by design — see
|
|
20
20
|
// the "No source-module imports" section above.
|
|
21
21
|
type FlagSlot = {
|
|
22
|
-
overrides: Record<string, boolean> | null;
|
|
22
|
+
overrides: Record<string, boolean | string> | null;
|
|
23
23
|
fromGateway: boolean;
|
|
24
24
|
};
|
|
25
25
|
|
|
@@ -45,7 +45,7 @@ function flagSlot(): FlagSlot {
|
|
|
45
45
|
* `clearFeatureFlagOverridesCache()` from `assistant-feature-flags.ts`.
|
|
46
46
|
*/
|
|
47
47
|
export function setOverridesForTesting(
|
|
48
|
-
overrides: Record<string, boolean>,
|
|
48
|
+
overrides: Record<string, boolean | string>,
|
|
49
49
|
): void {
|
|
50
50
|
const s = flagSlot();
|
|
51
51
|
s.overrides = { ...overrides };
|
|
@@ -260,3 +260,66 @@ describe("file_write tool PKB re-index hook", () => {
|
|
|
260
260
|
expect(existsSync(join(workingDir, "pkb", "oops.md"))).toBe(true);
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
|
+
|
|
264
|
+
describe("file_write artifact-HTML guard", () => {
|
|
265
|
+
test("rejects a self-contained interactive HTML visualization", async () => {
|
|
266
|
+
const dir = makeTempDir();
|
|
267
|
+
const html =
|
|
268
|
+
"<!doctype html><html><head><title>Food Market</title></head>" +
|
|
269
|
+
"<body><canvas id='c'></canvas><script>" +
|
|
270
|
+
("const data=[{x:1,y:2}];").padEnd(4000, "/") +
|
|
271
|
+
"new Chart(document.getElementById('c'), {data});</script></body></html>";
|
|
272
|
+
|
|
273
|
+
const result = await fileWriteTool.execute(
|
|
274
|
+
{ path: "food-market-stats.html", content: html },
|
|
275
|
+
makeContext(dir),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(result.isError).toBe(true);
|
|
279
|
+
expect(result.content).toContain('skill: "app-builder"');
|
|
280
|
+
expect(existsSync(join(dir, "food-market-stats.html"))).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("allows app-builder's thin shell index.html (external module script)", async () => {
|
|
284
|
+
const dir = makeTempDir();
|
|
285
|
+
const shell =
|
|
286
|
+
"<!doctype html><html><head>" +
|
|
287
|
+
"<link rel='stylesheet' href='/src/styles.css'>".padEnd(3200, " ") +
|
|
288
|
+
"</head><body><div id='app'></div>" +
|
|
289
|
+
"<script type='module' src='/src/main.tsx'></script></body></html>";
|
|
290
|
+
|
|
291
|
+
const result = await fileWriteTool.execute(
|
|
292
|
+
{ path: "src/index.html", content: shell },
|
|
293
|
+
makeContext(dir),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
expect(result.isError).toBe(false);
|
|
297
|
+
expect(existsSync(join(dir, "src", "index.html"))).toBe(true);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("allows a small/static HTML snippet", async () => {
|
|
301
|
+
const dir = makeTempDir();
|
|
302
|
+
const result = await fileWriteTool.execute(
|
|
303
|
+
{ path: "note.html", content: "<html><body><h1>Hi</h1></body></html>" },
|
|
304
|
+
makeContext(dir),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
expect(result.isError).toBe(false);
|
|
308
|
+
expect(existsSync(join(dir, "note.html"))).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("allows a non-HTML file even with inline script-like content", async () => {
|
|
312
|
+
const dir = makeTempDir();
|
|
313
|
+
const result = await fileWriteTool.execute(
|
|
314
|
+
{
|
|
315
|
+
path: "notes.md",
|
|
316
|
+
content:
|
|
317
|
+
"<script>" + "x".padEnd(2000, "x") + "</script>" + "\n# notes\n",
|
|
318
|
+
},
|
|
319
|
+
makeContext(dir),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
expect(result.isError).toBe(false);
|
|
323
|
+
expect(existsSync(join(dir, "notes.md"))).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -27,6 +27,7 @@ const ALLOWLIST = new Set([
|
|
|
27
27
|
"clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
|
|
28
28
|
"clients/macos/vellum-assistant/App/AppDelegate.swift",
|
|
29
29
|
"clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
|
|
30
|
+
"apps/macos/src/main/bundle-flow.ts", // Electron main calls the local gateway (gatewayPort) with a Guardian token to scan bundles
|
|
30
31
|
".claude/skills/update/SKILL.md", // daemon health check script
|
|
31
32
|
|
|
32
33
|
// --- Test fixtures that poll the daemon directly (gateway may require auth) ---
|
|
@@ -84,12 +85,21 @@ function isGatewayInternal(filePath: string): boolean {
|
|
|
84
85
|
return filePath.startsWith("gateway/");
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/** Additional files allowed for the interpolated-port check only (use gateway port, not runtime). */
|
|
89
|
+
const INTERPOLATED_PORT_ALLOWLIST = new Set([
|
|
90
|
+
"apps/macos/src/main/bundle-flow.ts",
|
|
91
|
+
]);
|
|
92
|
+
|
|
87
93
|
/** Shared violation filter: exempt test files, gateway internals, and allowlisted paths. */
|
|
88
|
-
function filterViolations(
|
|
94
|
+
function filterViolations(
|
|
95
|
+
files: string[],
|
|
96
|
+
extraAllowlist?: Set<string>,
|
|
97
|
+
): string[] {
|
|
89
98
|
return files.filter((f) => {
|
|
90
99
|
if (isTestFile(f)) return false;
|
|
91
100
|
if (isGatewayInternal(f)) return false;
|
|
92
101
|
if (ALLOWLIST.has(f)) return false;
|
|
102
|
+
if (extraAllowlist?.has(f)) return false;
|
|
93
103
|
return true;
|
|
94
104
|
});
|
|
95
105
|
}
|
|
@@ -181,7 +191,7 @@ describe("gateway-only API consumption guard", () => {
|
|
|
181
191
|
}
|
|
182
192
|
|
|
183
193
|
const files = grepOutput.split("\n").filter((f) => f.length > 0);
|
|
184
|
-
const violations = filterViolations(files);
|
|
194
|
+
const violations = filterViolations(files, INTERPOLATED_PORT_ALLOWLIST);
|
|
185
195
|
|
|
186
196
|
if (violations.length > 0) {
|
|
187
197
|
const message = [
|
|
@@ -287,6 +287,19 @@ describe("mapGeminiError", () => {
|
|
|
287
287
|
expect(msg).toContain("Rate limit");
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
+
test("maps 402 status to a non-retryable out-of-credits message", () => {
|
|
291
|
+
const msg = mapGeminiError(new FakeApiError(402, "payment required"));
|
|
292
|
+
expect(msg).toContain("out of credits");
|
|
293
|
+
expect(msg).not.toContain("Please try again");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("maps a managed-proxy 402 failure (plain Error) to out-of-credits message", () => {
|
|
297
|
+
const msg = mapGeminiError(
|
|
298
|
+
new Error("Managed proxy request failed (402): insufficient credits"),
|
|
299
|
+
);
|
|
300
|
+
expect(msg).toContain("out of credits");
|
|
301
|
+
});
|
|
302
|
+
|
|
290
303
|
test("maps 500 status to server error message", () => {
|
|
291
304
|
const msg = mapGeminiError(new FakeApiError(500, "internal"));
|
|
292
305
|
expect(msg).toContain("temporarily unavailable");
|
|
@@ -20,7 +20,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
const _conversationMocks = new Map<string, unknown>();
|
|
23
|
-
mock.module("../daemon/conversation-
|
|
23
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
24
24
|
findConversation: (id: string) => _conversationMocks.get(id),
|
|
25
25
|
}));
|
|
26
26
|
|
|
@@ -21,7 +21,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
21
21
|
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
22
22
|
|
|
23
23
|
const _conversationMocks = new Map<string, unknown>();
|
|
24
|
-
mock.module("../daemon/conversation-
|
|
24
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
25
25
|
findConversation: (id: string) => _conversationMocks.get(id),
|
|
26
26
|
}));
|
|
27
27
|
|
|
@@ -51,9 +51,7 @@ import {
|
|
|
51
51
|
routeGuardianReply,
|
|
52
52
|
} from "../runtime/guardian-reply-router.js";
|
|
53
53
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
54
|
-
import {
|
|
55
|
-
listGuardianDecisionPrompts,
|
|
56
|
-
} from "../runtime/routes/guardian-action-routes.js";
|
|
54
|
+
import { listGuardianDecisionPrompts } from "../runtime/routes/guardian-action-routes.js";
|
|
57
55
|
|
|
58
56
|
initializeDb();
|
|
59
57
|
|
|
@@ -143,7 +143,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
143
143
|
import {
|
|
144
144
|
clearConversations,
|
|
145
145
|
setConversation,
|
|
146
|
-
} from "../daemon/conversation-
|
|
146
|
+
} from "../daemon/conversation-registry.js";
|
|
147
147
|
import { handleConfirmationResponse } from "../daemon/handlers/conversations.js";
|
|
148
148
|
|
|
149
149
|
describe("handleConfirmationResponse canonical status sync", () => {
|
|
@@ -56,6 +56,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
|
|
|
56
56
|
markStaleRunsAsMissed: mockMarkStaleRunsAsMissed,
|
|
57
57
|
markStaleRunningAsError: mockMarkStaleRunningAsError,
|
|
58
58
|
countCompletedHeartbeatRuns: mock(() => 10),
|
|
59
|
+
countCompletedRunsToday: mock(() => 0),
|
|
59
60
|
countRecentConsecutiveRuns: mock(() => 0),
|
|
60
61
|
}));
|
|
61
62
|
|
|
@@ -32,6 +32,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
|
|
|
32
32
|
markStaleRunningAsError: mockMarkStaleRunningAsError,
|
|
33
33
|
listHeartbeatRuns: mockListHeartbeatRuns,
|
|
34
34
|
countCompletedHeartbeatRuns: mockCountCompletedHeartbeatRuns,
|
|
35
|
+
countCompletedRunsToday: mock(() => 0),
|
|
35
36
|
countRecentConsecutiveRuns: mock(() => 0),
|
|
36
37
|
}));
|
|
37
38
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Type-only import: this binds the mock to the real `Provider` contract so it
|
|
2
|
+
// can be passed to a live `AgentLoop` without casts. It must stay `import type`
|
|
3
|
+
// — a value import would pull `providers/types.ts`'s runtime exports into this
|
|
4
|
+
// shared helper, which the test-machinery isolation rule forbids.
|
|
5
|
+
import type {
|
|
6
|
+
Message,
|
|
7
|
+
Provider,
|
|
8
|
+
ProviderResponse,
|
|
9
|
+
SendMessageOptions,
|
|
10
|
+
ToolDefinition,
|
|
11
|
+
} from "../../providers/types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Records the arguments of a single `provider.sendMessage` invocation so tests
|
|
15
|
+
* can assert on what the agent loop sent (messages, tools, system prompt, the
|
|
16
|
+
* resolved options bag).
|
|
17
|
+
*/
|
|
18
|
+
export interface RecordedProviderCall {
|
|
19
|
+
messages: Message[];
|
|
20
|
+
tools?: ToolDefinition[];
|
|
21
|
+
systemPrompt?: string;
|
|
22
|
+
options?: SendMessageOptions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One scripted provider turn. A `ProviderResponse` is returned normally; an
|
|
27
|
+
* `Error` is thrown to simulate a provider HTTP rejection (e.g. a
|
|
28
|
+
* context-too-large error the orchestrator must recover from).
|
|
29
|
+
*/
|
|
30
|
+
export type ScriptedResponse = ProviderResponse | Error;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A mock provider that returns pre-configured responses in sequence.
|
|
34
|
+
*
|
|
35
|
+
* Drives the real {@link import("../../agent/loop.js").AgentLoop} by mocking
|
|
36
|
+
* only the provider HTTP boundary: each call returns the next scripted
|
|
37
|
+
* `ProviderResponse` (the last response repeats once the script is exhausted)
|
|
38
|
+
* and replays its text blocks as `text_delta` events so the loop streams
|
|
39
|
+
* exactly as it would against a live provider. A scripted `Error` entry is
|
|
40
|
+
* thrown instead of returned, so a rejection can be sequenced before a
|
|
41
|
+
* recovery response.
|
|
42
|
+
*/
|
|
43
|
+
export function createMockProvider(
|
|
44
|
+
responses: ScriptedResponse[],
|
|
45
|
+
name = "mock",
|
|
46
|
+
): {
|
|
47
|
+
provider: Provider;
|
|
48
|
+
calls: RecordedProviderCall[];
|
|
49
|
+
} {
|
|
50
|
+
const calls: RecordedProviderCall[] = [];
|
|
51
|
+
let callIndex = 0;
|
|
52
|
+
|
|
53
|
+
const provider: Provider = {
|
|
54
|
+
name,
|
|
55
|
+
async sendMessage(
|
|
56
|
+
messages: Message[],
|
|
57
|
+
options?: SendMessageOptions,
|
|
58
|
+
): Promise<ProviderResponse> {
|
|
59
|
+
calls.push({
|
|
60
|
+
messages: [...messages],
|
|
61
|
+
tools: options?.tools,
|
|
62
|
+
systemPrompt: options?.systemPrompt,
|
|
63
|
+
options,
|
|
64
|
+
});
|
|
65
|
+
const scripted = responses[callIndex] ?? responses[responses.length - 1];
|
|
66
|
+
callIndex++;
|
|
67
|
+
|
|
68
|
+
if (scripted instanceof Error) {
|
|
69
|
+
throw scripted;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Replay streaming deltas for text blocks, mirroring a live provider.
|
|
73
|
+
if (options?.onEvent) {
|
|
74
|
+
for (const block of scripted.content) {
|
|
75
|
+
if (block.type === "text") {
|
|
76
|
+
options.onEvent({ type: "text_delta", text: block.text });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return scripted;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return { provider, calls };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A scripted assistant turn that ends with a plain text response. */
|
|
89
|
+
export function textResponse(text: string): ProviderResponse {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text }],
|
|
92
|
+
model: "mock-model",
|
|
93
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
94
|
+
stopReason: "end_turn",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A scripted assistant turn that invokes a single tool. */
|
|
99
|
+
export function toolUseResponse(
|
|
100
|
+
id: string,
|
|
101
|
+
name: string,
|
|
102
|
+
input: Record<string, unknown>,
|
|
103
|
+
): ProviderResponse {
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "tool_use", id, name, input }],
|
|
106
|
+
model: "mock-model",
|
|
107
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
108
|
+
stopReason: "tool_use",
|
|
109
|
+
};
|
|
110
|
+
}
|