@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `memoryRetrieval` post-compaction hook.
|
|
3
|
+
*
|
|
4
|
+
* After the agent loop compacts a conversation mid-turn it must re-apply the
|
|
5
|
+
* runtime injections compaction stripped — the NOW.md scratchpad, PKB context,
|
|
6
|
+
* memory-v2 static block, workspace top-level context, and Slack chronological
|
|
7
|
+
* snapshot — onto the compacted history before the turn continues. This hook
|
|
8
|
+
* is the memory system's home for that transform: it receives the message
|
|
9
|
+
* history plus the resolved runtime-injection options and returns the edited
|
|
10
|
+
* history (and the blocks it captured), with no dependency on the agent loop's
|
|
11
|
+
* closure state.
|
|
12
|
+
*
|
|
13
|
+
* It re-applies the runtime injections via {@link applyRuntimeInjections},
|
|
14
|
+
* re-tracks the memory graph's cached nodes against the re-injected history,
|
|
15
|
+
* and converts now-historical `web_search_tool_result` blocks to text so their
|
|
16
|
+
* expired `encrypted_content` tokens are not replayed. The remaining
|
|
17
|
+
* orchestrator-side step (the post-injection bookkeeping the loop records) is
|
|
18
|
+
* expected to migrate here as the hook subsumes the loop's re-injection
|
|
19
|
+
* ceremony.
|
|
20
|
+
*
|
|
21
|
+
* The memory graph handle is sourced internally from the plugin's own
|
|
22
|
+
* conversation-keyed registry ({@link getLiveGraphMemory}) rather than being
|
|
23
|
+
* threaded in by the loop — it is memory-retrieval-specific state, not
|
|
24
|
+
* something the generic loop or the shared {@link TurnContext} should carry.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
applyRuntimeInjections,
|
|
29
|
+
type RuntimeInjectionOptions,
|
|
30
|
+
type RuntimeInjectionResult,
|
|
31
|
+
} from "../../../../daemon/conversation-runtime-assembly.js";
|
|
32
|
+
import { stripHistoricalWebSearchResults } from "../../../../daemon/web-search-history.js";
|
|
33
|
+
import { getLiveGraphMemory } from "../../../../memory/graph/conversation-graph-memory.js";
|
|
34
|
+
import type { PluginLogger } from "../../../../plugin-api/types.js";
|
|
35
|
+
import type { Message } from "../../../../providers/types.js";
|
|
36
|
+
import type { TurnContext } from "../../../types.js";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The slice of the hook's context the agent loop supplies from its own working
|
|
40
|
+
* state. Re-injection inputs migrate loop-ward by growing this type; the loop
|
|
41
|
+
* hands the hook an object of this shape via
|
|
42
|
+
* {@link MidLoopCompaction.postCompactionHook}.
|
|
43
|
+
*/
|
|
44
|
+
export interface PostCompactionHookInput {
|
|
45
|
+
/** Compacted message history to re-inject onto. */
|
|
46
|
+
history: Message[];
|
|
47
|
+
/** Per-turn conversation context forwarded to the injector chain. */
|
|
48
|
+
turnContext?: TurnContext;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Everything the hook needs in a single context: the loop-supplied
|
|
53
|
+
* {@link PostCompactionHookInput}, the resolved {@link RuntimeInjectionOptions}
|
|
54
|
+
* (spread top-level so each field stays individually addressable), and the
|
|
55
|
+
* conversation-scoped state the options bag cannot carry (actor trust and a
|
|
56
|
+
* turn-scoped logger). The memory graph handle is not part of this context —
|
|
57
|
+
* the hook sources it internally via {@link getLiveGraphMemory}.
|
|
58
|
+
*/
|
|
59
|
+
export interface PostCompactContext
|
|
60
|
+
extends RuntimeInjectionOptions, PostCompactionHookInput {
|
|
61
|
+
/** True when the actor for this turn is trusted (guardian-class). */
|
|
62
|
+
isTrustedActor: boolean;
|
|
63
|
+
/** Turn-scoped logger for diagnostics emitted while re-injecting. */
|
|
64
|
+
logger: PluginLogger;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default async function postCompactReinject(
|
|
68
|
+
ctx: PostCompactContext,
|
|
69
|
+
): Promise<RuntimeInjectionResult> {
|
|
70
|
+
const { history, isTrustedActor, logger, ...options } = ctx;
|
|
71
|
+
const result = await applyRuntimeInjections(history, options);
|
|
72
|
+
// Re-track the nodes the memory graph last injected so they survive against
|
|
73
|
+
// the re-injected history. Untrusted actors and minimal-mode turns never
|
|
74
|
+
// received a memory-graph injection, so there is nothing to re-track. The
|
|
75
|
+
// live graph handle is looked up from the plugin's own registry by the
|
|
76
|
+
// turn's conversation id — the same instance the turn's retrieval mutated,
|
|
77
|
+
// so re-tracking sees the real cached-node state.
|
|
78
|
+
if (isTrustedActor && options.mode !== "minimal") {
|
|
79
|
+
getLiveGraphMemory(
|
|
80
|
+
options.turnContext?.conversationId,
|
|
81
|
+
)?.retrackCachedNodes();
|
|
82
|
+
}
|
|
83
|
+
const strip = stripHistoricalWebSearchResults(result.messages);
|
|
84
|
+
if (strip.stats.blocksStripped > 0) {
|
|
85
|
+
logger.info(
|
|
86
|
+
{ phase: "mid-loop-compact", ...strip.stats },
|
|
87
|
+
"Converted historical web_search_tool_result blocks to text summaries",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return { ...result, messages: strip.messages };
|
|
91
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `user-prompt-submit-temp` hook: runs the memory-graph retrieval the
|
|
3
|
+
* agent loop needs before building a turn's runtime-injection block.
|
|
4
|
+
*
|
|
5
|
+
* **Memory graph** via {@link ConversationGraphMemory.prepareMemory} —
|
|
6
|
+
* dispatches to context-load or per-turn retrieval depending on initialization
|
|
7
|
+
* state; gated on the actor being trusted (guardian).
|
|
8
|
+
*
|
|
9
|
+
* The hook also owns the retrieval's side effects — persisting the injected
|
|
10
|
+
* block onto the user message's metadata, writing the recall log, and emitting
|
|
11
|
+
* the `memory_recalled` event — so the loop only consumes the turn-scoped
|
|
12
|
+
* `latestMessages` written back onto the context. The PKB query-vector pair is
|
|
13
|
+
* recorded on the conversation's graph handle for the PKB-reminder injector to
|
|
14
|
+
* read back. PKB context and NOW.md are sourced directly by their injectors
|
|
15
|
+
* (gated on block presence), not produced here.
|
|
16
|
+
*
|
|
17
|
+
* This fires at the early "prompt submitted, before context assembly" moment,
|
|
18
|
+
* distinct from the canonical late `user-prompt-submit` hook (history repair,
|
|
19
|
+
* title): memory's outputs feed the injection and overflow-reduction transforms
|
|
20
|
+
* that run between the two moments. The `-temp` suffix marks this as a
|
|
21
|
+
* transitional staging point that folds into `user-prompt-submit` once
|
|
22
|
+
* compaction is cleared from the gap between the two call sites.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { PluginHookFn } from "@vellumai/plugin-api";
|
|
26
|
+
import type { Logger } from "pino";
|
|
27
|
+
|
|
28
|
+
import type { AssistantConfig } from "../../../../config/schema.js";
|
|
29
|
+
import type { ServerMessage } from "../../../../daemon/message-protocol.js";
|
|
30
|
+
import type { MemoryRecalled } from "../../../../daemon/message-types/memory.js";
|
|
31
|
+
import { updateMessageMetadata } from "../../../../memory/conversation-crud.js";
|
|
32
|
+
import type { ConversationGraphMemory } from "../../../../memory/graph/conversation-graph-memory.js";
|
|
33
|
+
import { recordMemoryRecallLog } from "../../../../memory/memory-recall-log-store.js";
|
|
34
|
+
import type { Message } from "../../../../providers/types.js";
|
|
35
|
+
import type { GraphMemoryResult } from "../../../types.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Context threaded through the `user-prompt-submit-temp` hook. The readonly
|
|
39
|
+
* fields carry the conversation-scoped state the retriever needs (graph
|
|
40
|
+
* handle, event sink, abort signal); the output fields are populated by the
|
|
41
|
+
* hook and read back by the agent loop. `latestMessages` straddles both: the
|
|
42
|
+
* loop seeds it with the pre-injection array and the hook overwrites it with
|
|
43
|
+
* the injected result.
|
|
44
|
+
*/
|
|
45
|
+
export interface MemoryRetrievalHookContext {
|
|
46
|
+
/** Per-conversation memory graph handle. */
|
|
47
|
+
readonly graphMemory: ConversationGraphMemory;
|
|
48
|
+
/** Assistant config snapshot. */
|
|
49
|
+
readonly config: AssistantConfig;
|
|
50
|
+
/** Event sink used by the graph retriever and `memory_recalled` emission. */
|
|
51
|
+
readonly onEvent: (msg: ServerMessage) => void;
|
|
52
|
+
/** True when the actor for this turn is trusted (guardian-class). */
|
|
53
|
+
readonly isTrustedActor: boolean;
|
|
54
|
+
/** Conversation the turn belongs to — keys the recall-log row. */
|
|
55
|
+
readonly conversationId: string;
|
|
56
|
+
/** User message the injected memory block is persisted onto. */
|
|
57
|
+
readonly userMessageId: string;
|
|
58
|
+
/** Turn-scoped logger for non-fatal persistence warnings. */
|
|
59
|
+
readonly logger: Logger;
|
|
60
|
+
/**
|
|
61
|
+
* Per-turn abort signal forwarded to `prepareMemory`. An external cancel
|
|
62
|
+
* aborts the underlying retrieval instead of letting it run to completion.
|
|
63
|
+
*/
|
|
64
|
+
readonly signal: AbortSignal;
|
|
65
|
+
/**
|
|
66
|
+
* Working message list for the turn. Seeded by the loop with the
|
|
67
|
+
* pre-injection messages and consumed as the retrieval input; the hook
|
|
68
|
+
* overwrites it with the memory-graph block injected, or leaves it
|
|
69
|
+
* unchanged when no graph retrieval ran (untrusted actor, or a no-op
|
|
70
|
+
* retrieval). Read back by the loop.
|
|
71
|
+
*/
|
|
72
|
+
latestMessages: Message[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Persist and broadcast the retrieval's side effects: the injected block on
|
|
77
|
+
* the user message's metadata (so it survives reloads), a recall-log row, and
|
|
78
|
+
* the `memory_recalled` debug event. All three are best-effort — a failure to
|
|
79
|
+
* persist must not abort the turn.
|
|
80
|
+
*/
|
|
81
|
+
function recordRecallSideEffects(
|
|
82
|
+
graphResult: GraphMemoryResult,
|
|
83
|
+
ctx: MemoryRetrievalHookContext,
|
|
84
|
+
): void {
|
|
85
|
+
// Persist the injected block text in message metadata so it survives
|
|
86
|
+
// conversation reloads (eviction, restart, fork). loadFromDb re-injects
|
|
87
|
+
// from metadata.
|
|
88
|
+
if (graphResult.injectedBlockText) {
|
|
89
|
+
try {
|
|
90
|
+
updateMessageMetadata(ctx.userMessageId, {
|
|
91
|
+
memoryInjectedBlock: graphResult.injectedBlockText,
|
|
92
|
+
});
|
|
93
|
+
} catch (err) {
|
|
94
|
+
ctx.logger.warn(
|
|
95
|
+
{ err },
|
|
96
|
+
"Failed to persist memory injection to metadata (non-fatal)",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const m = graphResult.metrics;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
recordMemoryRecallLog({
|
|
105
|
+
conversationId: ctx.conversationId,
|
|
106
|
+
enabled: true,
|
|
107
|
+
degraded: false,
|
|
108
|
+
provider: m?.embeddingProvider ?? undefined,
|
|
109
|
+
model: m?.embeddingModel ?? undefined,
|
|
110
|
+
semanticHits: m?.semanticHits ?? 0,
|
|
111
|
+
mergedCount: m?.mergedCount ?? 0,
|
|
112
|
+
selectedCount: m?.selectedCount ?? 0,
|
|
113
|
+
tier1Count: m?.tier1Count ?? 0,
|
|
114
|
+
tier2Count: m?.tier2Count ?? 0,
|
|
115
|
+
hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
|
|
116
|
+
sparseVectorUsed: m?.sparseVectorUsed ?? false,
|
|
117
|
+
injectedTokens: graphResult.injectedTokens,
|
|
118
|
+
latencyMs: graphResult.latencyMs,
|
|
119
|
+
topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
|
|
120
|
+
key: c.nodeId,
|
|
121
|
+
type: c.type,
|
|
122
|
+
kind: "graph",
|
|
123
|
+
finalScore: c.score,
|
|
124
|
+
semantic: c.semanticSimilarity,
|
|
125
|
+
recency: c.recencyBoost,
|
|
126
|
+
})),
|
|
127
|
+
injectedText: graphResult.injectedBlockText ?? undefined,
|
|
128
|
+
reason: `graph:${graphResult.mode}`,
|
|
129
|
+
queryContext: m?.queryContext ?? undefined,
|
|
130
|
+
});
|
|
131
|
+
} catch (err) {
|
|
132
|
+
ctx.logger.warn({ err }, "Failed to persist memory recall log (non-fatal)");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (m) {
|
|
136
|
+
const memoryRecalledEvent: MemoryRecalled = {
|
|
137
|
+
type: "memory_recalled",
|
|
138
|
+
provider: m.embeddingProvider ?? "unknown",
|
|
139
|
+
model: m.embeddingModel ?? "unknown",
|
|
140
|
+
semanticHits: m.semanticHits,
|
|
141
|
+
mergedCount: m.mergedCount,
|
|
142
|
+
selectedCount: m.selectedCount,
|
|
143
|
+
tier1Count: m.tier1Count,
|
|
144
|
+
tier2Count: m.tier2Count,
|
|
145
|
+
hybridSearchLatencyMs: m.hybridSearchLatencyMs,
|
|
146
|
+
sparseVectorUsed: m.sparseVectorUsed,
|
|
147
|
+
injectedTokens: graphResult.injectedTokens,
|
|
148
|
+
latencyMs: graphResult.latencyMs,
|
|
149
|
+
topCandidates: m.topCandidates.map((c) => ({
|
|
150
|
+
key: c.nodeId,
|
|
151
|
+
type: c.type,
|
|
152
|
+
kind: "graph",
|
|
153
|
+
finalScore: c.score,
|
|
154
|
+
semantic: c.semanticSimilarity,
|
|
155
|
+
recency: c.recencyBoost,
|
|
156
|
+
})),
|
|
157
|
+
};
|
|
158
|
+
ctx.onEvent(memoryRecalledEvent);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Run the default retrieval, writing `latestMessages` back onto the context and
|
|
164
|
+
* recording the PKB query-vector pair on the graph handle. Skips the
|
|
165
|
+
* memory-graph call entirely (leaving `latestMessages` as the seeded input
|
|
166
|
+
* messages and no query pair recorded) when the actor is not trusted.
|
|
167
|
+
*
|
|
168
|
+
* Memory retrieval blocks the turn — there is no soft timeout here. Memory is
|
|
169
|
+
* critical context, and silently dropping it produces a worse outcome than a
|
|
170
|
+
* slower turn. Cancellation still works via `ctx.signal`, which is threaded
|
|
171
|
+
* into `prepareMemory`.
|
|
172
|
+
*/
|
|
173
|
+
const userPromptSubmitMemoryRetrieval: PluginHookFn<
|
|
174
|
+
MemoryRetrievalHookContext
|
|
175
|
+
> = async (ctx) => {
|
|
176
|
+
if (!ctx.isTrustedActor) {
|
|
177
|
+
// Untrusted actors skip memory-graph retrieval entirely.
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const graphResult = await ctx.graphMemory.prepareMemory(
|
|
182
|
+
ctx.latestMessages,
|
|
183
|
+
ctx.config,
|
|
184
|
+
ctx.signal,
|
|
185
|
+
ctx.onEvent,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
recordRecallSideEffects(graphResult, ctx);
|
|
189
|
+
|
|
190
|
+
ctx.latestMessages = graphResult.runMessages;
|
|
191
|
+
// Select dense+sparse as a matched pair so RRF fusion combines two signals
|
|
192
|
+
// aligned to the same query text:
|
|
193
|
+
// 1. Context-load with a user query: user-query dense + user-query sparse
|
|
194
|
+
// — the cleanest pairing.
|
|
195
|
+
// 2. Otherwise (context-load without a user query, or per-turn): whatever
|
|
196
|
+
// `queryVector` / `sparseVector` the retriever produced, which are
|
|
197
|
+
// themselves co-aligned (both summary-derived in context-load, both
|
|
198
|
+
// user-last-message-derived in per-turn).
|
|
199
|
+
// Never pair a user-query dense with a summary-aligned sparse.
|
|
200
|
+
// The PKB-reminder injector reads this pair back off the same graph handle
|
|
201
|
+
// (looked up by conversation id) rather than receiving it threaded through
|
|
202
|
+
// the agent loop.
|
|
203
|
+
if (graphResult.userQueryVector) {
|
|
204
|
+
ctx.graphMemory.recordPkbQueryVectors(
|
|
205
|
+
graphResult.userQueryVector,
|
|
206
|
+
graphResult.userQuerySparseVector,
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
ctx.graphMemory.recordPkbQueryVectors(
|
|
210
|
+
graphResult.queryVector,
|
|
211
|
+
graphResult.sparseVector,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default userPromptSubmitMemoryRetrieval;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assembled runtime injector chain.
|
|
3
|
+
*
|
|
4
|
+
* Injection is not a plugin contribution: the first-party injectors are
|
|
5
|
+
* imported directly and sorted once, by ascending `order`, into the single
|
|
6
|
+
* sequence `applyRuntimeInjections` walks each turn. This co-locates the
|
|
7
|
+
* chain with the memory-retrieval domain it serves, rather than aggregating
|
|
8
|
+
* injectors out of the plugin registry.
|
|
9
|
+
*
|
|
10
|
+
* The chain combines the default injectors ({@link defaultInjectors}) with the
|
|
11
|
+
* memory-v3 injector ({@link memoryV3Injector}). The sort mirrors the previous
|
|
12
|
+
* registry aggregation (`Array.prototype.sort` is stable, so injectors sharing
|
|
13
|
+
* an `order` keep their listed order), so the produced sequence — and therefore
|
|
14
|
+
* the injected content — is identical.
|
|
15
|
+
*
|
|
16
|
+
* The chain is assembled lazily on first access and memoized, so the sort runs
|
|
17
|
+
* once per process rather than per turn and module evaluation stays free of any
|
|
18
|
+
* ordering assumptions about when `defaultInjectors` finishes initializing.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { memoryV3Injector } from "../../../memory/v3/shadow-plugin.js";
|
|
22
|
+
import type { Injector } from "../../types.js";
|
|
23
|
+
import { defaultInjectors } from "../injectors/register.js";
|
|
24
|
+
|
|
25
|
+
let cachedChain: Injector[] | null = null;
|
|
26
|
+
|
|
27
|
+
/** The order-sorted runtime injector chain, assembled once and memoized. */
|
|
28
|
+
export function getInjectorChain(): Injector[] {
|
|
29
|
+
if (cachedChain === null) {
|
|
30
|
+
cachedChain = [...defaultInjectors, memoryV3Injector].sort(
|
|
31
|
+
(a, b) => a.order - b.order,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return cachedChain;
|
|
35
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `stop` hook: triggers the second-pass conversation-title
|
|
3
|
+
* regeneration once a conversation has accumulated enough context.
|
|
4
|
+
*
|
|
5
|
+
* The first title is generated from the opening prompt alone (see
|
|
6
|
+
* `./user-prompt-submit.ts`). After a few exchanges the conversation's real
|
|
7
|
+
* topic is usually clearer, so a single second pass re-titles using the most
|
|
8
|
+
* recent messages. This hook is the trigger — it fires the regeneration when
|
|
9
|
+
* the conversation reaches its third user turn — and delegates the title
|
|
10
|
+
* itself to the service (`memory/conversation-title-service.ts`), which
|
|
11
|
+
* re-checks that the title is still auto-generated, resolves the title
|
|
12
|
+
* provider, persists, and broadcasts the `conversation_title_updated` /
|
|
13
|
+
* `sync_changed` events.
|
|
14
|
+
*
|
|
15
|
+
* Turn count is read from history rather than an external counter: the number
|
|
16
|
+
* of genuine user prompts — user-role messages that aren't purely tool results
|
|
17
|
+
* — is the conversation's turn number. Deriving it from history keeps the hook
|
|
18
|
+
* stateless and means a mid-run array rewrite (compaction) can't invalidate it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
|
|
22
|
+
|
|
23
|
+
import { getConfig } from "../../../../config/loader.js";
|
|
24
|
+
import { queueRegenerateConversationTitle } from "../../../../memory/conversation-title-service.js";
|
|
25
|
+
import type { Message } from "../../../../providers/types.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* User turn at which the second title pass fires. Matches the
|
|
29
|
+
* `conversations.skipAutoRetitling` opt-out, documented as skipping the
|
|
30
|
+
* regeneration "that fires after the third user turn".
|
|
31
|
+
*/
|
|
32
|
+
const SECOND_PASS_USER_TURN = 3;
|
|
33
|
+
|
|
34
|
+
/** A user-role message carrying only tool results, not a fresh prompt. */
|
|
35
|
+
function isToolResultMessage(message: Message): boolean {
|
|
36
|
+
return (
|
|
37
|
+
message.role === "user" &&
|
|
38
|
+
message.content.length > 0 &&
|
|
39
|
+
message.content.every((block) => block.type === "tool_result")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Count of genuine user prompts in history — the conversation's turn number. */
|
|
44
|
+
function countUserTurns(messages: ReadonlyArray<Message>): number {
|
|
45
|
+
let turns = 0;
|
|
46
|
+
for (const message of messages) {
|
|
47
|
+
if (message.role === "user" && !isToolResultMessage(message)) turns++;
|
|
48
|
+
}
|
|
49
|
+
return turns;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stop: PluginHookFn<StopContext> = async (ctx) => {
|
|
53
|
+
// Only re-title at a genuine turn end. A `"continue"` decision means an
|
|
54
|
+
// earlier hook is re-querying the model (e.g. an empty-response nudge), so
|
|
55
|
+
// defer to the eventual terminal stop.
|
|
56
|
+
if (ctx.decision !== "stop") return;
|
|
57
|
+
|
|
58
|
+
if (getConfig().conversations.skipAutoRetitling) return;
|
|
59
|
+
|
|
60
|
+
if (countUserTurns(ctx.messages) !== SECOND_PASS_USER_TURN) return;
|
|
61
|
+
|
|
62
|
+
const { conversationId } = ctx;
|
|
63
|
+
// Deferred to a later macrotask so the just-completed turn lands first. The
|
|
64
|
+
// hook fires at the stop boundary, before the loop appends the turn's
|
|
65
|
+
// assistant reply to history and emits `message_complete` (which persists
|
|
66
|
+
// it). The service regenerates from the most recent stored messages, so it
|
|
67
|
+
// must run after the reply is persisted to reflect it. The service is itself
|
|
68
|
+
// fire-and-forget and re-checks replaceability, owning provider resolution,
|
|
69
|
+
// persistence, and the resulting broadcast.
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
queueRegenerateConversationTitle({ conversationId });
|
|
72
|
+
}, 0);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default stop;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `user-prompt-submit` hook: kicks off conversation-title generation
|
|
3
|
+
* from the submitted prompt.
|
|
4
|
+
*
|
|
5
|
+
* Title generation is a self-contained side effect that only needs the user's
|
|
6
|
+
* prompt as context, so it belongs at the prompt-submit boundary rather than
|
|
7
|
+
* threaded through the agent loop. The hook is a pure trigger — it schedules
|
|
8
|
+
* the work and returns; persistence and the resulting
|
|
9
|
+
* `conversation_title_updated` / `sync_changed` broadcast are owned by the
|
|
10
|
+
* title service (see `memory/conversation-title-service.ts`).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
PluginHookFn,
|
|
15
|
+
UserPromptSubmitContext,
|
|
16
|
+
} from "@vellumai/plugin-api";
|
|
17
|
+
|
|
18
|
+
import { queueGenerateConversationTitle } from "../../../../memory/conversation-title-service.js";
|
|
19
|
+
|
|
20
|
+
const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
|
|
21
|
+
// Deferred to a later macrotask so the main agent-loop LLM request is
|
|
22
|
+
// issued first; on strict single-slot provider configs this keeps the
|
|
23
|
+
// background title call from claiming the rate-limit slot ahead of the
|
|
24
|
+
// user-visible response. The title service is itself fire-and-forget and
|
|
25
|
+
// re-checks title replaceability before making any LLM call, so an
|
|
26
|
+
// already-titled conversation incurs no generation.
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
queueGenerateConversationTitle({
|
|
29
|
+
conversationId: ctx.conversationId,
|
|
30
|
+
userMessage: ctx.prompt,
|
|
31
|
+
});
|
|
32
|
+
}, 0);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default userPromptSubmit;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "default-title-generate",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"description": "First-party default plugin
|
|
4
|
+
"description": "First-party default plugin that triggers conversation-title generation from the user-prompt-submit hook.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default `
|
|
2
|
+
* Default `title-generate` plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Contributes two hooks, both pure triggers that delegate the title work to
|
|
5
|
+
* the service (`memory/conversation-title-service.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - `user-prompt-submit` (`./hooks/user-prompt-submit.ts`) — first-pass title
|
|
8
|
+
* generation from the submitted prompt.
|
|
9
|
+
* - `stop` (`./hooks/stop.ts`) — second-pass regeneration once the
|
|
10
|
+
* conversation reaches its third user turn, for a title that reflects the
|
|
11
|
+
* established topic.
|
|
12
|
+
*
|
|
13
|
+
* Persistence and the resulting `conversation_title_updated` / `sync_changed`
|
|
14
|
+
* broadcast are owned by the title service.
|
|
9
15
|
*
|
|
10
16
|
* Registered via a side-effect import from
|
|
11
17
|
* `daemon/external-plugins-bootstrap.ts` so it is present in the registry
|
|
@@ -13,23 +19,17 @@
|
|
|
13
19
|
*/
|
|
14
20
|
|
|
15
21
|
import { type Plugin } from "../../types.js";
|
|
22
|
+
import stop from "./hooks/stop.js";
|
|
23
|
+
import userPromptSubmit from "./hooks/user-prompt-submit.js";
|
|
16
24
|
import pkg from "./package.json" with { type: "json" };
|
|
17
25
|
|
|
18
|
-
/**
|
|
19
|
-
* Default titleGenerate plugin. Declares no middleware — it exists purely
|
|
20
|
-
* to negotiate the `titleGenerateApi` capability so bootstrap has a record
|
|
21
|
-
* that the assistant runtime exposes this pipeline.
|
|
22
|
-
*
|
|
23
|
-
* The terminal handler (`./terminal.ts`) is supplied at the call site in
|
|
24
|
-
* `conversation-agent-loop.ts` rather than through `middleware.titleGenerate`,
|
|
25
|
-
* because a default middleware would short-circuit user-registered middleware
|
|
26
|
-
* by always running first in onion order. Keeping the terminal outside the
|
|
27
|
-
* middleware chain lets user plugins observe/transform/short-circuit the
|
|
28
|
-
* call without competing with an assistant-owned default middleware.
|
|
29
|
-
*/
|
|
30
26
|
export const defaultTitleGeneratePlugin: Plugin = {
|
|
31
27
|
manifest: {
|
|
32
28
|
name: pkg.name,
|
|
33
29
|
version: pkg.version,
|
|
34
30
|
},
|
|
31
|
+
hooks: {
|
|
32
|
+
"user-prompt-submit": userPromptSubmit,
|
|
33
|
+
stop,
|
|
34
|
+
},
|
|
35
35
|
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `post-tool-use` hook: when a tool result carries `is_error`, set
|
|
3
|
+
* `additionalContext` with a system-notice that coaches the model to either
|
|
4
|
+
* retry with corrected parameters (for recoverable errors) or report the
|
|
5
|
+
* failure to the user (for unrecoverable ones).
|
|
6
|
+
*
|
|
7
|
+
* The coaching is delivered via `additionalContext`, not by mutating the tool
|
|
8
|
+
* result's `content`. The loop appends it to the provider-bound history as a
|
|
9
|
+
* separate block after the tool_result event is emitted, so the model sees the
|
|
10
|
+
* guidance while the client-facing and persisted tool output stay the tool's
|
|
11
|
+
* actual result. This mirrors how Claude Code (`additionalContext`) and Codex
|
|
12
|
+
* (`additional_contexts`) surface PostToolUse feedback as separate context
|
|
13
|
+
* rather than rewriting the tool response.
|
|
14
|
+
*
|
|
15
|
+
* The coaching is bounded per tool: once a single tool has failed
|
|
16
|
+
* `MAX_CONSECUTIVE_ERROR_NUDGES` times in a row the notice is dropped — the
|
|
17
|
+
* error is likely not something the model can fix on its own, and continuing
|
|
18
|
+
* to coach a retry only burns tokens. The consecutive-failure count is derived
|
|
19
|
+
* from the conversation history (the trailing run of error results for this
|
|
20
|
+
* tool name, plus the current one) rather than a loop-held counter, so the
|
|
21
|
+
* guard survives mid-run compaction rewriting the history array. A successful
|
|
22
|
+
* result for the tool resets its streak.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
|
|
26
|
+
|
|
27
|
+
import type { Message } from "../../../../providers/types.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Canonical tool-error coaching text. Kept as a module-level constant so tests
|
|
31
|
+
* and plugins that wrap the default can match it without duplicating the
|
|
32
|
+
* string.
|
|
33
|
+
*
|
|
34
|
+
* This is shown to the model as provider-only context, not the user. Edits
|
|
35
|
+
* here affect retry behavior but not end-user UX directly.
|
|
36
|
+
*/
|
|
37
|
+
export const TOOL_ERROR_NUDGE_TEXT =
|
|
38
|
+
"<system_notice>This tool call returned an error. If the error looks recoverable (e.g. missing or invalid parameters), fix the parameters and retry. If the error is clearly unrecoverable (e.g. a service is down, a resource does not exist, or a permission is permanently denied), report it to the user.</system_notice>";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Number of back-to-back failures of a single tool to coach before giving up.
|
|
42
|
+
* Coaching fires on the 1st through Nth consecutive failure and is dropped from
|
|
43
|
+
* the (N+1)th onward.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
|
|
46
|
+
|
|
47
|
+
/** Map every `tool_use` block id in history to the tool name it invoked. */
|
|
48
|
+
function toolNamesById(messages: ReadonlyArray<Message>): Map<string, string> {
|
|
49
|
+
const names = new Map<string, string>();
|
|
50
|
+
for (const message of messages) {
|
|
51
|
+
if (message.role !== "assistant") continue;
|
|
52
|
+
for (const block of message.content) {
|
|
53
|
+
if (block.type === "tool_use") names.set(block.id, block.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return names;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Trailing run of consecutive error results for `toolName` already in history.
|
|
61
|
+
* Walks the tool's results in chronological order and counts back from the most
|
|
62
|
+
* recent until a successful result breaks the streak. The current result is not
|
|
63
|
+
* yet in history, so callers add it themselves.
|
|
64
|
+
*/
|
|
65
|
+
function priorConsecutiveErrors(
|
|
66
|
+
messages: ReadonlyArray<Message>,
|
|
67
|
+
toolName: string,
|
|
68
|
+
namesById: ReadonlyMap<string, string>,
|
|
69
|
+
): number {
|
|
70
|
+
const isErrorByOrder: boolean[] = [];
|
|
71
|
+
for (const message of messages) {
|
|
72
|
+
if (message.role !== "user") continue;
|
|
73
|
+
for (const block of message.content) {
|
|
74
|
+
if (block.type !== "tool_result") continue;
|
|
75
|
+
if (namesById.get(block.tool_use_id) !== toolName) continue;
|
|
76
|
+
isErrorByOrder.push(block.is_error === true);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let streak = 0;
|
|
81
|
+
for (let i = isErrorByOrder.length - 1; i >= 0; i--) {
|
|
82
|
+
if (!isErrorByOrder[i]) break;
|
|
83
|
+
streak++;
|
|
84
|
+
}
|
|
85
|
+
return streak;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
|
|
89
|
+
if (ctx.toolResponse.is_error !== true) return;
|
|
90
|
+
|
|
91
|
+
const namesById = toolNamesById(ctx.messages);
|
|
92
|
+
const toolName = namesById.get(ctx.toolResponse.tool_use_id);
|
|
93
|
+
|
|
94
|
+
// Prior failures of this tool plus the current one. An unresolved name (the
|
|
95
|
+
// current turn's tool_use is always in history, so this is defensive) falls
|
|
96
|
+
// back to coaching this lone failure.
|
|
97
|
+
const consecutiveErrors =
|
|
98
|
+
(toolName === undefined
|
|
99
|
+
? 0
|
|
100
|
+
: priorConsecutiveErrors(ctx.messages, toolName, namesById)) + 1;
|
|
101
|
+
|
|
102
|
+
if (consecutiveErrors > MAX_CONSECUTIVE_ERROR_NUDGES) {
|
|
103
|
+
ctx.logger.info(
|
|
104
|
+
{
|
|
105
|
+
plugin: "tool-error",
|
|
106
|
+
toolName,
|
|
107
|
+
toolUseId: ctx.toolResponse.tool_use_id,
|
|
108
|
+
consecutiveErrors,
|
|
109
|
+
},
|
|
110
|
+
"Skipping tool-error coaching after repeated consecutive failures",
|
|
111
|
+
);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
ctx.additionalContext = TOOL_ERROR_NUDGE_TEXT;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default postToolUse;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "default-tool-error",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"description": "First-party default plugin
|
|
4
|
+
"description": "First-party default plugin contributing a post-tool-use hook that coaches the model to retry or report a failed tool call.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|