@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
|
@@ -167,6 +167,7 @@ const mockFailStalledJobs = mock(() => 0);
|
|
|
167
167
|
const mockClaimMemoryJobs = mock(() => []);
|
|
168
168
|
mock.module("../memory/jobs-store.js", () => ({
|
|
169
169
|
claimMemoryJobs: mockClaimMemoryJobs,
|
|
170
|
+
cancelPendingAutomaticConsolidationJobs: mock(() => 0),
|
|
170
171
|
completeMemoryJob: mock(() => {}),
|
|
171
172
|
deferMemoryJob: mock(() => "deferred"),
|
|
172
173
|
EMBED_JOB_TYPES: [],
|
|
@@ -178,7 +179,12 @@ mock.module("../memory/jobs-store.js", () => ({
|
|
|
178
179
|
failStalledJobs: mockFailStalledJobs,
|
|
179
180
|
getMemoryJobCounts: mock(() => ({})),
|
|
180
181
|
hasActiveJobOfType: mock(() => false),
|
|
182
|
+
isAutomaticConsolidationJob: mock(() => true),
|
|
181
183
|
isMemoryEnabled: () => true,
|
|
184
|
+
MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS: {
|
|
185
|
+
automatic: "automatic",
|
|
186
|
+
manual: "manual",
|
|
187
|
+
},
|
|
182
188
|
resetRunningJobsToPending: mock(() => 0),
|
|
183
189
|
SLOW_LLM_JOB_TYPES: [],
|
|
184
190
|
upsertAutoAnalysisJob: mock(() => "job-auto-analysis"),
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* and no session.processing mutation.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
12
|
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Mocks — must be defined before importing the module under test
|
|
@@ -76,7 +78,13 @@ mock.module("../runtime/routes/identity-intro-cache.js", () => ({
|
|
|
76
78
|
getCachedIntro: () => null,
|
|
77
79
|
readWorkspaceIdentityIntro: () => null,
|
|
78
80
|
setCachedIntro: () => {},
|
|
79
|
-
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const assistantFeatureFlags: Record<string, boolean> = {};
|
|
84
|
+
|
|
85
|
+
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
86
|
+
isAssistantFeatureFlagEnabled: (key: string) =>
|
|
87
|
+
assistantFeatureFlags[key] ?? false,
|
|
80
88
|
}));
|
|
81
89
|
|
|
82
90
|
// Mock getOrCreateConversation from conversation-store so the handler
|
|
@@ -123,6 +131,7 @@ import {
|
|
|
123
131
|
ServiceUnavailableError,
|
|
124
132
|
} from "../runtime/routes/errors.js";
|
|
125
133
|
import type { RouteHandlerArgs } from "../runtime/routes/types.js";
|
|
134
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
126
135
|
|
|
127
136
|
// ---------------------------------------------------------------------------
|
|
128
137
|
// Helpers
|
|
@@ -177,6 +186,12 @@ function makeMockSession(
|
|
|
177
186
|
const route = ROUTES.find((r) => r.endpoint === "btw" && r.method === "POST");
|
|
178
187
|
if (!route) throw new Error("btw route not found in ROUTES");
|
|
179
188
|
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
for (const key of Object.keys(assistantFeatureFlags)) {
|
|
191
|
+
delete assistantFeatureFlags[key];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
180
195
|
async function callHandler(
|
|
181
196
|
body: Record<string, unknown>,
|
|
182
197
|
): Promise<{ result: unknown; error?: unknown }> {
|
|
@@ -324,7 +339,25 @@ describe("POST /v1/btw", () => {
|
|
|
324
339
|
expect(options!.config!.modelIntent).toBeUndefined();
|
|
325
340
|
});
|
|
326
341
|
|
|
327
|
-
test("greeting requests
|
|
342
|
+
test("greeting requests return static fallback when the dynamic greetings flag is off", async () => {
|
|
343
|
+
mockGetOrCreateConversation.mockClear();
|
|
344
|
+
|
|
345
|
+
const { result } = await callHandler({
|
|
346
|
+
conversationKey: "greeting",
|
|
347
|
+
content: "Generate a greeting",
|
|
348
|
+
});
|
|
349
|
+
const text = await readStream(result as ReadableStream<Uint8Array>);
|
|
350
|
+
|
|
351
|
+
expect(text).toContain(
|
|
352
|
+
`event: btw_text_delta\ndata: {"text":"What are we working on?"}`,
|
|
353
|
+
);
|
|
354
|
+
expect(text).toContain("event: btw_complete\ndata: {}");
|
|
355
|
+
expect(mockGetOrCreateConversation).not.toHaveBeenCalled();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("greeting requests pass callSite: 'emptyStateGreeting' when the dynamic greetings flag is on", async () => {
|
|
359
|
+
assistantFeatureFlags["empty-state-dynamic-greetings"] = true;
|
|
360
|
+
|
|
328
361
|
const provider = makeMockProvider();
|
|
329
362
|
const session = makeMockSession(provider);
|
|
330
363
|
mockGetOrCreateConversation.mockImplementationOnce(async () => session);
|
|
@@ -356,6 +389,32 @@ describe("POST /v1/btw", () => {
|
|
|
356
389
|
expect(options!.config!.callSite).toBe("identityIntro");
|
|
357
390
|
});
|
|
358
391
|
|
|
392
|
+
test("identity intro requests do not synthesize a static name greeting", async () => {
|
|
393
|
+
const identityPath = join(getWorkspaceDir(), "IDENTITY.md");
|
|
394
|
+
writeFileSync(
|
|
395
|
+
identityPath,
|
|
396
|
+
"# Identity\n\n- **Name:** Example Assistant\n",
|
|
397
|
+
"utf-8",
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const provider = makeMockProvider();
|
|
402
|
+
const session = makeMockSession(provider);
|
|
403
|
+
mockGetOrCreateConversation.mockImplementationOnce(async () => session);
|
|
404
|
+
|
|
405
|
+
const { result } = await callHandler({
|
|
406
|
+
conversationKey: "identity-intro",
|
|
407
|
+
content: "Generate an intro",
|
|
408
|
+
});
|
|
409
|
+
const text = await readStream(result as ReadableStream<Uint8Array>);
|
|
410
|
+
|
|
411
|
+
expect(provider.sendMessage).toHaveBeenCalledTimes(1);
|
|
412
|
+
expect(text).not.toContain("Hi, I'm Example Assistant!");
|
|
413
|
+
} finally {
|
|
414
|
+
rmSync(identityPath, { force: true });
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
359
418
|
// -- No persistence --
|
|
360
419
|
|
|
361
420
|
test("does not persist any messages (addMessage never called)", async () => {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `buildPersistedAssistantContent` stamping native web-search
|
|
3
|
+
* activity (`_activityMetadata`) onto `server_tool_use` blocks at persist time.
|
|
4
|
+
*
|
|
5
|
+
* Native Anthropic web_search resolves mid-stream — `server_tool_complete`
|
|
6
|
+
* fires before `message_complete` — so the captured activity is available when
|
|
7
|
+
* the content is persisted. Unlike external provider tools, a pure-native turn
|
|
8
|
+
* has no `tool_result` and never runs `annotatePersistedAssistantMessage`, so
|
|
9
|
+
* stamping must happen here or the WebSearchProgressCard is lost on a history
|
|
10
|
+
* reopen. Read-side coverage lives in server-history-render.test.ts.
|
|
11
|
+
*/
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
// ── Mock platform (must precede imports that read it) ─────────────────────────
|
|
15
|
+
mock.module("../util/logger.js", () => ({
|
|
16
|
+
getLogger: () =>
|
|
17
|
+
new Proxy({} as Record<string, unknown>, {
|
|
18
|
+
get: () => () => {},
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
mock.module("../config/loader.js", () => ({
|
|
23
|
+
getConfig: () => ({
|
|
24
|
+
skills: {
|
|
25
|
+
entries: {},
|
|
26
|
+
load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
|
|
27
|
+
install: { nodeManager: "npm" },
|
|
28
|
+
allowBundled: null,
|
|
29
|
+
remoteProviders: {
|
|
30
|
+
skillssh: { enabled: true },
|
|
31
|
+
clawhub: { enabled: true },
|
|
32
|
+
},
|
|
33
|
+
remotePolicy: {
|
|
34
|
+
blockSuspicious: true,
|
|
35
|
+
blockMalware: true,
|
|
36
|
+
maxSkillsShRisk: "medium",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
loadConfig: () => ({}),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
44
|
+
addMessage: () => ({ id: "mock-msg-id" }),
|
|
45
|
+
getMessageById: () => null,
|
|
46
|
+
updateMessageContent: () => {},
|
|
47
|
+
provenanceFromTrustContext: () => ({}),
|
|
48
|
+
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
52
|
+
recordRequestLog: () => {},
|
|
53
|
+
backfillMessageIdOnLogs: () => {},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
57
|
+
import { buildPersistedAssistantContent } from "../daemon/conversation-agent-loop-handlers.js";
|
|
58
|
+
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
59
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
60
|
+
|
|
61
|
+
const webSearchActivity: ToolActivityMetadata = {
|
|
62
|
+
webSearch: {
|
|
63
|
+
query: "vellum docs",
|
|
64
|
+
provider: "anthropic-native",
|
|
65
|
+
resultCount: 1,
|
|
66
|
+
durationMs: 88,
|
|
67
|
+
results: [
|
|
68
|
+
{
|
|
69
|
+
rank: 1,
|
|
70
|
+
title: "Vellum",
|
|
71
|
+
url: "https://vellum.ai",
|
|
72
|
+
domain: "vellum.ai",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function findBlockById(
|
|
79
|
+
blocks: ContentBlock[],
|
|
80
|
+
id: string,
|
|
81
|
+
): Record<string, unknown> {
|
|
82
|
+
const block = (blocks as unknown as Array<Record<string, unknown>>).find(
|
|
83
|
+
(b) => b.id === id,
|
|
84
|
+
);
|
|
85
|
+
if (!block) throw new Error(`block ${id} not found`);
|
|
86
|
+
return block;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
describe("buildPersistedAssistantContent — native activityMetadata", () => {
|
|
92
|
+
let activity: Map<string, ToolActivityMetadata>;
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
activity = new Map();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("stamps captured activity onto a pure-native server_tool_use block", () => {
|
|
99
|
+
// GIVEN a turn whose only tool is a native server_tool_use whose activity
|
|
100
|
+
// was captured at server_tool_complete (no external tool_result, so the
|
|
101
|
+
// annotate pass never runs for this turn)
|
|
102
|
+
const nativeId = "srvtu_native_search";
|
|
103
|
+
activity.set(nativeId, webSearchActivity);
|
|
104
|
+
const rawBlocks = [
|
|
105
|
+
{ type: "text", text: "Let me search." },
|
|
106
|
+
{
|
|
107
|
+
type: "server_tool_use",
|
|
108
|
+
id: nativeId,
|
|
109
|
+
name: "web_search",
|
|
110
|
+
input: { query: "vellum docs" },
|
|
111
|
+
},
|
|
112
|
+
] as unknown as ContentBlock[];
|
|
113
|
+
|
|
114
|
+
// WHEN the content is built for persistence
|
|
115
|
+
const built = buildPersistedAssistantContent(rawBlocks, [], activity);
|
|
116
|
+
|
|
117
|
+
// THEN the server_tool_use block carries the native activity verbatim so it
|
|
118
|
+
// survives a history reopen
|
|
119
|
+
const block = findBlockById(built, nativeId);
|
|
120
|
+
expect(block._activityMetadata).toEqual(webSearchActivity);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("leaves a server_tool_use block untouched when no activity was captured", () => {
|
|
124
|
+
// GIVEN a native server_tool_use with no entry in the activity map
|
|
125
|
+
const nativeId = "srvtu_no_activity";
|
|
126
|
+
const rawBlocks = [
|
|
127
|
+
{
|
|
128
|
+
type: "server_tool_use",
|
|
129
|
+
id: nativeId,
|
|
130
|
+
name: "web_search",
|
|
131
|
+
input: { query: "vellum docs" },
|
|
132
|
+
},
|
|
133
|
+
] as unknown as ContentBlock[];
|
|
134
|
+
|
|
135
|
+
// WHEN the content is built for persistence
|
|
136
|
+
const built = buildPersistedAssistantContent(rawBlocks, [], activity);
|
|
137
|
+
|
|
138
|
+
// THEN no _activityMetadata is written
|
|
139
|
+
const block = findBlockById(built, nativeId);
|
|
140
|
+
expect(block._activityMetadata).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("does not stamp external tool_use blocks (handled by the annotate pass)", () => {
|
|
144
|
+
// GIVEN an external tool_use block whose id happens to have captured
|
|
145
|
+
// activity (external activity is stamped later in handleToolResult, not here)
|
|
146
|
+
const externalId = "tu_external_search";
|
|
147
|
+
activity.set(externalId, webSearchActivity);
|
|
148
|
+
const rawBlocks = [
|
|
149
|
+
{
|
|
150
|
+
type: "tool_use",
|
|
151
|
+
id: externalId,
|
|
152
|
+
name: "web_search",
|
|
153
|
+
input: { query: "vellum docs" },
|
|
154
|
+
},
|
|
155
|
+
] as unknown as ContentBlock[];
|
|
156
|
+
|
|
157
|
+
// WHEN the content is built for persistence
|
|
158
|
+
const built = buildPersistedAssistantContent(rawBlocks, [], activity);
|
|
159
|
+
|
|
160
|
+
// THEN the tool_use block is left untouched by this function
|
|
161
|
+
const block = findBlockById(built, externalId);
|
|
162
|
+
expect(block._activityMetadata).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("does not stamp when no activity map is provided", () => {
|
|
166
|
+
// GIVEN a native server_tool_use block AND no activity map argument
|
|
167
|
+
const nativeId = "srvtu_no_map";
|
|
168
|
+
const rawBlocks = [
|
|
169
|
+
{
|
|
170
|
+
type: "server_tool_use",
|
|
171
|
+
id: nativeId,
|
|
172
|
+
name: "web_search",
|
|
173
|
+
input: { query: "vellum docs" },
|
|
174
|
+
},
|
|
175
|
+
] as unknown as ContentBlock[];
|
|
176
|
+
|
|
177
|
+
// WHEN the content is built for persistence without the optional map
|
|
178
|
+
const built = buildPersistedAssistantContent(rawBlocks, []);
|
|
179
|
+
|
|
180
|
+
// THEN no _activityMetadata is written
|
|
181
|
+
const block = findBlockById(built, nativeId);
|
|
182
|
+
expect(block._activityMetadata).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -963,7 +963,7 @@ describe("createVellumCatalogProvider", () => {
|
|
|
963
963
|
expect(slim!.kind).toBe("catalog");
|
|
964
964
|
expect(slim!.origin).toBe("vellum");
|
|
965
965
|
expect(slim!.status).toBe("available");
|
|
966
|
-
expect(slim!.category).toBe("
|
|
966
|
+
expect(slim!.category).toBe("system");
|
|
967
967
|
});
|
|
968
968
|
|
|
969
969
|
test("toSlimSkill returns null for unknown skill", async () => {
|
|
@@ -291,7 +291,7 @@ describe("toSlimSkill", () => {
|
|
|
291
291
|
expect(slim!.kind).toBe("catalog");
|
|
292
292
|
expect(slim!.status).toBe("available");
|
|
293
293
|
expect(slim!.origin).toBe("clawhub");
|
|
294
|
-
expect(slim!.category).toBe("
|
|
294
|
+
expect(slim!.category).toBe("integrations");
|
|
295
295
|
|
|
296
296
|
// Clawhub-specific fields
|
|
297
297
|
const clawhub = slim as unknown as Record<string, unknown>;
|
|
@@ -113,7 +113,7 @@ describe("compaction pipeline", () => {
|
|
|
113
113
|
const args: CompactionArgs = {
|
|
114
114
|
messages: [{ role: "user", content: "hi" }],
|
|
115
115
|
signal: new AbortController().signal,
|
|
116
|
-
options: {
|
|
116
|
+
options: { precomputedEstimate: 1234 },
|
|
117
117
|
};
|
|
118
118
|
|
|
119
119
|
// No middleware registered — the runner invokes the terminal directly.
|
|
@@ -172,34 +172,4 @@ describe("Compaction benchmark", () => {
|
|
|
172
172
|
expect(result.summaryCalls).toBe(1);
|
|
173
173
|
expect(result.summaryCalls).toBe(counter.calls);
|
|
174
174
|
});
|
|
175
|
-
|
|
176
|
-
test("severe pressure triggers compaction even during cooldown", async () => {
|
|
177
|
-
const counter = { calls: 0 };
|
|
178
|
-
const provider = makeSummaryProvider(counter);
|
|
179
|
-
// Use a tighter maxInputTokens so 90 turns exceeds the 95% severe threshold
|
|
180
|
-
const config = {
|
|
181
|
-
...makeConfig(),
|
|
182
|
-
maxInputTokens: 4000,
|
|
183
|
-
targetBudgetRatio: 0.55,
|
|
184
|
-
};
|
|
185
|
-
const manager = new ContextWindowManager({
|
|
186
|
-
provider,
|
|
187
|
-
systemPrompt: "system prompt",
|
|
188
|
-
config,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const messages = makeLongMessages(90);
|
|
192
|
-
const estimated = estimatePromptTokens(messages, "system prompt", {
|
|
193
|
-
providerName: "mock",
|
|
194
|
-
});
|
|
195
|
-
expect(estimated).toBeGreaterThan(config.maxInputTokens * 0.95);
|
|
196
|
-
|
|
197
|
-
// Simulate being within cooldown by setting lastCompactedAt to now
|
|
198
|
-
const result = await manager.maybeCompact(messages, undefined, {
|
|
199
|
-
lastCompactedAt: Date.now(),
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
expect(result.compacted).toBe(true);
|
|
203
|
-
expect(result.summaryCalls).toBeGreaterThan(0);
|
|
204
|
-
});
|
|
205
175
|
});
|
|
@@ -231,7 +231,7 @@ describe("ConfigWatcher workspace file handlers", () => {
|
|
|
231
231
|
expect(evictCallCount).toBe(1);
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
test("SOUL.md change triggers identity intro
|
|
234
|
+
test("SOUL.md change triggers identity intro refetch notification", async () => {
|
|
235
235
|
let introCallCount = 0;
|
|
236
236
|
watcher.start(
|
|
237
237
|
onConversationEvict,
|
|
@@ -108,8 +108,13 @@ mock.module("../workspace/git-service.js", () => ({
|
|
|
108
108
|
}),
|
|
109
109
|
}));
|
|
110
110
|
|
|
111
|
-
// Track all messages persisted to DB
|
|
111
|
+
// Track all messages persisted to DB via addMessage (single-shot writes).
|
|
112
112
|
let persistedMessages: Array<{ role: string; content: string }> = [];
|
|
113
|
+
// Track the latest content written into each reserved row (reserve + update
|
|
114
|
+
// pattern). Tool results persist on arrival and finalize at the loop boundary
|
|
115
|
+
// through this path, so the final value per row id is what lands in the DB.
|
|
116
|
+
let reservedRowContent: Map<string, string> = new Map();
|
|
117
|
+
let reserveCounter = 0;
|
|
113
118
|
|
|
114
119
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
115
120
|
setConversationOriginChannelIfUnset: () => {},
|
|
@@ -139,8 +144,10 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
139
144
|
updateConversationTitle: () => {},
|
|
140
145
|
getMessageById: () => null,
|
|
141
146
|
getLastUserTimestampBefore: () => 0,
|
|
142
|
-
reserveMessage: mock(async () => ({ id:
|
|
143
|
-
updateMessageContent: mock(() => {
|
|
147
|
+
reserveMessage: mock(async () => ({ id: `msg-reserve-${++reserveCounter}` })),
|
|
148
|
+
updateMessageContent: mock((id: string, content: string) => {
|
|
149
|
+
reservedRowContent.set(id, content);
|
|
150
|
+
}),
|
|
144
151
|
}));
|
|
145
152
|
|
|
146
153
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -226,8 +233,11 @@ mock.module("../agent/loop.js", () => ({
|
|
|
226
233
|
isError: false,
|
|
227
234
|
});
|
|
228
235
|
|
|
229
|
-
// Abort happens before second tool
|
|
230
|
-
//
|
|
236
|
+
// Abort happens before second tool. The real AgentLoop synthesizes
|
|
237
|
+
// cancelled results into history AND emits a `cancelled` tool_result
|
|
238
|
+
// event per tool so the orchestrator captures them for persistence and
|
|
239
|
+
// forwards them to the client. tu_1 (already captured via its real
|
|
240
|
+
// tool_result event) wins via the handler's gap-fill guard.
|
|
231
241
|
const resultBlocks: ContentBlock[] = [
|
|
232
242
|
{
|
|
233
243
|
type: "tool_result",
|
|
@@ -243,8 +253,27 @@ mock.module("../agent/loop.js", () => ({
|
|
|
243
253
|
},
|
|
244
254
|
];
|
|
245
255
|
history.push({ role: "user", content: resultBlocks });
|
|
256
|
+
onEvent({
|
|
257
|
+
type: "tool_result",
|
|
258
|
+
toolUseId: "tu_1",
|
|
259
|
+
content: "Cancelled by user",
|
|
260
|
+
isError: true,
|
|
261
|
+
cancelled: true,
|
|
262
|
+
});
|
|
263
|
+
onEvent({
|
|
264
|
+
type: "tool_result",
|
|
265
|
+
toolUseId: "tu_2",
|
|
266
|
+
content: "Cancelled by user",
|
|
267
|
+
isError: true,
|
|
268
|
+
cancelled: true,
|
|
269
|
+
});
|
|
246
270
|
|
|
247
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
history,
|
|
273
|
+
exitReason: null,
|
|
274
|
+
appendedNewMessages: true,
|
|
275
|
+
newMessages: history.slice(messages.length),
|
|
276
|
+
};
|
|
248
277
|
}
|
|
249
278
|
},
|
|
250
279
|
}));
|
|
@@ -295,6 +324,8 @@ function makeConversation(): Conversation {
|
|
|
295
324
|
describe("abort tool result persistence", () => {
|
|
296
325
|
test("abort after first of multiple tool calls still persists all required tool_result blocks", async () => {
|
|
297
326
|
persistedMessages = [];
|
|
327
|
+
reservedRowContent = new Map();
|
|
328
|
+
reserveCounter = 0;
|
|
298
329
|
const conversation = makeConversation();
|
|
299
330
|
await conversation.loadFromDb();
|
|
300
331
|
|
|
@@ -303,19 +334,26 @@ describe("abort tool result persistence", () => {
|
|
|
303
334
|
attachments: [],
|
|
304
335
|
});
|
|
305
336
|
|
|
306
|
-
// Find
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
337
|
+
// Find persisted rows whose final content contains tool_result blocks.
|
|
338
|
+
// Tool results persist on arrival into one grouped row and finalize at the
|
|
339
|
+
// abort/loop boundary; the latest content per reserved row id is what lands
|
|
340
|
+
// in the DB, so one entry per row models the persisted state (a second
|
|
341
|
+
// entry would mean the batch was wrongly split across rows).
|
|
342
|
+
const toolResultUserMessages = Array.from(reservedRowContent.values())
|
|
343
|
+
.map((content) => ({ content }))
|
|
344
|
+
.filter((m) => {
|
|
345
|
+
try {
|
|
346
|
+
const content = JSON.parse(m.content);
|
|
347
|
+
return (
|
|
348
|
+
Array.isArray(content) &&
|
|
349
|
+
content.some(
|
|
350
|
+
(b: Record<string, unknown>) => b.type === "tool_result",
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
} catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
319
357
|
|
|
320
358
|
// There should be at least one persisted user message with tool_results
|
|
321
359
|
expect(toolResultUserMessages.length).toBeGreaterThanOrEqual(1);
|
|
@@ -109,10 +109,14 @@ import { runAgentLoopImpl } from "../daemon/conversation-agent-loop.js";
|
|
|
109
109
|
function makeCtx(
|
|
110
110
|
overrides: Partial<Context> = {},
|
|
111
111
|
): AgentLoopConversationContext {
|
|
112
|
+
let processing = true;
|
|
112
113
|
return {
|
|
113
114
|
conversationId: "conv-123",
|
|
114
115
|
messages: [{ role: "user", content: [{ type: "text", text: "hello" }] }],
|
|
115
|
-
|
|
116
|
+
isProcessing: () => processing,
|
|
117
|
+
setProcessing: (value: boolean) => {
|
|
118
|
+
processing = value;
|
|
119
|
+
},
|
|
116
120
|
abortController: new AbortController(),
|
|
117
121
|
currentRequestId: "req-123",
|
|
118
122
|
agentLoop: {
|
|
@@ -217,7 +221,7 @@ describe("runAgentLoopImpl disk pressure gate", () => {
|
|
|
217
221
|
"error_terminal",
|
|
218
222
|
{ anchor: "global", requestId: "req-123" },
|
|
219
223
|
]);
|
|
220
|
-
expect(ctx.
|
|
224
|
+
expect(ctx.isProcessing()).toBe(false);
|
|
221
225
|
expect(ctx.abortController).toBeNull();
|
|
222
226
|
expect(ctx.currentRequestId).toBeUndefined();
|
|
223
227
|
expect(drainQueue).toHaveBeenCalledWith("loop_complete");
|
|
@@ -96,6 +96,9 @@ mock.module("../config/loader.js", () => ({
|
|
|
96
96
|
mock.module("../context/token-estimator.js", () => ({
|
|
97
97
|
estimatePromptTokens: () => 1000,
|
|
98
98
|
estimatePromptTokensRaw: () => 1000,
|
|
99
|
+
// The preflight overflow gate calls this calibrated wrapper directly; stub
|
|
100
|
+
// it alongside the others so it returns the same small value.
|
|
101
|
+
estimatePromptTokensWithTools: () => 1000,
|
|
99
102
|
estimateToolsTokens: () => 0,
|
|
100
103
|
}));
|
|
101
104
|
|
|
@@ -220,10 +223,6 @@ mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
|
220
223
|
blocks: {},
|
|
221
224
|
}),
|
|
222
225
|
stripInjectionsForCompaction: (msgs: Message[]) => msgs,
|
|
223
|
-
findLastInjectedNowContent: () => null,
|
|
224
|
-
readNowScratchpad: () => null,
|
|
225
|
-
readPkbContext: () => null,
|
|
226
|
-
getPkbAutoInjectList: () => [],
|
|
227
226
|
isSlackChannelConversation: () => false,
|
|
228
227
|
getSlackCompactionWatermarkForPrefix: () => null,
|
|
229
228
|
loadSlackChronologicalContext: () => null,
|
|
@@ -392,6 +391,12 @@ function makeCtx(
|
|
|
392
391
|
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
393
392
|
] as Message[],
|
|
394
393
|
processing: true,
|
|
394
|
+
isProcessing(this: { processing: boolean }) {
|
|
395
|
+
return this.processing;
|
|
396
|
+
},
|
|
397
|
+
setProcessing(this: { processing: boolean }, value: boolean) {
|
|
398
|
+
this.processing = value;
|
|
399
|
+
},
|
|
395
400
|
abortController: new AbortController(),
|
|
396
401
|
currentRequestId: "test-req",
|
|
397
402
|
|
|
@@ -494,6 +499,7 @@ function makeCtx(
|
|
|
494
499
|
injectedTokens: 0,
|
|
495
500
|
}),
|
|
496
501
|
retrackCachedNodes: () => {},
|
|
502
|
+
recordPkbQueryVectors: () => {},
|
|
497
503
|
} as unknown as AgentLoopConversationContext["graphMemory"],
|
|
498
504
|
|
|
499
505
|
...overrides,
|