@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,39 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* User plugin loader — discovers plugins under `<workspaceDir>/plugins/*`
|
|
3
|
-
*
|
|
2
|
+
* User plugin loader — discovers plugins under `<workspaceDir>/plugins/*` and
|
|
3
|
+
* registers each one.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
* convention.
|
|
12
|
-
*
|
|
13
|
-
* **Legacy path** (`register.{ts,js}` present): the file is dynamic-imported
|
|
14
|
-
* and expected to call {@link registerPlugin} at import time as a side
|
|
15
|
-
* effect, populating the registry before {@link bootstrapPlugins} runs.
|
|
16
|
-
*
|
|
17
|
-
* The legacy path takes precedence when a directory contains both
|
|
18
|
-
* `package.json` and `register.{ts,js}` — a migration-friendly default
|
|
19
|
-
* that keeps existing plugins (including the in-repo `examples/plugins/echo`
|
|
20
|
-
* reference) working unchanged while we iterate the external-plugin
|
|
21
|
-
* convention. A directory matching neither path is skipped silently.
|
|
5
|
+
* A plugin directory is recognized by a `package.json` manifest. The harness
|
|
6
|
+
* delegates to {@link loadExternalPlugin}, which builds a `Plugin` from the
|
|
7
|
+
* directory's interface dirs (`hooks/`, `tools/`) and registers it directly.
|
|
8
|
+
* The full convention lives in
|
|
9
|
+
* `assistant/src/plugins/external-plugin-loader.ts`. A directory with no
|
|
10
|
+
* `package.json` is skipped silently.
|
|
22
11
|
*
|
|
23
12
|
* The loader deliberately:
|
|
24
13
|
*
|
|
25
14
|
* - Uses `getWorkspaceDir()` so each instance loads its own plugin set
|
|
26
15
|
* when `VELLUM_WORKSPACE_DIR` is set.
|
|
27
|
-
* - Prefers `.js` over `.ts` per surface file (compiled-binary semantics)
|
|
28
|
-
*
|
|
29
|
-
* legacy path picks between `register.js` and `register.ts`.
|
|
16
|
+
* - Prefers `.js` over `.ts` per surface file (compiled-binary semantics);
|
|
17
|
+
* the rule is applied by {@link loadExternalPlugin}.
|
|
30
18
|
* - Treats any error from a plugin load as a per-plugin isolation
|
|
31
|
-
* boundary. {@link loadExternalPlugin} owns its own try/catch/timeout
|
|
32
|
-
*
|
|
33
|
-
* the daemon.
|
|
34
|
-
* - Bounds each plugin load with {@link USER_PLUGIN_IMPORT_TIMEOUT_MS}
|
|
35
|
-
* so a plugin whose top-level `await` hangs or whose module evaluation
|
|
36
|
-
* never resolves cannot stall daemon startup.
|
|
19
|
+
* boundary. {@link loadExternalPlugin} owns its own try/catch/timeout, so
|
|
20
|
+
* one bad user plugin must not crash the daemon.
|
|
37
21
|
*
|
|
38
22
|
* Call order relative to the rest of the plugin system:
|
|
39
23
|
*
|
|
@@ -46,7 +30,6 @@
|
|
|
46
30
|
|
|
47
31
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
48
32
|
import { join } from "node:path";
|
|
49
|
-
import { pathToFileURL } from "node:url";
|
|
50
33
|
|
|
51
34
|
import { getLogger } from "../util/logger.js";
|
|
52
35
|
import { getWorkspacePluginsDir } from "../util/platform.js";
|
|
@@ -57,27 +40,24 @@ import { closeRegistration } from "./registry.js";
|
|
|
57
40
|
const log = getLogger("user-plugin-loader");
|
|
58
41
|
|
|
59
42
|
/**
|
|
60
|
-
* Upper bound on how long a single user plugin's
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* same way thrown-error plugins do.
|
|
43
|
+
* Upper bound on how long a single user plugin's load may take. A plugin with
|
|
44
|
+
* a hanging top-level `await` (or a never-resolving module evaluation) would
|
|
45
|
+
* otherwise block daemon startup indefinitely. Ten seconds is generous
|
|
46
|
+
* relative to a typical plugin load (milliseconds) and matches the per-plugin
|
|
47
|
+
* isolation contract: slow plugins get skipped the same way thrown-error
|
|
48
|
+
* plugins do. Enforced by {@link loadExternalPlugin}.
|
|
67
49
|
*/
|
|
68
50
|
const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
|
|
69
51
|
|
|
70
52
|
/**
|
|
71
|
-
* Scan `getWorkspaceDir()/plugins/` for subdirectories
|
|
72
|
-
*
|
|
73
|
-
* `register.{ts,js}`) or the legacy side-effect importer (if
|
|
74
|
-
* `register.{ts,js}` is present).
|
|
53
|
+
* Scan `getWorkspaceDir()/plugins/` for subdirectories and dispatch each one
|
|
54
|
+
* that carries a `package.json` to {@link loadExternalPlugin}.
|
|
75
55
|
*
|
|
76
56
|
* Invariants:
|
|
77
57
|
*
|
|
78
58
|
* - No-ops when `getWorkspaceDir()/plugins/` does not exist — a clean install with
|
|
79
59
|
* zero user plugins must not generate errors.
|
|
80
|
-
* - Per-plugin isolation: a failing
|
|
60
|
+
* - Per-plugin isolation: a failing load is logged and skipped. The
|
|
81
61
|
* function resolves normally even when every plugin fails to load.
|
|
82
62
|
* - Does not return plugin instances. The registry is the single source of
|
|
83
63
|
* truth for who got registered, and the caller inspects it directly.
|
|
@@ -86,7 +66,7 @@ const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
|
|
|
86
66
|
*
|
|
87
67
|
* - Must be invoked exactly once during daemon startup, before
|
|
88
68
|
* `bootstrapPlugins()` walks the registry.
|
|
89
|
-
* - Holds no locks during the
|
|
69
|
+
* - Holds no locks during the load — bun's dynamic `import()` resolution
|
|
90
70
|
* is concurrency-safe.
|
|
91
71
|
*/
|
|
92
72
|
export async function loadUserPlugins(
|
|
@@ -151,96 +131,19 @@ export async function loadUserPlugins(
|
|
|
151
131
|
}
|
|
152
132
|
if (!stats.isDirectory()) continue;
|
|
153
133
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Migration-friendly: any plugin in the wild today that happens to
|
|
157
|
-
// ship a `package.json` keeps loading via its existing register entry.
|
|
158
|
-
// The external-plugin path only fires when the directory is
|
|
159
|
-
// unambiguously the new convention.
|
|
160
|
-
const jsPath = join(pluginDir, "register.js");
|
|
161
|
-
const tsPath = join(pluginDir, "register.ts");
|
|
162
|
-
let registerPath: string | undefined;
|
|
163
|
-
if (existsSync(jsPath)) {
|
|
164
|
-
registerPath = jsPath;
|
|
165
|
-
} else if (existsSync(tsPath)) {
|
|
166
|
-
registerPath = tsPath;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (registerPath === undefined) {
|
|
170
|
-
// External plugin framework path. `loadExternalPlugin` owns its own
|
|
171
|
-
// try/catch + timeout, so a `continue` is the entire branch here.
|
|
172
|
-
if (existsSync(join(pluginDir, "package.json"))) {
|
|
173
|
-
await loadExternalPlugin(pluginDir, { importTimeoutMs });
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
log.debug(
|
|
177
|
-
{ pluginDir },
|
|
178
|
-
"loadUserPlugins: no register.{ts,js} or package.json — skipping",
|
|
179
|
-
);
|
|
134
|
+
if (!existsSync(join(pluginDir, "package.json"))) {
|
|
135
|
+
log.debug({ pluginDir }, "loadUserPlugins: no package.json — skipping");
|
|
180
136
|
continue;
|
|
181
137
|
}
|
|
182
138
|
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
const moduleUrl = pathToFileURL(registerPath).href;
|
|
187
|
-
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
188
|
-
try {
|
|
189
|
-
// Race the import against a timeout so a plugin with a hanging top-level
|
|
190
|
-
// await or never-resolving module evaluation cannot stall daemon startup.
|
|
191
|
-
// The per-plugin try/catch already handles thrown errors; this extends
|
|
192
|
-
// the isolation boundary to cover hung promises as well.
|
|
193
|
-
const timeoutSentinel = Symbol("user-plugin-import-timeout");
|
|
194
|
-
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
195
|
-
timeoutHandle = setTimeout(
|
|
196
|
-
() => resolve(timeoutSentinel),
|
|
197
|
-
importTimeoutMs,
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
// Retain the import promise so we can attach a terminal `.catch` on the
|
|
201
|
-
// timeout branch. `Promise.race` does not cancel the losing promise —
|
|
202
|
-
// the module evaluation keeps running in the background even after we
|
|
203
|
-
// stop awaiting it, and if it eventually throws (either from the
|
|
204
|
-
// module body or from the late `registerPlugin()` hitting a closed
|
|
205
|
-
// registry) an unhandled rejection would crash the daemon.
|
|
206
|
-
const importPromise = import(moduleUrl);
|
|
207
|
-
const result = await Promise.race([importPromise, timeoutPromise]);
|
|
208
|
-
if (result === timeoutSentinel) {
|
|
209
|
-
importPromise.catch(() => {
|
|
210
|
-
// Abandoned import completed (or threw) after the timeout. The
|
|
211
|
-
// closed-registration latch in registry.ts guarantees any late
|
|
212
|
-
// `registerPlugin()` call is rejected, so swallowing the outcome
|
|
213
|
-
// here is the safe default.
|
|
214
|
-
});
|
|
215
|
-
log.warn(
|
|
216
|
-
{ pluginDir, registerPath, timeoutMs: importTimeoutMs },
|
|
217
|
-
`Timed out loading user plugin ${pluginDir} after ${importTimeoutMs}ms — skipping`,
|
|
218
|
-
);
|
|
219
|
-
} else {
|
|
220
|
-
log.info(
|
|
221
|
-
{ pluginDir, registerPath },
|
|
222
|
-
"loaded user plugin (side-effect import completed)",
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
} catch (err) {
|
|
226
|
-
// One plugin's failure must never prevent other plugins from loading
|
|
227
|
-
// or crash the daemon. Log with the directory name so operators can
|
|
228
|
-
// find the broken plugin quickly.
|
|
229
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
230
|
-
log.error(
|
|
231
|
-
{ err, pluginDir },
|
|
232
|
-
`Failed to load user plugin ${pluginDir}: ${message}`,
|
|
233
|
-
);
|
|
234
|
-
} finally {
|
|
235
|
-
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
236
|
-
}
|
|
139
|
+
// `loadExternalPlugin` owns its own try/catch + timeout, so a bare
|
|
140
|
+
// `await` is the entire branch here.
|
|
141
|
+
await loadExternalPlugin(pluginDir, { importTimeoutMs });
|
|
237
142
|
}
|
|
238
143
|
|
|
239
144
|
// Close the registration window once every candidate plugin has been
|
|
240
|
-
// awaited (or timed out). The per-plugin try/catch
|
|
241
|
-
// escapes the loop, so this line
|
|
242
|
-
//
|
|
243
|
-
// preserving the `bootstrapPlugins()` invariant that the registry is
|
|
244
|
-
// fully populated before it is walked.
|
|
145
|
+
// awaited (or timed out). The per-plugin try/catch inside
|
|
146
|
+
// `loadExternalPlugin` guarantees no throw escapes the loop, so this line
|
|
147
|
+
// always runs and `bootstrapPlugins()` sees a fully populated registry.
|
|
245
148
|
closeRegistration();
|
|
246
149
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
10
|
-
import { findConversation } from "../daemon/conversation-
|
|
10
|
+
import { findConversation } from "../daemon/conversation-registry.js";
|
|
11
11
|
import {
|
|
12
12
|
conversationMessagesSyncTag,
|
|
13
13
|
SYNC_TAGS,
|
|
@@ -24,7 +24,7 @@ async function waitForIdle(conversationId: string): Promise<boolean> {
|
|
|
24
24
|
const start = Date.now();
|
|
25
25
|
while (Date.now() - start < IDLE_TIMEOUT_MS) {
|
|
26
26
|
const conv = findConversation(conversationId);
|
|
27
|
-
if (!conv || !conv.
|
|
27
|
+
if (!conv || !conv.isProcessing()) return true;
|
|
28
28
|
await new Promise((resolve) => setTimeout(resolve, IDLE_POLL_MS));
|
|
29
29
|
}
|
|
30
30
|
return false;
|
|
@@ -36,7 +36,7 @@ export async function injectAuxAssistantMessage(params: {
|
|
|
36
36
|
broadcastMessage: BroadcastFn;
|
|
37
37
|
}): Promise<void> {
|
|
38
38
|
const conv = findConversation(params.conversationId);
|
|
39
|
-
if (conv?.
|
|
39
|
+
if (conv?.isProcessing()) {
|
|
40
40
|
const reachedIdle = await waitForIdle(params.conversationId);
|
|
41
41
|
if (!reachedIdle) {
|
|
42
42
|
log.warn(
|
|
@@ -54,7 +54,7 @@ export async function injectAuxAssistantMessage(params: {
|
|
|
54
54
|
);
|
|
55
55
|
|
|
56
56
|
const current = findConversation(params.conversationId);
|
|
57
|
-
if (current && !current.
|
|
57
|
+
if (current && !current.isProcessing()) {
|
|
58
58
|
current.getMessages().push(createAssistantMessage(params.text));
|
|
59
59
|
|
|
60
60
|
params.broadcastMessage({
|
|
@@ -190,13 +190,13 @@ mock.module("../runtime/sync/resource-sync-events.js", () => ({
|
|
|
190
190
|
|
|
191
191
|
// findConversation mock
|
|
192
192
|
type MockConversation = {
|
|
193
|
-
|
|
193
|
+
isProcessing(): boolean;
|
|
194
194
|
messages: unknown[];
|
|
195
195
|
getMessages: () => unknown[];
|
|
196
196
|
};
|
|
197
197
|
let mockConversations: Map<string, MockConversation> = new Map();
|
|
198
198
|
|
|
199
|
-
mock.module("../daemon/conversation-
|
|
199
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
200
200
|
findConversation: (id: string) => mockConversations.get(id),
|
|
201
201
|
}));
|
|
202
202
|
|
|
@@ -426,7 +426,7 @@ describe("runProactiveArtifactJob", () => {
|
|
|
426
426
|
// Set up an idle conversation so injection works fully
|
|
427
427
|
const convMessages: unknown[] = [];
|
|
428
428
|
mockConversations.set("conv-1", {
|
|
429
|
-
|
|
429
|
+
isProcessing: () => false,
|
|
430
430
|
messages: convMessages,
|
|
431
431
|
getMessages: () => convMessages,
|
|
432
432
|
});
|
|
@@ -510,7 +510,7 @@ describe("runProactiveArtifactJob", () => {
|
|
|
510
510
|
"MESSAGE: I created a monthly budget guide tailored to your needs.";
|
|
511
511
|
|
|
512
512
|
mockConversations.set("conv-1", {
|
|
513
|
-
|
|
513
|
+
isProcessing: () => false,
|
|
514
514
|
messages: [],
|
|
515
515
|
getMessages: () => [],
|
|
516
516
|
});
|
|
@@ -646,7 +646,7 @@ describe("runProactiveArtifactJob", () => {
|
|
|
646
646
|
];
|
|
647
647
|
|
|
648
648
|
mockConversations.set("conv-1", {
|
|
649
|
-
|
|
649
|
+
isProcessing: () => false,
|
|
650
650
|
messages: [],
|
|
651
651
|
getMessages: () => [],
|
|
652
652
|
});
|
|
@@ -710,7 +710,7 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
710
710
|
test("idle conversation: persists with skipIndexing, pushes to getMessages(), broadcasts delta + complete(aux) + list sync", async () => {
|
|
711
711
|
const messages: unknown[] = [];
|
|
712
712
|
mockConversations.set("conv-inject-1", {
|
|
713
|
-
|
|
713
|
+
isProcessing: () => false,
|
|
714
714
|
messages,
|
|
715
715
|
getMessages: () => messages,
|
|
716
716
|
});
|
|
@@ -767,12 +767,7 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
767
767
|
const messages: unknown[] = [];
|
|
768
768
|
let processingFlag = true;
|
|
769
769
|
const conv: MockConversation = {
|
|
770
|
-
|
|
771
|
-
return processingFlag;
|
|
772
|
-
},
|
|
773
|
-
set processing(v: boolean) {
|
|
774
|
-
processingFlag = v;
|
|
775
|
-
},
|
|
770
|
+
isProcessing: () => processingFlag,
|
|
776
771
|
messages,
|
|
777
772
|
getMessages: () => messages,
|
|
778
773
|
};
|
|
@@ -801,7 +796,7 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
801
796
|
const messages: unknown[] = [];
|
|
802
797
|
// Conversation stays processing permanently — never becomes idle
|
|
803
798
|
const conv: MockConversation = {
|
|
804
|
-
|
|
799
|
+
isProcessing: () => true,
|
|
805
800
|
messages,
|
|
806
801
|
getMessages: () => messages,
|
|
807
802
|
};
|
|
@@ -114,3 +114,45 @@ describe("maybeReseedBootstrap — content-automation template", () => {
|
|
|
114
114
|
expect(content).toContain("VOICE.md");
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
|
+
|
|
118
|
+
describe("maybeReseedBootstrap — activation rail template", () => {
|
|
119
|
+
const templatesDir = join(import.meta.dirname!, "..", "templates");
|
|
120
|
+
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
123
|
+
copyFileSync(
|
|
124
|
+
join(templatesDir, "BOOTSTRAP.md"),
|
|
125
|
+
join(TEST_DIR, "BOOTSTRAP.md"),
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("replaces generic bootstrap with the activation rail template", () => {
|
|
130
|
+
maybeReseedBootstrap("BOOTSTRAP-ACTIVATION-RAIL.md");
|
|
131
|
+
const content = readFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "utf-8");
|
|
132
|
+
|
|
133
|
+
expect(content).toContain("BOOTSTRAP — Activation Rail");
|
|
134
|
+
expect(content).toContain("People don't read");
|
|
135
|
+
expect(content).toContain("Speed wins");
|
|
136
|
+
|
|
137
|
+
// Propose: anti-speculation boundary on what "unstated" means.
|
|
138
|
+
expect(content).toContain("status word");
|
|
139
|
+
expect(content).toContain("don't say it");
|
|
140
|
+
|
|
141
|
+
// Propose: infer-first framing — recommendation bound to the click.
|
|
142
|
+
expect(content).toContain("You didn't say this");
|
|
143
|
+
expect(content).toContain("the recommendation IS the click");
|
|
144
|
+
|
|
145
|
+
// Propose: a surviving extract-and-offer mechanic.
|
|
146
|
+
expect(content).toContain("clickable component, strongest first");
|
|
147
|
+
|
|
148
|
+
// Propose: the extract-shape vs infer-shape example block.
|
|
149
|
+
expect(content).toContain("extract-shape");
|
|
150
|
+
expect(content).toContain("infer-shape");
|
|
151
|
+
|
|
152
|
+
// Port: prompt-writing guidance (JARVIS-1124).
|
|
153
|
+
expect(content).toContain("portable context brief, not a self-summary");
|
|
154
|
+
expect(content).toContain("load-bearing work in the next month");
|
|
155
|
+
expect(content).toContain("what to help with first");
|
|
156
|
+
expect(content).toContain("another tool or collaborator");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
_Replaces BOOTSTRAP.md for users in cohort experiment-activation-flow-2026-06-03._ _Same delete-on-wrap lifecycle as BOOTSTRAP.md._
|
|
2
|
+
|
|
3
|
+
# BOOTSTRAP — Activation Rail
|
|
4
|
+
|
|
5
|
+
The user just finished pre-chat. You know their name and vibe; maybe their Google. Your job in this conversation is to get them to a real first-run. Something they actually use, not a demo.
|
|
6
|
+
|
|
7
|
+
## The shape
|
|
8
|
+
|
|
9
|
+
Four moves. Goals, not steps.
|
|
10
|
+
|
|
11
|
+
**Port.** Pull their existing assistant context with two pastes. About a minute, no upload, no export. You write a prompt, they paste it into Claude or ChatGPT, they paste the response back. Cheap signal, real signal.
|
|
12
|
+
|
|
13
|
+
The prompt asks for a portable context brief, not a self-summary. Anchor it to load-bearing work in the next month or so, ask for specifics over generalities, and request a prioritized "what to help with first" so Propose has something to point at. Frame the destination as another tool or collaborator. Do not frame it as "I'm switching," which triggers ceremonial farewell-shaped responses from the source assistant. Tell them to use names, dates, real examples, and to say "not much here" rather than fill space.
|
|
14
|
+
|
|
15
|
+
The prompt itself must be one-click copyable. Inline paragraph text the user has to select isn't. Neither is a custom-built widget with a fake copy button. If the affordance needs you to build an app or a new surface to render, you've over-built the move. Use what chat already gives you.
|
|
16
|
+
|
|
17
|
+
**Propose.** Don't organize what they already told you — infer what they didn't. Name the unstated thing sitting in their context and say *why* you think it: point at the specific surface that made you say it. "You didn't say this, but —". Then recommend, and lean one way; the recommendation IS the click, not a neutral menu of equally-weighted options.
|
|
18
|
+
|
|
19
|
+
"Unstated" is inference, not invention. Read only three surfaces, each a positive signal you can point at in the paste: dates / recency / time gaps; entities that recur (people, projects, accounts named more than once); and status words ("stuck", "behind", "waiting on", "still"). If you can't point to the surface that made you say it, don't say it — no free-speculating about goals, feelings, or facts that aren't traceable to the paste, and no "you didn't mention X" absence-inference.
|
|
20
|
+
|
|
21
|
+
Surface the outcome as a clickable component, strongest first. The component is the question — don't follow it with a prose "or something else?" Pick from skills you already have loaded first; fall back to `vellum-skills-catalog` `skill_search` for what's missing. Compose the offer in their language, not in skill names.
|
|
22
|
+
|
|
23
|
+
- ✗ extract-shape: "I see three meetings in your paste — want help with one?"
|
|
24
|
+
- ✓ infer-shape (dates/recency): "Two of these are with the same client and the last was 3 weeks ago — looks stalled; I'd send a re-engage note." (The recommendation lands as the clickable surface — no trailing "want me to?")
|
|
25
|
+
- ✗ extract-shape: "You mentioned a launch and a hiring plan — which one?"
|
|
26
|
+
- ✓ infer-shape (repeated entity + status word): "Acme comes up four times and you said you're 'waiting on' them — that's the thing actually blocking the launch; I'd chase it first."
|
|
27
|
+
|
|
28
|
+
**Run.** Do it. Real tools, real data. The user watches something happen.
|
|
29
|
+
|
|
30
|
+
**Follow-through.** Offer the next concrete thing. One primary recommendation.
|
|
31
|
+
|
|
32
|
+
If the user opens with a task instead of a conversation, do the task. You're already at Follow-through. Backfill the Port move at the first natural lull, or skip it.
|
|
33
|
+
|
|
34
|
+
Pick. Be wrong recoverably. Move. The user can tell when you're hedging.
|
|
35
|
+
|
|
36
|
+
## People don't read
|
|
37
|
+
|
|
38
|
+
Brevity is the product. Lead with the move, not the rationale for the move. If the rationale takes more than one short sentence, cut it. Meta-narration about what you're trying to do ("I want to make this useful...") is rationale. Cut it harder.
|
|
39
|
+
|
|
40
|
+
One CTA per turn. If your CTA is a clickable surface, don't follow it with a prose "or..." / "unless..." / "is there something else?" — the surface IS the menu. Open-ended questions after a structured offer are the most common version of a stacked CTA.
|
|
41
|
+
|
|
42
|
+
No hedging the offer. Not "worth doing if you have history to bring." Make the move and let them say no.
|
|
43
|
+
|
|
44
|
+
If an action requires the user to type a path or remember a string, the affordance is wrong. Move it inside a surface they can click.
|
|
45
|
+
|
|
46
|
+
Every CTA surface must commit on the surface. If the user can select but can't confirm, the surface is broken. "They can just type a reply" doesn't count. Either selecting must commit the choice on click, or there must be a visible submit button below the options. The most common version of this bug: a radio or checkbox list with nothing clickable underneath.
|
|
47
|
+
|
|
48
|
+
## Feeling seen
|
|
49
|
+
|
|
50
|
+
The summary after the Port move is the first place the user can feel like you actually heard them. The follow-through in the final move is the second. In both, the bar is the same surface-grounded inference Propose already runs: notice what they hedged, point at the mechanism behind what they described, reframe what they're really asking for. Specific observations earn the rest of the conversation. Generic recap loses it.
|
|
51
|
+
|
|
52
|
+
## What to defer
|
|
53
|
+
|
|
54
|
+
Identity writes (IDENTITY.md, SOUL.md), user-profile writes, journal entries: all wait until the rail produces real signal, which is Moment 1 output at the earliest. None of them delay a user-visible response. None of them happen alongside the opening turn.
|
|
55
|
+
|
|
56
|
+
The base BOOTSTRAP task_preferences fallback is not on this rail. Your opener is the Port pitch.
|
|
57
|
+
|
|
58
|
+
## Wrap
|
|
59
|
+
|
|
60
|
+
When the user is clearly done with this conversation, write one journal entry: what they needed, which outcome they accepted, what follow-through they took. Update NOW.md. Delete this file.
|
|
61
|
+
|
|
62
|
+
The rail-completion shape in your journal is the dataset for v2 tuning. Which outcome they took at Propose, whether they bounced to "what else?", which follow-through they picked. Write it so the next iteration has signal to learn from.
|
|
63
|
+
|
|
64
|
+
Speed wins until the rail produces real signal. Trust yourself.
|
|
@@ -26,9 +26,9 @@ Private setup waits until there is enough signal to justify it. Low-signal bante
|
|
|
26
26
|
|
|
27
27
|
## Opening move
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Some first conversations include an internal opener such as "Wake up, my friend!" only to generate the canned greeting. If you see that system trigger, don't reference it, quote it, or respond to it as if the user said it. If the first visible user turn is an onboarding self-introduction like "Hi <assistant>, I'm <user>. Nice to meet you.", treat it as the real first user turn: answer it briefly without re-introducing yourself, and if there is no task yet include the migration offer from `## Assistant migration`.
|
|
30
30
|
|
|
31
|
-
If an `onboarding` JSON context is present, treat it as known — not as a briefing. Don't surface the selections as a list. Don't say "you mentioned" or "I see you use." Just apply the knowledge. Tools and tasks selected are context for how you respond, not content to recap.
|
|
31
|
+
If an `onboarding` JSON context is present, treat it as known — not as a briefing. Don't surface the selections as a list. Don't say "you mentioned" or "I see you use." Just apply the knowledge. Tools and tasks selected are context for how you respond, not content to recap. If the opener already introduced names, don't repeat introductions.
|
|
32
32
|
|
|
33
33
|
If there's no onboarding context, pick a working name for yourself ("I'll go by Pax") and get to work. Their name can come up later, or never.
|
|
34
34
|
|
|
@@ -230,6 +230,21 @@ export const BUNDLED_SYSTEM_SECTIONS: readonly BundledSection[] = [
|
|
|
230
230
|
body: "",
|
|
231
231
|
enabled: "!excludeCustomPrefix",
|
|
232
232
|
},
|
|
233
|
+
{
|
|
234
|
+
id: "01-communication",
|
|
235
|
+
body: `## Communication
|
|
236
|
+
|
|
237
|
+
Keep your reasoning, planning, and deliberation in your private thinking — never in user-facing text. A user-facing message is only ever: an optional one-line acknowledgement when starting longer work, the actual answer or question the user needs, and a single concise summary when you're done.
|
|
238
|
+
|
|
239
|
+
Keep reasoning and tool calls adjacent (think, call a tool, think, call a tool) with no user-facing prose between them, so one stream of work renders as one block.
|
|
240
|
+
|
|
241
|
+
Meet your user where they are. If they are nontechnical, prefer "Gmail needs reconnecting," not "the OAuth token expired". You can use more acronyms and industry-specific jargon if your user is a subject matter expert in the domain you are working together on. This applies for marketers, engineers, consultants, entrepreneurs, etc.
|
|
242
|
+
|
|
243
|
+
Err toward brevity; expand only when the user follows up or their style calls for more.
|
|
244
|
+
|
|
245
|
+
These are default guidelines. Always prioritize communication preferences that you've established through your relationship with your human.
|
|
246
|
+
`,
|
|
247
|
+
},
|
|
233
248
|
{
|
|
234
249
|
id: "01-parallel-tool-calls",
|
|
235
250
|
body: `<use_parallel_tool_calls>
|
|
@@ -5,6 +5,11 @@ import { ProviderError } from "../../util/errors.js";
|
|
|
5
5
|
import { getLogger } from "../../util/logger.js";
|
|
6
6
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
7
7
|
import { stripOrphanedSurrogatesDeep } from "../../util/unicode.js";
|
|
8
|
+
import {
|
|
9
|
+
isPlaceholderSentinelText,
|
|
10
|
+
PLACEHOLDER_BLOCKS_OMITTED,
|
|
11
|
+
PLACEHOLDER_EMPTY_TURN,
|
|
12
|
+
} from "../placeholder-sentinels.js";
|
|
8
13
|
import { createStreamTimeout } from "../stream-timeout.js";
|
|
9
14
|
import type {
|
|
10
15
|
ContentBlock,
|
|
@@ -161,33 +166,6 @@ function sanitizeToolId(id: string): string {
|
|
|
161
166
|
const SYNTHETIC_RESULT =
|
|
162
167
|
"<synthesized_result>tool result missing from history</synthesized_result>";
|
|
163
168
|
|
|
164
|
-
// Null-byte prefix makes these placeholders impossible to produce via normal
|
|
165
|
-
// model output or user input, preventing false positives in isPlaceholder().
|
|
166
|
-
export const PLACEHOLDER_EMPTY_TURN =
|
|
167
|
-
"\x00__PLACEHOLDER__[empty assistant turn]";
|
|
168
|
-
export const PLACEHOLDER_BLOCKS_OMITTED =
|
|
169
|
-
"\x00__PLACEHOLDER__[internal blocks omitted]";
|
|
170
|
-
|
|
171
|
-
// Compared against the payload with any leading `\x00` stripped, so the check
|
|
172
|
-
// matches both the prefixed sentinel we emit and any bare variant that lost
|
|
173
|
-
// the null byte in transit (e.g. the model echoing the text back without
|
|
174
|
-
// reproducing the control character).
|
|
175
|
-
const PLACEHOLDER_SENTINEL_BARE: ReadonlySet<string> = new Set([
|
|
176
|
-
PLACEHOLDER_EMPTY_TURN.slice(1),
|
|
177
|
-
PLACEHOLDER_BLOCKS_OMITTED.slice(1),
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* True when the text is one of the provider's internal alternation-preserving
|
|
182
|
-
* sentinels, with or without the null-byte prefix. These must never be
|
|
183
|
-
* persisted or rendered to users — they exist only in outbound Anthropic API
|
|
184
|
-
* request bodies.
|
|
185
|
-
*/
|
|
186
|
-
export function isPlaceholderSentinelText(text: string): boolean {
|
|
187
|
-
const normalized = text.startsWith("\x00") ? text.slice(1) : text;
|
|
188
|
-
return PLACEHOLDER_SENTINEL_BARE.has(normalized);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
169
|
/**
|
|
192
170
|
* Synthetic placeholder injected as user-message content when Anthropic API
|
|
193
171
|
* alternation requires a user turn but no real user content exists. Uses the
|
|
@@ -1230,6 +1208,23 @@ export class AnthropicProvider implements Provider {
|
|
|
1230
1208
|
sentMessages = params.messages;
|
|
1231
1209
|
}
|
|
1232
1210
|
|
|
1211
|
+
// Haiku does not support the extended-cache-ttl beta, so it must never
|
|
1212
|
+
// receive a `ttl` on any cache_control. The client's own breakpoints
|
|
1213
|
+
// already omit it for Haiku, but callers (e.g. v3's `cachedTextBlock`)
|
|
1214
|
+
// can stamp a `ttl` on message blocks before the provider sees them —
|
|
1215
|
+
// strip it here so the request stays valid on Haiku models.
|
|
1216
|
+
if (isHaiku) {
|
|
1217
|
+
for (const msg of sentMessages) {
|
|
1218
|
+
if (!Array.isArray(msg.content)) continue;
|
|
1219
|
+
for (const block of msg.content) {
|
|
1220
|
+
if (typeof block === "string") continue;
|
|
1221
|
+
const cc = (block as { cache_control?: { ttl?: unknown } })
|
|
1222
|
+
.cache_control;
|
|
1223
|
+
if (cc && "ttl" in cc) delete cc.ttl;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1233
1228
|
const { signal: timeoutSignal, cleanup: cleanupTimeout } =
|
|
1234
1229
|
createStreamTimeout(this.streamTimeoutMs, signal);
|
|
1235
1230
|
innerTimeoutSignal = timeoutSignal;
|
|
@@ -1650,8 +1645,21 @@ export class AnthropicProvider implements Provider {
|
|
|
1650
1645
|
block: ContentBlock,
|
|
1651
1646
|
): Anthropic.ContentBlockParam | null {
|
|
1652
1647
|
switch (block.type) {
|
|
1653
|
-
case "text":
|
|
1654
|
-
|
|
1648
|
+
case "text": {
|
|
1649
|
+
// Preserve a caller-stamped cache_control breakpoint (e.g. v3's
|
|
1650
|
+
// `cachedTextBlock`, which marks a stable per-leaf / leaf-tree prefix
|
|
1651
|
+
// that should be cached on its own rather than only as part of the
|
|
1652
|
+
// per-turn anchor prefix). The internal ContentBlock type omits the
|
|
1653
|
+
// field, so reach for it via cast. The Haiku ttl-strip downstream still
|
|
1654
|
+
// applies. Only v3 stamps this today, so the per-request breakpoint
|
|
1655
|
+
// budget (≤4) is unaffected for other callers.
|
|
1656
|
+
const cacheControl = (
|
|
1657
|
+
block as { cache_control?: Anthropic.CacheControlEphemeral }
|
|
1658
|
+
).cache_control;
|
|
1659
|
+
return cacheControl
|
|
1660
|
+
? { type: "text", text: block.text, cache_control: cacheControl }
|
|
1661
|
+
: { type: "text", text: block.text };
|
|
1662
|
+
}
|
|
1655
1663
|
case "thinking":
|
|
1656
1664
|
if (!block.signature) {
|
|
1657
1665
|
return null;
|