@vellumai/assistant 0.8.7 → 0.8.8
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/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/docs/plugins.md +75 -79
- package/examples/plugins/echo/README.md +6 -12
- package/examples/plugins/echo/register.ts +0 -41
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/openapi.yaml +3381 -348
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
- 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__/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__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- 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 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
- package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- 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 +26 -5
- 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 +170 -229
- package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- 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-injection.test.ts +6 -1
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -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__/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 +2 -8
- package/src/__tests__/injector-chain.test.ts +106 -270
- package/src/__tests__/injector-disk-pressure.test.ts +3 -12
- package/src/__tests__/injector-document-comments.test.ts +2 -2
- 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-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__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pipeline-runner.test.ts +29 -39
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +13 -28
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-types.test.ts +2 -125
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- 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__/server-history-render.test.ts +314 -1
- package/src/__tests__/skillssh-files.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 +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/agent/loop.ts +346 -442
- 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 +368 -0
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
- 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/document-editor/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +35 -3
- 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/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 +122 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +3 -6
- 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 +605 -153
- package/src/daemon/conversation-agent-loop.ts +281 -760
- 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-runtime-assembly.ts +130 -347
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation.ts +32 -14
- package/src/daemon/external-plugins-bootstrap.ts +9 -10
- 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/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +39 -10
- package/src/daemon/lifecycle.ts +4 -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/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- 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/home/home-greeting-cache.ts +24 -1
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- 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-title-service.ts +65 -41
- package/src/memory/db-init.ts +4 -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/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +31 -4
- 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/index.ts +2 -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/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
- package/src/memory/v2/consolidation-job.ts +1 -1
- package/src/memory/v3/__tests__/health.test.ts +16 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
- package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
- package/src/memory/v3/__tests__/router.test.ts +101 -29
- package/src/memory/v3/__tests__/selector.test.ts +93 -27
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/llm-retry.ts +32 -0
- package/src/memory/v3/orchestrate.ts +26 -14
- package/src/memory/v3/provider-blocks.ts +15 -5
- package/src/memory/v3/router.ts +48 -42
- package/src/memory/v3/selector.ts +57 -42
- package/src/memory/v3/shadow-plugin.ts +47 -15
- package/src/memory/v3/types.ts +8 -0
- 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 +8 -1
- package/src/plugin-api/types.ts +151 -1
- 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 +1 -15
- package/src/plugins/defaults/injectors/register.ts +243 -74
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -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/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/pipeline.ts +6 -18
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +43 -474
- package/src/proactive-artifact/aux-message-injector.ts +3 -3
- package/src/proactive-artifact/job.test.ts +7 -12
- package/src/prompts/__tests__/system-prompt.test.ts +36 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -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 +5 -1
- package/src/runtime/agent-wake.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- 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__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-query-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +358 -184
- 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/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +208 -17
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- 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 +50 -28
- 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/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/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/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/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- 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/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/executor.ts +1 -53
- 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/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +9 -1
- 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/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 +93 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- 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__/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/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/package.json +0 -15
- 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/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
|
@@ -87,6 +87,14 @@ export const DaemonConfigSchema = z
|
|
|
87
87
|
.describe(
|
|
88
88
|
"Whether the daemon records conversations even when no client is connected",
|
|
89
89
|
),
|
|
90
|
+
reapOrphanedSubprocesses: z
|
|
91
|
+
.boolean({
|
|
92
|
+
error: "daemon.reapOrphanedSubprocesses must be a boolean",
|
|
93
|
+
})
|
|
94
|
+
.default(false)
|
|
95
|
+
.describe(
|
|
96
|
+
"Whether the daemon, when running as PID 1 in a container, periodically reaps orphaned subprocesses that reparented to it. Off by default while the behavior is being validated.",
|
|
97
|
+
),
|
|
90
98
|
})
|
|
91
99
|
.describe("Background daemon process configuration");
|
|
92
100
|
|
|
@@ -58,7 +58,7 @@ const MANAGED_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
|
58
58
|
label: "Quality",
|
|
59
59
|
description: "Best results with the most capable model",
|
|
60
60
|
maxTokens: 32000,
|
|
61
|
-
effort: "
|
|
61
|
+
effort: "high",
|
|
62
62
|
thinking: { enabled: true, streamThinking: true },
|
|
63
63
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
64
64
|
},
|
|
@@ -104,7 +104,7 @@ const USER_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
|
104
104
|
label: "Quality",
|
|
105
105
|
description: "Best results with the most capable model",
|
|
106
106
|
maxTokens: 32000,
|
|
107
|
-
effort: "
|
|
107
|
+
effort: "high",
|
|
108
108
|
thinking: { enabled: true, streamThinking: true },
|
|
109
109
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
110
110
|
},
|
package/src/config/skills.ts
CHANGED
|
@@ -52,6 +52,7 @@ const VellumMetadataSchema = z
|
|
|
52
52
|
|
|
53
53
|
const SkillMetadataSchema = z
|
|
54
54
|
.object({
|
|
55
|
+
icon: z.string().optional(),
|
|
55
56
|
emoji: z.string().optional(),
|
|
56
57
|
vellum: VellumMetadataSchema.optional(),
|
|
57
58
|
})
|
|
@@ -210,6 +211,7 @@ interface ParsedFrontmatter {
|
|
|
210
211
|
displayName: string;
|
|
211
212
|
description: string;
|
|
212
213
|
body: string;
|
|
214
|
+
icon?: string;
|
|
213
215
|
emoji?: string;
|
|
214
216
|
includes?: string[];
|
|
215
217
|
featureFlag?: string;
|
|
@@ -253,6 +255,7 @@ function parseFrontmatter(
|
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
// metadata is already a parsed object from YAML — validate with Zod schema
|
|
258
|
+
let icon: string | undefined;
|
|
256
259
|
let emoji: string | undefined;
|
|
257
260
|
let parsedMeta: z.infer<typeof SkillMetadataSchema> | undefined;
|
|
258
261
|
let vellum: z.infer<typeof VellumMetadataSchema> | undefined;
|
|
@@ -271,6 +274,7 @@ function parseFrontmatter(
|
|
|
271
274
|
if (zodResult.success) {
|
|
272
275
|
parsedMeta = zodResult.data;
|
|
273
276
|
vellum = parsedMeta.vellum;
|
|
277
|
+
icon = parsedMeta.icon;
|
|
274
278
|
emoji = vellum?.emoji ?? parsedMeta.emoji;
|
|
275
279
|
} else {
|
|
276
280
|
// Zod validation failed — fall back to raw parsed object so we don't
|
|
@@ -284,6 +288,9 @@ function parseFrontmatter(
|
|
|
284
288
|
const raw = metadataRaw as Record<string, unknown>;
|
|
285
289
|
parsedMeta = raw as z.infer<typeof SkillMetadataSchema>;
|
|
286
290
|
vellum = raw?.vellum as z.infer<typeof VellumMetadataSchema>;
|
|
291
|
+
if (typeof parsedMeta?.icon === "string") {
|
|
292
|
+
icon = parsedMeta.icon;
|
|
293
|
+
}
|
|
287
294
|
if (vellum && typeof vellum === "object") {
|
|
288
295
|
emoji = typeof vellum.emoji === "string" ? vellum.emoji : undefined;
|
|
289
296
|
}
|
|
@@ -337,6 +344,7 @@ function parseFrontmatter(
|
|
|
337
344
|
displayName,
|
|
338
345
|
description,
|
|
339
346
|
body: strippedBody,
|
|
347
|
+
icon,
|
|
340
348
|
emoji,
|
|
341
349
|
includes,
|
|
342
350
|
featureFlag,
|
|
@@ -489,6 +497,7 @@ function readSkillFromDirectory(
|
|
|
489
497
|
directoryPath,
|
|
490
498
|
skillFilePath,
|
|
491
499
|
body: parsed.body,
|
|
500
|
+
icon: parsed.icon,
|
|
492
501
|
emoji: parsed.emoji,
|
|
493
502
|
|
|
494
503
|
source,
|
|
@@ -540,6 +549,7 @@ function readBundledSkillFromDirectory(
|
|
|
540
549
|
skillFilePath,
|
|
541
550
|
body: parsed.body,
|
|
542
551
|
bundled: true,
|
|
552
|
+
icon: parsed.icon,
|
|
543
553
|
emoji: parsed.emoji,
|
|
544
554
|
|
|
545
555
|
source: "bundled",
|
|
@@ -599,6 +609,7 @@ function loadBundledSkills(): SkillSummary[] {
|
|
|
599
609
|
directoryPath: skill.directoryPath,
|
|
600
610
|
skillFilePath: skill.skillFilePath,
|
|
601
611
|
bundled: true,
|
|
612
|
+
icon: skill.icon,
|
|
602
613
|
emoji: skill.emoji,
|
|
603
614
|
|
|
604
615
|
source: "bundled",
|
|
@@ -649,6 +660,7 @@ function skillSummaryFromDefinition(
|
|
|
649
660
|
directoryPath: skill.directoryPath,
|
|
650
661
|
skillFilePath: skill.skillFilePath,
|
|
651
662
|
bundled: skill.bundled,
|
|
663
|
+
icon: skill.icon,
|
|
652
664
|
emoji: skill.emoji,
|
|
653
665
|
source,
|
|
654
666
|
toolManifest: skill.toolManifest,
|
|
@@ -836,6 +848,7 @@ export function loadSkillCatalog(
|
|
|
836
848
|
description: parsed.description,
|
|
837
849
|
directoryPath: directory,
|
|
838
850
|
skillFilePath,
|
|
851
|
+
icon: parsed.icon,
|
|
839
852
|
emoji: parsed.emoji,
|
|
840
853
|
|
|
841
854
|
source: "workspace",
|
package/src/context/compactor.ts
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
import { optimizeImageForTransport } from "../agent/image-optimize.js";
|
|
22
22
|
import type { CompactionConfig } from "../config/schemas/compaction.js";
|
|
23
23
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
24
|
-
import { stripInjectionsForCompaction } from "../daemon/conversation-runtime-assembly.js";
|
|
25
24
|
import { filterMessagesForUntrustedActor } from "../daemon/message-provenance.js";
|
|
26
25
|
import {
|
|
27
26
|
getAttachmentContent,
|
|
@@ -42,6 +41,7 @@ import {
|
|
|
42
41
|
type TrustClass,
|
|
43
42
|
} from "../runtime/actor-trust-resolver.js";
|
|
44
43
|
import { getLogger } from "../util/logger.js";
|
|
44
|
+
import { stripInjectionsForCompaction } from "./strip-injections.js";
|
|
45
45
|
import { estimatePromptTokens } from "./token-estimator.js";
|
|
46
46
|
|
|
47
47
|
const log = getLogger("compactor");
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-injection stripping for compaction and overflow recovery.
|
|
3
|
+
*
|
|
4
|
+
* Runtime injections (turn context, memory, NOW.md, workspace, Slack
|
|
5
|
+
* chronological, etc.) persist in message history to keep the conversation
|
|
6
|
+
* prefix stable for Anthropic's prefix caching. They only need to be removed
|
|
7
|
+
* when compaction rewrites the message array, so the compactor summarizes the
|
|
8
|
+
* raw persistent messages rather than the ephemeral injected blocks.
|
|
9
|
+
*
|
|
10
|
+
* This module is the compaction-layer home for that strip so both the agent
|
|
11
|
+
* loop (which drives the compaction pipeline) and the compactor can call it
|
|
12
|
+
* without reaching up into the daemon orchestrator.
|
|
13
|
+
*/
|
|
14
|
+
import type { Message } from "../providers/types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A matcher for an injected text block. A plain string matches by prefix
|
|
18
|
+
* (`startsWith`). A `{ prefix, suffix }` wrapper requires BOTH the opening
|
|
19
|
+
* prefix and the closing suffix, so user-authored content that merely begins
|
|
20
|
+
* with an injection-like opening tag (e.g. a message discussing `<info>`
|
|
21
|
+
* markup) is not mistaken for an injected block and dropped. This mirrors
|
|
22
|
+
* `countMemoryPrefixBlocks`, which only treats `<memory>…</memory>` /
|
|
23
|
+
* `<info>…</info>` blocks as injected when the full wrapper is present.
|
|
24
|
+
*/
|
|
25
|
+
export type InjectionMatcher = string | { prefix: string; suffix: string };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Remove text blocks from user messages that match any of the given matchers.
|
|
29
|
+
* If stripping removes all content blocks from a message, the message itself
|
|
30
|
+
* is dropped.
|
|
31
|
+
*
|
|
32
|
+
* This is the shared primitive behind the individual strip* functions and
|
|
33
|
+
* the `stripInjectionsForCompaction` pipeline.
|
|
34
|
+
*/
|
|
35
|
+
export function stripUserTextBlocksByPrefix(
|
|
36
|
+
messages: Message[],
|
|
37
|
+
matchers: InjectionMatcher[],
|
|
38
|
+
): Message[] {
|
|
39
|
+
return messages
|
|
40
|
+
.map((message) => {
|
|
41
|
+
if (message.role !== "user") return message;
|
|
42
|
+
const nextContent = message.content.filter((block) => {
|
|
43
|
+
if (block.type !== "text") return true;
|
|
44
|
+
return !matchers.some((m) =>
|
|
45
|
+
typeof m === "string"
|
|
46
|
+
? block.text.startsWith(m)
|
|
47
|
+
: block.text.startsWith(m.prefix) && block.text.endsWith(m.suffix),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
if (nextContent.length === message.content.length) return message;
|
|
51
|
+
if (nextContent.length === 0) return null;
|
|
52
|
+
return { ...message, content: nextContent };
|
|
53
|
+
})
|
|
54
|
+
.filter(
|
|
55
|
+
(message): message is NonNullable<typeof message> => message != null,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Matchers stripped by the pipeline (order doesn't matter — single pass). */
|
|
60
|
+
const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
|
|
61
|
+
"<channel_capabilities>",
|
|
62
|
+
"<channel_command_context>",
|
|
63
|
+
"<disk_pressure_warning>",
|
|
64
|
+
"<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
|
|
65
|
+
"<guardian_context>",
|
|
66
|
+
"<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
|
|
67
|
+
"<interface_turn_context>", // backward-compat: strip legacy separate interface blocks
|
|
68
|
+
// NOTE: <turn_context> is intentionally NOT stripped — unified turn context
|
|
69
|
+
// blocks persist in history so the assistant retains temporal/actor grounding.
|
|
70
|
+
"<background_turn>",
|
|
71
|
+
"<memory_context __injected>",
|
|
72
|
+
"<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
|
|
73
|
+
// The static `memory-v2-static` block (`<info>\n…</info>`) and the
|
|
74
|
+
// dynamic activation block (`<memory>\n…</memory>`, plus legacy
|
|
75
|
+
// `<memory __injected>…`) are both stripped so each compaction
|
|
76
|
+
// re-injects the freshest essentials/threads/recent/buffer view and
|
|
77
|
+
// re-runs the activation pipeline, matching the `<knowledge_base>`
|
|
78
|
+
// cadence. The activation pipeline dedupes via `everInjected`, and
|
|
79
|
+
// compaction handles aggregate growth, so accumulation does not cause
|
|
80
|
+
// unbounded context growth. Both wrappers may appear in persisted rows.
|
|
81
|
+
//
|
|
82
|
+
// These two use the full `{ prefix, suffix }` wrapper shape (not a bare
|
|
83
|
+
// prefix) so that user-authored text merely starting with `<memory>\n` or
|
|
84
|
+
// `<info>\n` is never silently dropped during compaction/`/clean`. This
|
|
85
|
+
// matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
|
|
86
|
+
{ prefix: "<memory>\n", suffix: "\n</memory>" },
|
|
87
|
+
{ prefix: "<info>\n", suffix: "\n</info>" },
|
|
88
|
+
"<voice_call_control>",
|
|
89
|
+
"<workspace_top_level>", // backward-compat: strip legacy workspace blocks
|
|
90
|
+
// NOTE: <workspace> is intentionally NOT stripped — workspace context
|
|
91
|
+
// persists in history so the assistant retains workspace grounding.
|
|
92
|
+
"<temporal_context>\nToday:", // backward-compat: strip legacy temporal blocks
|
|
93
|
+
"<active_subagents>",
|
|
94
|
+
"<active_workspace>",
|
|
95
|
+
"<active_dynamic_page>",
|
|
96
|
+
"<non_interactive_context>",
|
|
97
|
+
// Shared prefix catches both the current NOW.md tag and any pre-line-limit
|
|
98
|
+
// variant that may linger in in-flight histories during a rolling deploy.
|
|
99
|
+
"<NOW.md Always keep this up to date",
|
|
100
|
+
"<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
|
|
101
|
+
"<knowledge_base>",
|
|
102
|
+
"<pkb>", // backward-compat: strip legacy tag from pre-rename history
|
|
103
|
+
"<system_reminder>",
|
|
104
|
+
"<transport_hints>",
|
|
105
|
+
// The Slack active-thread focus block is non-persisted and injected on
|
|
106
|
+
// the FINAL user turn only. Strip it here so re-assembly during compaction
|
|
107
|
+
// and overflow recovery does not duplicate it across turns.
|
|
108
|
+
"<active_thread>",
|
|
109
|
+
"<system_notice>One or more tool calls returned an error.",
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Strip all runtime-injected context from message history in a single pass.
|
|
114
|
+
*
|
|
115
|
+
* Used only during compaction and overflow recovery — not on normal turns.
|
|
116
|
+
* Runtime injections persist in history to keep the conversation prefix
|
|
117
|
+
* stable for Anthropic's prefix caching. Stripping is only needed when
|
|
118
|
+
* compaction rewrites the message array (cache miss is expected anyway).
|
|
119
|
+
*/
|
|
120
|
+
export function stripInjectionsForCompaction(messages: Message[]): Message[] {
|
|
121
|
+
return stripUserTextBlocksByPrefix(messages, RUNTIME_INJECTION_PREFIXES);
|
|
122
|
+
}
|
|
@@ -359,6 +359,29 @@ export function estimatePromptTokens(
|
|
|
359
359
|
return correction === 1.0 ? raw : Math.ceil(raw * correction);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Calibrated prompt-token estimate including the tool-definition budget.
|
|
364
|
+
*
|
|
365
|
+
* Combines the per-tool budget ({@link estimateToolsTokens}) with the
|
|
366
|
+
* message/system estimate ({@link estimatePromptTokens}) under the EWMA
|
|
367
|
+
* calibration correction. This is the estimate the overflow gate consumes;
|
|
368
|
+
* the pre-send calibration capture in `agent/loop.ts` deliberately stays on
|
|
369
|
+
* `estimatePromptTokensRaw` so the calibrator trains against the uncorrected
|
|
370
|
+
* value rather than chasing its own output.
|
|
371
|
+
*/
|
|
372
|
+
export function estimatePromptTokensWithTools(
|
|
373
|
+
history: Message[],
|
|
374
|
+
systemPrompt: string | undefined,
|
|
375
|
+
tools: ToolDefinition[],
|
|
376
|
+
providerName: string,
|
|
377
|
+
): number {
|
|
378
|
+
const toolTokenBudget = tools.length > 0 ? estimateToolsTokens(tools) : 0;
|
|
379
|
+
return estimatePromptTokens(history, systemPrompt, {
|
|
380
|
+
providerName,
|
|
381
|
+
toolTokenBudget,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
362
385
|
function stableJson(value: unknown): string {
|
|
363
386
|
try {
|
|
364
387
|
return JSON.stringify(value);
|
|
@@ -5,29 +5,6 @@ import type {
|
|
|
5
5
|
ToolResultContent,
|
|
6
6
|
} from "../providers/types.js";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Maximum share of the context window that a single tool result may occupy.
|
|
10
|
-
*/
|
|
11
|
-
const MAX_TOOL_RESULT_CONTEXT_SHARE = 0.3;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Absolute cap on tool-result characters (~100K tokens).
|
|
15
|
-
*/
|
|
16
|
-
export const HARD_MAX_TOOL_RESULT_CHARS = 400_000;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Calculate the maximum allowed characters for a tool result based on the
|
|
20
|
-
* context window size. Uses ~4 chars per token as a rough heuristic.
|
|
21
|
-
*/
|
|
22
|
-
export function calculateMaxToolResultChars(
|
|
23
|
-
contextWindowTokens: number,
|
|
24
|
-
): number {
|
|
25
|
-
return Math.min(
|
|
26
|
-
HARD_MAX_TOOL_RESULT_CHARS,
|
|
27
|
-
Math.floor(contextWindowTokens * MAX_TOOL_RESULT_CONTEXT_SHARE * 4),
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
8
|
/**
|
|
32
9
|
* Aggressively truncate all tool-result text across an entire message history.
|
|
33
10
|
*
|
|
@@ -90,7 +90,6 @@ export interface ShouldCompactResult {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export interface ContextWindowCompactOptions {
|
|
93
|
-
lastCompactedAt?: number;
|
|
94
93
|
/** Skip the auto-threshold check (used for /compact and recovery). */
|
|
95
94
|
force?: boolean;
|
|
96
95
|
/**
|
|
@@ -104,14 +103,12 @@ export interface ContextWindowCompactOptions {
|
|
|
104
103
|
*/
|
|
105
104
|
precomputedEstimate?: number;
|
|
106
105
|
/**
|
|
107
|
-
* Legacy
|
|
108
|
-
* callers. The new assistant-driven compactor does not consume
|
|
109
|
-
* the model decides where to cut and what to keep — but accepting
|
|
106
|
+
* Legacy field retained for backwards compatibility with existing
|
|
107
|
+
* callers. The new assistant-driven compactor does not consume it —
|
|
108
|
+
* the model decides where to cut and what to keep — but accepting it
|
|
110
109
|
* here lets callers keep their existing call sites unchanged.
|
|
111
110
|
*/
|
|
112
111
|
minKeepRecentUserTurns?: number;
|
|
113
|
-
conversationOriginChannel?: string;
|
|
114
|
-
targetInputTokensOverride?: number;
|
|
115
112
|
/**
|
|
116
113
|
* Trust class of the actor whose turn triggered compaction. Forwarded to
|
|
117
114
|
* the compactor so the image manifest excludes guardian-only attachments
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
20
21
|
import { dirname, join } from "node:path";
|
|
21
22
|
|
|
22
23
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
@@ -160,6 +161,21 @@ export function discoverLocalCes():
|
|
|
160
161
|
return { mode: "local-source", sourcePath: sourceEntry };
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
// npm-layout fallback: resolve via node_modules when installed as a package
|
|
165
|
+
try {
|
|
166
|
+
const _require = createRequire(import.meta.url);
|
|
167
|
+
const pkgPath = _require.resolve(
|
|
168
|
+
"@vellumai/credential-executor/package.json",
|
|
169
|
+
);
|
|
170
|
+
const npmSourceEntry = join(dirname(pkgPath), "src", "main.ts");
|
|
171
|
+
if (existsSync(npmSourceEntry)) {
|
|
172
|
+
log.info({ path: npmSourceEntry }, "Found CES source via npm package");
|
|
173
|
+
return { mode: "local-source", sourcePath: npmSourceEntry };
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// Package not installed — fall through to unavailable
|
|
177
|
+
}
|
|
178
|
+
|
|
163
179
|
const reason = `CES executable not found. Searched: ${searchPaths.join(", ")}; also checked source at ${sourceEntry}`;
|
|
164
180
|
log.warn(reason);
|
|
165
181
|
return { mode: "unavailable", reason };
|
|
@@ -165,6 +165,12 @@ function makeDisposeContext(
|
|
|
165
165
|
const ctx = {
|
|
166
166
|
conversationId: overrides.conversationId ?? "conv-1",
|
|
167
167
|
processing: false,
|
|
168
|
+
isProcessing(this: { processing: boolean }) {
|
|
169
|
+
return this.processing;
|
|
170
|
+
},
|
|
171
|
+
setProcessing(this: { processing: boolean }, value: boolean) {
|
|
172
|
+
this.processing = value;
|
|
173
|
+
},
|
|
168
174
|
abortController,
|
|
169
175
|
prompter,
|
|
170
176
|
secretPrompter,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that the inference-profile-change notification is persisted only
|
|
3
|
+
* once the model has actually received the turn context carrying the
|
|
4
|
+
* `model_profile` block — signalled by the first `message_complete` event from
|
|
5
|
+
* the agent loop — rather than inline before the provider call. A turn that
|
|
6
|
+
* fails or is cancelled before delivery never emits `message_complete`, so the
|
|
7
|
+
* notice is re-sent on the next turn instead of being silently suppressed.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
// ── Mock platform (must precede imports that read it) ────────────────────────
|
|
12
|
+
mock.module("../../util/logger.js", () => ({
|
|
13
|
+
getLogger: () =>
|
|
14
|
+
new Proxy({} as Record<string, unknown>, {
|
|
15
|
+
get: () => () => {},
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
mock.module("../../config/loader.js", () => ({
|
|
20
|
+
getConfig: () => ({ memory: {} }),
|
|
21
|
+
loadConfig: () => ({}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const setLastNotifiedInferenceProfile = mock(
|
|
25
|
+
(_conversationId: string, _profileKey: string) => {},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
mock.module("../../memory/conversation-crud.js", () => ({
|
|
29
|
+
deleteMessageById: () => {},
|
|
30
|
+
getConversation: () => null,
|
|
31
|
+
getMessageById: () => null,
|
|
32
|
+
messageMetadataSchema: { safeParse: () => ({ success: false }) },
|
|
33
|
+
provenanceFromTrustContext: () => ({}),
|
|
34
|
+
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
35
|
+
setConversationHistoryStrippedAt: () => {},
|
|
36
|
+
setLastNotifiedInferenceProfile,
|
|
37
|
+
updateMessageContent: () => {},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
mock.module("../../memory/llm-request-log-store.js", () => ({
|
|
41
|
+
backfillMessageIdOnLogs: () => {},
|
|
42
|
+
buildProviderErrorResponsePayload: () => ({}),
|
|
43
|
+
recordRequestLog: () => {},
|
|
44
|
+
setAgentLoopExitReasonOnLatestLog: () => {},
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
mock.module("../../memory/memory-recall-log-store.js", () => ({
|
|
48
|
+
backfillMemoryRecallLogMessageId: () => {},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
mock.module("../../memory/memory-v2-activation-log-store.js", () => ({
|
|
52
|
+
backfillMemoryV2ActivationMessageId: () => {},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
// ── Imports (after mocks) ────────────────────────────────────────────────────
|
|
56
|
+
import type { AgentEvent } from "../../agent/loop.js";
|
|
57
|
+
import type { Message } from "../../providers/types.js";
|
|
58
|
+
import type {
|
|
59
|
+
EventHandlerDeps,
|
|
60
|
+
EventHandlerState,
|
|
61
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
62
|
+
import {
|
|
63
|
+
createEventHandlerState,
|
|
64
|
+
dispatchAgentEvent,
|
|
65
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
66
|
+
|
|
67
|
+
const CONVERSATION_ID = "conv-profile-notify";
|
|
68
|
+
const PROFILE_KEY = "quality-optimized";
|
|
69
|
+
|
|
70
|
+
function makeDeps(): EventHandlerDeps {
|
|
71
|
+
return {
|
|
72
|
+
ctx: {
|
|
73
|
+
conversationId: CONVERSATION_ID,
|
|
74
|
+
provider: { name: "mock-provider" },
|
|
75
|
+
traceEmitter: { emit: () => {} },
|
|
76
|
+
currentTurnSurfaces: [],
|
|
77
|
+
trustContext: undefined,
|
|
78
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
79
|
+
onEvent: () => {},
|
|
80
|
+
reqId: "req-profile-notify",
|
|
81
|
+
isFirstMessage: false,
|
|
82
|
+
shouldGenerateTitle: false,
|
|
83
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
84
|
+
get: () => () => {},
|
|
85
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
86
|
+
turnChannelContext: {
|
|
87
|
+
userMessageChannel: "vellum",
|
|
88
|
+
assistantMessageChannel: "vellum",
|
|
89
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
90
|
+
turnInterfaceContext: {
|
|
91
|
+
userMessageInterface: "macos",
|
|
92
|
+
assistantMessageInterface: "macos",
|
|
93
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
94
|
+
applyCompaction: async () => {},
|
|
95
|
+
} as EventHandlerDeps;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function messageCompleteEvent(): Extract<
|
|
99
|
+
AgentEvent,
|
|
100
|
+
{ type: "message_complete" }
|
|
101
|
+
> {
|
|
102
|
+
const message: Message = {
|
|
103
|
+
role: "assistant",
|
|
104
|
+
content: [{ type: "text", text: "response" }],
|
|
105
|
+
};
|
|
106
|
+
return { type: "message_complete", message };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A reserved assistant row id is the precondition `handleMessageComplete`
|
|
110
|
+
// asserts before finalizing; the row reservation itself is exercised elsewhere.
|
|
111
|
+
function readyState(): EventHandlerState {
|
|
112
|
+
const state = createEventHandlerState();
|
|
113
|
+
state.lastAssistantMessageId = "assistant-row-1";
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe("inference-profile-change notification persistence", () => {
|
|
118
|
+
test("persists on the first message_complete and clears the pending slot", async () => {
|
|
119
|
+
setLastNotifiedInferenceProfile.mockClear();
|
|
120
|
+
const state = readyState();
|
|
121
|
+
state.pendingNotifiedInferenceProfile = PROFILE_KEY;
|
|
122
|
+
|
|
123
|
+
await dispatchAgentEvent(state, makeDeps(), messageCompleteEvent());
|
|
124
|
+
|
|
125
|
+
expect(setLastNotifiedInferenceProfile).toHaveBeenCalledTimes(1);
|
|
126
|
+
expect(setLastNotifiedInferenceProfile).toHaveBeenCalledWith(
|
|
127
|
+
CONVERSATION_ID,
|
|
128
|
+
PROFILE_KEY,
|
|
129
|
+
);
|
|
130
|
+
expect(state.pendingNotifiedInferenceProfile).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("does not persist when the turn carries no profile-change notice", async () => {
|
|
134
|
+
setLastNotifiedInferenceProfile.mockClear();
|
|
135
|
+
const state = readyState();
|
|
136
|
+
|
|
137
|
+
await dispatchAgentEvent(state, makeDeps(), messageCompleteEvent());
|
|
138
|
+
|
|
139
|
+
expect(setLastNotifiedInferenceProfile).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("persists exactly once across a multi-call turn", async () => {
|
|
143
|
+
setLastNotifiedInferenceProfile.mockClear();
|
|
144
|
+
const state = readyState();
|
|
145
|
+
state.pendingNotifiedInferenceProfile = PROFILE_KEY;
|
|
146
|
+
const deps = makeDeps();
|
|
147
|
+
|
|
148
|
+
await dispatchAgentEvent(state, deps, messageCompleteEvent());
|
|
149
|
+
await dispatchAgentEvent(state, deps, messageCompleteEvent());
|
|
150
|
+
|
|
151
|
+
expect(setLastNotifiedInferenceProfile).toHaveBeenCalledTimes(1);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -51,6 +51,7 @@ mock.module("../../memory/llm-request-log-store.js", () => ({
|
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
53
|
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
54
|
+
import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../../tools/network/web-search-error.js";
|
|
54
55
|
import type {
|
|
55
56
|
EventHandlerDeps,
|
|
56
57
|
EventHandlerState,
|
|
@@ -95,6 +96,7 @@ function createCollectorDeps(providerName = "anthropic"): {
|
|
|
95
96
|
userMessageInterface: "macos",
|
|
96
97
|
assistantMessageInterface: "macos",
|
|
97
98
|
} as EventHandlerDeps["turnInterfaceContext"],
|
|
99
|
+
applyCompaction: async () => {},
|
|
98
100
|
} as EventHandlerDeps;
|
|
99
101
|
return { deps, events };
|
|
100
102
|
}
|
|
@@ -257,9 +259,7 @@ describe("native server_tool_complete metadata", () => {
|
|
|
257
259
|
);
|
|
258
260
|
expect(toolResults).toHaveLength(2);
|
|
259
261
|
|
|
260
|
-
const byId = new Map(
|
|
261
|
-
toolResults.map((r) => [r.toolUseId, r] as const),
|
|
262
|
-
);
|
|
262
|
+
const byId = new Map(toolResults.map((r) => [r.toolUseId, r] as const));
|
|
263
263
|
expect(byId.get("tu_a")?.activityMetadata?.webSearch?.query).toBe("alpha");
|
|
264
264
|
expect(byId.get("tu_b")?.activityMetadata?.webSearch?.query).toBe("beta");
|
|
265
265
|
|
|
@@ -270,7 +270,7 @@ describe("native server_tool_complete metadata", () => {
|
|
|
270
270
|
expect(durB).toBeGreaterThanOrEqual(durA);
|
|
271
271
|
});
|
|
272
272
|
|
|
273
|
-
test("
|
|
273
|
+
test("maps recoverable error codes to friendly copy, not the raw code", async () => {
|
|
274
274
|
const { deps, events } = createCollectorDeps();
|
|
275
275
|
const toolUseId = "tu_err_code";
|
|
276
276
|
|
|
@@ -292,9 +292,11 @@ describe("native server_tool_complete metadata", () => {
|
|
|
292
292
|
const toolResultEvent = events.find(
|
|
293
293
|
(e): e is ToolResultEvent => e.type === "tool_result",
|
|
294
294
|
);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
const errorMessage =
|
|
296
|
+
toolResultEvent?.activityMetadata?.webSearch?.errorMessage;
|
|
297
|
+
// The raw provider code is never user-visible.
|
|
298
|
+
expect(errorMessage).not.toBe("max_uses_exceeded");
|
|
299
|
+
expect(errorMessage).toContain("web-search limit");
|
|
298
300
|
});
|
|
299
301
|
|
|
300
302
|
test("does NOT emit activityMetadata for non-Anthropic providers", async () => {
|
|
@@ -350,7 +352,7 @@ describe("native server_tool_complete metadata", () => {
|
|
|
350
352
|
(e): e is ToolResultEvent => e.type === "tool_result",
|
|
351
353
|
);
|
|
352
354
|
const meta = toolResultEvent?.activityMetadata?.webSearch;
|
|
353
|
-
expect(meta?.errorMessage).toBe(
|
|
355
|
+
expect(meta?.errorMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
354
356
|
expect(meta?.resultCount).toBe(0);
|
|
355
357
|
expect(meta?.results).toEqual([]);
|
|
356
358
|
expect(toolResultEvent?.isError).toBe(true);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { readFileSync, statSync } from "node:fs";
|
|
9
9
|
import { basename } from "node:path";
|
|
10
10
|
|
|
11
|
-
import { isPlaceholderSentinelText } from "../providers/
|
|
11
|
+
import { isPlaceholderSentinelText } from "../providers/placeholder-sentinels.js";
|
|
12
12
|
import {
|
|
13
13
|
hostPolicy,
|
|
14
14
|
sandboxPolicy,
|
|
@@ -178,8 +178,8 @@ export class ConfigWatcher {
|
|
|
178
178
|
* Start all file watchers. `onConversationEvict` is called when watched
|
|
179
179
|
* files change and conversations need to be evicted for reload.
|
|
180
180
|
* `onIdentityChanged` is called when IDENTITY.md changes on disk.
|
|
181
|
-
* `onIdentityIntroChanged` is called when SOUL.md changes and
|
|
182
|
-
*
|
|
181
|
+
* `onIdentityIntroChanged` is called when SOUL.md changes and identity
|
|
182
|
+
* intro subscribers should refetch.
|
|
183
183
|
* `onSkillsChanged` is called after skill directory changes evict
|
|
184
184
|
* conversations.
|
|
185
185
|
*/
|
|
@@ -173,7 +173,6 @@ async function applyForcedCompaction(
|
|
|
173
173
|
const compactionOptions: ContextWindowCompactOptions = {
|
|
174
174
|
force: true,
|
|
175
175
|
minKeepRecentUserTurns: 0,
|
|
176
|
-
targetInputTokensOverride: config.targetTokens,
|
|
177
176
|
};
|
|
178
177
|
|
|
179
178
|
const result = await compactFn(messages, signal, compactionOptions);
|