@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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `stop` hook: when the model yields a turn with no tool calls, decide
|
|
3
|
+
* whether to let the turn end or re-query the model with a nudge.
|
|
4
|
+
*
|
|
5
|
+
* Two cases warrant a nudge:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Refusal stop.** The provider returned `stopReason === "refusal"` with no
|
|
8
|
+
* visible text (Anthropic's safety classifier zeroed the response). Nudged
|
|
9
|
+
* even on the first model call of the run — a refusal there guarantees no
|
|
10
|
+
* organic text exists yet, so without intervening the loop would persist an
|
|
11
|
+
* empty assistant bubble to the user. Uses `REFUSAL_NUDGE_TEXT`.
|
|
12
|
+
* 2. **Empty turn after tool use.** The turn produced no visible text, follows
|
|
13
|
+
* at least one prior assistant turn this run, and no earlier turn this run
|
|
14
|
+
* already delivered visible text. Uses `NUDGE_TEXT`.
|
|
15
|
+
*
|
|
16
|
+
* Every other case leaves the decision at `"stop"` (the model said its piece,
|
|
17
|
+
* or there is nothing to nudge about). The retry cap is owned by the agent
|
|
18
|
+
* loop: this hook always asks to continue when a nudge is warranted, and the
|
|
19
|
+
* loop stops anyway once the run's nudge budget is spent.
|
|
20
|
+
*
|
|
21
|
+
* Both prior-turn signals are derived from the current response cycle — the
|
|
22
|
+
* messages after the last genuine user prompt (a user turn that isn't purely
|
|
23
|
+
* tool results). Scoping this way keeps prior conversation turns from polluting
|
|
24
|
+
* the signals, and deriving the boundary from history content rather than an
|
|
25
|
+
* index means mid-run compaction (which rewrites the array in place) can't
|
|
26
|
+
* invalidate it. A prior assistant turn this cycle implies a completed tool-use
|
|
27
|
+
* iteration (an empty turn nudges-and-continues without pushing an assistant
|
|
28
|
+
* message), so "a prior assistant turn exists" is the equivalent of "this is
|
|
29
|
+
* not the first model call".
|
|
30
|
+
*
|
|
31
|
+
* Defaults register before any user plugin, so this hook runs at the front of
|
|
32
|
+
* the `stop` chain — later hooks see (and may override) its decision.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
|
|
36
|
+
|
|
37
|
+
import type { ContentBlock, Message } from "../../../../providers/types.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Canonical nudge text for an empty turn after tool use. Must stay verbatim so
|
|
41
|
+
* a plugin that wraps the default sees a stable string.
|
|
42
|
+
*
|
|
43
|
+
* Wire-compat note: this is shown to the LLM, not the user. Edits here affect
|
|
44
|
+
* model behavior but not end-user UX directly.
|
|
45
|
+
*/
|
|
46
|
+
export const NUDGE_TEXT =
|
|
47
|
+
"<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Refusal-specific nudge. Used when the provider stops with `"refusal"` and no
|
|
51
|
+
* visible text — i.e. the safety classifier zeroed the response. Kept distinct
|
|
52
|
+
* from `NUDGE_TEXT` so the model gets context-appropriate guidance (no "summary
|
|
53
|
+
* of what you found or did" — there is no tool trail to summarize on a refusal).
|
|
54
|
+
*
|
|
55
|
+
* Wire-compat note: this is shown to the LLM, not the user. Edits here affect
|
|
56
|
+
* retry behavior but not end-user UX directly.
|
|
57
|
+
*/
|
|
58
|
+
export const REFUSAL_NUDGE_TEXT =
|
|
59
|
+
'<system_notice>Your previous response was empty because the upstream provider returned stop_reason="refusal". Please answer the user\'s last message directly with a plain-text response. Do not use any tools — just respond with text.</system_notice>';
|
|
60
|
+
|
|
61
|
+
function hasVisibleText(content: ReadonlyArray<ContentBlock>): boolean {
|
|
62
|
+
return content.some(
|
|
63
|
+
(block) => block.type === "text" && block.text.trim().length > 0,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isAssistantTurn(message: Message): boolean {
|
|
68
|
+
return message.role === "assistant";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A user-role message carrying only tool results, not a fresh prompt. */
|
|
72
|
+
function isToolResultMessage(message: Message): boolean {
|
|
73
|
+
return (
|
|
74
|
+
message.role === "user" &&
|
|
75
|
+
message.content.length > 0 &&
|
|
76
|
+
message.content.every((block) => block.type === "tool_result")
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Messages belonging to the current response cycle: everything after the last
|
|
82
|
+
* genuine user prompt. Falls back to the whole history when none is found.
|
|
83
|
+
*/
|
|
84
|
+
function currentCycleMessages(
|
|
85
|
+
messages: ReadonlyArray<Message>,
|
|
86
|
+
): ReadonlyArray<Message> {
|
|
87
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
88
|
+
const message = messages[i];
|
|
89
|
+
if (message.role === "user" && !isToolResultMessage(message)) {
|
|
90
|
+
return messages.slice(i + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return messages;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const stop: PluginHookFn<StopContext> = async (ctx) => {
|
|
97
|
+
const turnHasVisibleText = hasVisibleText(ctx.responseContent);
|
|
98
|
+
|
|
99
|
+
const appendNudge = (text: string): void => {
|
|
100
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text }] });
|
|
101
|
+
ctx.decision = "continue";
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (ctx.stopReason === "refusal" && !turnHasVisibleText) {
|
|
105
|
+
appendNudge(REFUSAL_NUDGE_TEXT);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const cycleMessages = currentCycleMessages(ctx.messages);
|
|
110
|
+
const priorAssistantTurns = cycleMessages.filter(isAssistantTurn);
|
|
111
|
+
const hadPriorAssistantTurn = priorAssistantTurns.length > 0;
|
|
112
|
+
const priorAssistantHadVisibleText = priorAssistantTurns.some((message) =>
|
|
113
|
+
hasVisibleText(message.content),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const isEmptyTurnAfterTools =
|
|
117
|
+
!turnHasVisibleText &&
|
|
118
|
+
hadPriorAssistantTurn &&
|
|
119
|
+
!priorAssistantHadVisibleText;
|
|
120
|
+
|
|
121
|
+
if (isEmptyTurnAfterTools) {
|
|
122
|
+
appendNudge(NUDGE_TEXT);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default stop;
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default `
|
|
2
|
+
* Default `empty-response` plugin.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `./
|
|
7
|
-
*
|
|
8
|
-
* default plugin is registered before any user plugin (defaults load first in
|
|
9
|
-
* `bootstrapPlugins()`), which puts it at the OUTERMOST position of the onion
|
|
10
|
-
* chain. If the default middleware were to decide directly without calling
|
|
11
|
-
* `next`, it would shadow every later-registered plugin. Routing through
|
|
12
|
-
* `next(args)` lets user middleware participate normally.
|
|
4
|
+
* Contributes a `stop` hook that re-queries the model when a turn yields with
|
|
5
|
+
* no tool calls but came back empty (or as a provider refusal). The decision
|
|
6
|
+
* logic lives in `./hooks/stop.ts`. Defaults register before user plugins, so
|
|
7
|
+
* this runs at the front of the `stop` hook chain.
|
|
13
8
|
*/
|
|
14
9
|
|
|
15
10
|
import { type Plugin } from "../../types.js";
|
|
16
|
-
import
|
|
11
|
+
import stop from "./hooks/stop.js";
|
|
17
12
|
import pkg from "./package.json" with { type: "json" };
|
|
18
13
|
|
|
19
14
|
/** Singleton plugin — the registry rejects duplicate registrations by name. */
|
|
@@ -22,7 +17,7 @@ export const defaultEmptyResponsePlugin: Plugin = {
|
|
|
22
17
|
name: pkg.name,
|
|
23
18
|
version: pkg.version,
|
|
24
19
|
},
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
hooks: {
|
|
21
|
+
stop,
|
|
27
22
|
},
|
|
28
23
|
};
|
|
@@ -23,22 +23,15 @@
|
|
|
23
23
|
* registration work.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { memoryV3ShadowPlugin } from "../../memory/v3/shadow-plugin.js";
|
|
27
26
|
import { registerPlugin, resetPluginRegistryForTests } from "../registry.js";
|
|
28
27
|
import { type Plugin, PluginExecutionError } from "../types.js";
|
|
29
28
|
import { defaultCircuitBreakerPlugin } from "./circuit-breaker/register.js";
|
|
30
29
|
import { defaultCompactionPlugin } from "./compaction/register.js";
|
|
31
30
|
import { defaultEmptyResponsePlugin } from "./empty-response/register.js";
|
|
32
31
|
import { defaultHistoryRepairPlugin } from "./history-repair/register.js";
|
|
33
|
-
import { defaultInjectorsPlugin } from "./injectors/register.js";
|
|
34
|
-
import { defaultLlmCallPlugin } from "./llm-call/register.js";
|
|
35
|
-
import { defaultMemoryRetrievalPlugin } from "./memory-retrieval/register.js";
|
|
36
32
|
import { defaultOverflowReducePlugin } from "./overflow-reduce/register.js";
|
|
37
|
-
import { defaultPersistencePlugin } from "./persistence/register.js";
|
|
38
33
|
import { defaultTitleGeneratePlugin } from "./title-generate/register.js";
|
|
39
|
-
import { defaultTokenEstimatePlugin } from "./token-estimate/register.js";
|
|
40
34
|
import { defaultToolErrorPlugin } from "./tool-error/register.js";
|
|
41
|
-
import { defaultToolExecutePlugin } from "./tool-execute/register.js";
|
|
42
35
|
import { defaultToolResultTruncatePlugin } from "./tool-result-truncate/register.js";
|
|
43
36
|
|
|
44
37
|
/**
|
|
@@ -53,21 +46,14 @@ import { defaultToolResultTruncatePlugin } from "./tool-result-truncate/register
|
|
|
53
46
|
*/
|
|
54
47
|
function getAllDefaultPlugins(): readonly Plugin[] {
|
|
55
48
|
return [
|
|
56
|
-
defaultLlmCallPlugin,
|
|
57
|
-
defaultToolExecutePlugin,
|
|
58
49
|
defaultToolResultTruncatePlugin,
|
|
59
50
|
defaultEmptyResponsePlugin,
|
|
60
51
|
defaultToolErrorPlugin,
|
|
61
|
-
defaultMemoryRetrievalPlugin,
|
|
62
|
-
defaultInjectorsPlugin,
|
|
63
|
-
defaultTokenEstimatePlugin,
|
|
64
52
|
defaultOverflowReducePlugin,
|
|
65
53
|
defaultHistoryRepairPlugin,
|
|
66
54
|
defaultCompactionPlugin,
|
|
67
55
|
defaultCircuitBreakerPlugin,
|
|
68
|
-
defaultPersistencePlugin,
|
|
69
56
|
defaultTitleGeneratePlugin,
|
|
70
|
-
memoryV3ShadowPlugin,
|
|
71
57
|
];
|
|
72
58
|
}
|
|
73
59
|
|
|
@@ -99,7 +85,7 @@ export function registerDefaultPlugins(): void {
|
|
|
99
85
|
* so integration tests that exercise the full agent loop have a
|
|
100
86
|
* production-parity plugin stack. Use this in `beforeEach` of tests that
|
|
101
87
|
* dispatch through pipelines with a terminal that assumes the default
|
|
102
|
-
* plugin handles every op (e.g.
|
|
88
|
+
* plugin handles every op (e.g. overflowReduce).
|
|
103
89
|
*
|
|
104
90
|
* Tests that specifically need an empty registry (pipeline-unit tests, the
|
|
105
91
|
* plugin-registry tests themselves) should continue to call
|
|
@@ -32,35 +32,47 @@
|
|
|
32
32
|
* closer to the memory prefix themselves. For appends, ascending `order` is
|
|
33
33
|
* the natural left-to-right append sequence. The runtime-injection applier
|
|
34
34
|
* sorts and applies blocks declaratively so this invariant holds even when
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* explicitly from `daemon/external-plugins-bootstrap.ts` or lazily via the
|
|
45
|
-
* registry's default registrar the first time a query reads the registry.
|
|
35
|
+
* injectors slot additional blocks at fractional order values.
|
|
36
|
+
*
|
|
37
|
+
* This module exports the default injectors as a plain ordered array
|
|
38
|
+
* ({@link defaultInjectors}). The chain assembler in
|
|
39
|
+
* `plugins/defaults/memory-retrieval/injector-chain.ts` sorts them by `order`
|
|
40
|
+
* (alongside the memory-v3 injector) into the single sequence
|
|
41
|
+
* `applyRuntimeInjections` walks each turn — injection is not a plugin
|
|
42
|
+
* contribution, so injectors are imported directly rather than aggregated
|
|
43
|
+
* through the registry.
|
|
46
44
|
*/
|
|
47
45
|
|
|
48
46
|
import { resolve } from "node:path";
|
|
49
47
|
|
|
50
48
|
import { getConfig } from "../../../config/loader.js";
|
|
49
|
+
import type { InjectionMatcher } from "../../../context/strip-injections.js";
|
|
50
|
+
import { readNowScratchpad } from "../../../daemon/now-scratchpad.js";
|
|
51
51
|
import { getInContextPkbPaths } from "../../../daemon/pkb-context-tracker.js";
|
|
52
52
|
import { buildPkbReminder } from "../../../daemon/pkb-reminder-builder.js";
|
|
53
|
+
import {
|
|
54
|
+
resolveTrustClass,
|
|
55
|
+
type TrustContext,
|
|
56
|
+
} from "../../../daemon/trust-context.js";
|
|
53
57
|
import { listComments } from "../../../documents/document-comments-store.js";
|
|
58
|
+
import { getLiveGraphMemory } from "../../../memory/graph/conversation-graph-memory.js";
|
|
59
|
+
import { getPkbAutoInjectList } from "../../../memory/pkb/autoinject.js";
|
|
60
|
+
import { readPkbContext } from "../../../memory/pkb/context.js";
|
|
54
61
|
import { searchPkbFiles } from "../../../memory/pkb/pkb-search.js";
|
|
62
|
+
import { getPkbRoot, PKB_WORKSPACE_SCOPE } from "../../../memory/pkb/types.js";
|
|
63
|
+
import {
|
|
64
|
+
readMemoryV2StaticContent,
|
|
65
|
+
shouldExposePersonalMemory,
|
|
66
|
+
} from "../../../memory/v2/static-context.js";
|
|
67
|
+
import type { Message } from "../../../providers/types.js";
|
|
55
68
|
import { getLogger } from "../../../util/logger.js";
|
|
69
|
+
import { getSandboxWorkingDir } from "../../../util/platform.js";
|
|
56
70
|
import {
|
|
57
71
|
type InjectionBlock,
|
|
58
72
|
type Injector,
|
|
59
|
-
type Plugin,
|
|
60
73
|
type TurnContext,
|
|
61
74
|
type TurnInjectionInputs,
|
|
62
75
|
} from "../../types.js";
|
|
63
|
-
import pkg from "./package.json" with { type: "json" };
|
|
64
76
|
|
|
65
77
|
const pkbReminderLog = getLogger("pkb-reminder");
|
|
66
78
|
|
|
@@ -79,7 +91,7 @@ const PKB_HINT_ARCHIVE_THRESHOLD = 0.7;
|
|
|
79
91
|
* and any future integration code — can assert ordering without re-deriving
|
|
80
92
|
* the constants.
|
|
81
93
|
*
|
|
82
|
-
* Gaps of 10 between slots leave room for
|
|
94
|
+
* Gaps of 10 between slots leave room for future injectors to slot in
|
|
83
95
|
* at granular positions (e.g. `25` between unified-turn-context and pkb)
|
|
84
96
|
* without renumbering the defaults.
|
|
85
97
|
*/
|
|
@@ -241,20 +253,33 @@ const unifiedTurnContextInjector: Injector = {
|
|
|
241
253
|
*
|
|
242
254
|
* Gating:
|
|
243
255
|
* - `mode === "full"`.
|
|
244
|
-
* -
|
|
256
|
+
* - The personal-memory trust gate admits the actor and the workspace has
|
|
257
|
+
* PKB content (see {@link readGatedPkbContext}).
|
|
258
|
+
* - The `<knowledge_base>` block is not already present in the turn's working
|
|
259
|
+
* messages. The big block is injected once and then persists in history, so
|
|
260
|
+
* it only needs (re)injecting on the first turn and right after compaction
|
|
261
|
+
* strips it — both of which leave the working messages without the block.
|
|
262
|
+
* Skipping when it is present keeps the conversation prefix stable for
|
|
263
|
+
* Anthropic's prefix caching and avoids a duplicate splice.
|
|
245
264
|
*/
|
|
246
265
|
const pkbContextInjector: Injector = {
|
|
247
266
|
name: "pkb-context",
|
|
248
267
|
order: DEFAULT_INJECTOR_ORDER.pkbContext,
|
|
249
|
-
async produce(
|
|
268
|
+
async produce(
|
|
269
|
+
ctx: TurnContext,
|
|
270
|
+
runMessages?: Message[],
|
|
271
|
+
): Promise<InjectionBlock | null> {
|
|
250
272
|
const inputs = readInjectionInputs(ctx);
|
|
251
273
|
const mode = inputs.mode ?? "full";
|
|
252
274
|
if (mode !== "full") return null;
|
|
253
275
|
if (isPkbInjectionSilencedByV2()) return null;
|
|
254
|
-
|
|
276
|
+
const content = readGatedPkbContext(ctx.trust);
|
|
277
|
+
if (!content) return null;
|
|
278
|
+
if (hasInjectedUserTextBlock(runMessages, KNOWLEDGE_BASE_BLOCK_PREFIXES))
|
|
279
|
+
return null;
|
|
255
280
|
return {
|
|
256
281
|
id: "pkb-context",
|
|
257
|
-
text: buildPkbContextBlock(
|
|
282
|
+
text: buildPkbContextBlock(content),
|
|
258
283
|
placement: "after-memory-prefix",
|
|
259
284
|
};
|
|
260
285
|
},
|
|
@@ -271,18 +296,21 @@ const pkbContextInjector: Injector = {
|
|
|
271
296
|
*
|
|
272
297
|
* Gating:
|
|
273
298
|
* - `mode === "full"`.
|
|
274
|
-
* -
|
|
299
|
+
* - PKB is active for the turn (see {@link isPkbActive}).
|
|
275
300
|
*/
|
|
276
301
|
const pkbReminderInjector: Injector = {
|
|
277
302
|
name: "pkb-reminder",
|
|
278
303
|
order: DEFAULT_INJECTOR_ORDER.pkbReminder,
|
|
279
|
-
async produce(
|
|
304
|
+
async produce(
|
|
305
|
+
ctx: TurnContext,
|
|
306
|
+
runMessages?: Message[],
|
|
307
|
+
): Promise<InjectionBlock | null> {
|
|
280
308
|
const inputs = readInjectionInputs(ctx);
|
|
281
309
|
const mode = inputs.mode ?? "full";
|
|
282
310
|
if (mode !== "full") return null;
|
|
283
|
-
if (!
|
|
311
|
+
if (!isPkbActive(ctx.trust)) return null;
|
|
284
312
|
if (isPkbInjectionSilencedByV2()) return null;
|
|
285
|
-
const reminder = await buildPkbReminderWithHints(
|
|
313
|
+
const reminder = await buildPkbReminderWithHints(ctx, runMessages);
|
|
286
314
|
return {
|
|
287
315
|
id: "pkb-reminder",
|
|
288
316
|
text: reminder,
|
|
@@ -291,6 +319,125 @@ const pkbReminderInjector: Injector = {
|
|
|
291
319
|
},
|
|
292
320
|
};
|
|
293
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Whether personal-memory content (PKB, NOW.md) may be surfaced this turn: the
|
|
324
|
+
* trust gate admits the actor (guardian-class, or an internal/local flow). All
|
|
325
|
+
* memory-domain injectors share this gate so they apply identical exposure
|
|
326
|
+
* rules without it being threaded in from the agent loop.
|
|
327
|
+
*/
|
|
328
|
+
function isPersonalMemoryAllowed(trust: TrustContext): boolean {
|
|
329
|
+
return shouldExposePersonalMemory({
|
|
330
|
+
sourceChannel: trust.sourceChannel,
|
|
331
|
+
isTrustedActor: resolveTrustClass(trust) === "guardian",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Read the auto-injected PKB content for the turn, gated behind the
|
|
337
|
+
* personal-memory trust gate. Returns the content string when the gate admits
|
|
338
|
+
* the actor and the workspace has PKB content, otherwise `null`. Both the gate
|
|
339
|
+
* and the content are sourced from the turn's trust context and the PKB files
|
|
340
|
+
* directly, so the memory-domain injectors source their own inputs rather than
|
|
341
|
+
* having them threaded in from the agent loop.
|
|
342
|
+
*/
|
|
343
|
+
function readGatedPkbContext(trust: TrustContext): string | null {
|
|
344
|
+
return isPersonalMemoryAllowed(trust) ? readPkbContext() : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Read the NOW.md scratchpad content for the turn, gated behind the
|
|
349
|
+
* personal-memory trust gate and the `scratchpadInjection` config toggle.
|
|
350
|
+
* Returns the trimmed content when both gates admit and the file is non-empty,
|
|
351
|
+
* otherwise `null`. Sourced from the trust context and the NOW.md file directly
|
|
352
|
+
* so the `now-md` injector owns its input rather than having it threaded in.
|
|
353
|
+
*/
|
|
354
|
+
function readGatedNowScratchpad(trust: TrustContext): string | null {
|
|
355
|
+
if (!isPersonalMemoryAllowed(trust)) return null;
|
|
356
|
+
if (!getConfig().memory.retrieval.scratchpadInjection.enabled) return null;
|
|
357
|
+
return readNowScratchpad();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Read the v2 static memory content for the turn, gated behind the
|
|
362
|
+
* personal-memory trust gate. Returns the content (essentials/threads/recent/
|
|
363
|
+
* buffer concatenated) when the gate admits and v2 memory is enabled,
|
|
364
|
+
* otherwise `null`. {@link readMemoryV2StaticContent} self-gates on the v2
|
|
365
|
+
* flag + config, so the `memory-v2-static` injector owns its input rather than
|
|
366
|
+
* having it threaded in from the agent loop.
|
|
367
|
+
*/
|
|
368
|
+
function readGatedMemoryV2Static(trust: TrustContext): string | null {
|
|
369
|
+
return isPersonalMemoryAllowed(trust) ? readMemoryV2StaticContent() : null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Whether PKB is active for the turn: the personal-memory trust gate admits
|
|
374
|
+
* the actor and the workspace has PKB content to surface.
|
|
375
|
+
*/
|
|
376
|
+
function isPkbActive(trust: TrustContext): boolean {
|
|
377
|
+
return readGatedPkbContext(trust) !== null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Block prefixes that mark a persisted `<knowledge_base>` injection. */
|
|
381
|
+
const KNOWLEDGE_BASE_BLOCK_PREFIXES = [
|
|
382
|
+
"<knowledge_base>",
|
|
383
|
+
"<pkb>", // backward-compat: pre-rename history
|
|
384
|
+
] as const;
|
|
385
|
+
|
|
386
|
+
/** Block prefixes that mark a persisted NOW.md injection. */
|
|
387
|
+
const NOW_MD_BLOCK_PREFIXES = [
|
|
388
|
+
"<NOW.md Always keep this up to date",
|
|
389
|
+
"<now_scratchpad>", // backward-compat: pre-rename history
|
|
390
|
+
] as const;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Matchers that mark a persisted `memory-v2-static` injection. Uses the
|
|
394
|
+
* `{ prefix, suffix }` wrapper shape (not a bare prefix) so user-authored text
|
|
395
|
+
* merely starting with `<info>\n` is never mistaken for an injection — matching
|
|
396
|
+
* the full-wrapper requirement the compaction strip uses for this block.
|
|
397
|
+
*
|
|
398
|
+
* The static block is wrapped in `<info>…</info>` today, but rows persisted
|
|
399
|
+
* before that switch rehydrate verbatim as `<memory>…</memory>` (see
|
|
400
|
+
* `conversation-lifecycle`), so the legacy wrapper counts as present too.
|
|
401
|
+
* Matching `<memory>` cannot wrongly skip a needed injection: the static block
|
|
402
|
+
* is only (re)injected on the first turn (empty history) and right after
|
|
403
|
+
* compaction (which strips both wrappers), and on neither is a `<memory>` block
|
|
404
|
+
* present — the dynamic activation `<memory>` block only survives on normal
|
|
405
|
+
* cached turns, which is exactly when this injector must skip anyway.
|
|
406
|
+
*/
|
|
407
|
+
const MEMORY_V2_STATIC_BLOCK_MATCHERS: readonly InjectionMatcher[] = [
|
|
408
|
+
{ prefix: "<info>\n", suffix: "\n</info>" },
|
|
409
|
+
{ prefix: "<memory>\n", suffix: "\n</memory>" },
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Whether a block matching any of the given matchers is already present in the
|
|
414
|
+
* turn's working messages. Mirrors `stripUserTextBlocksByPrefix` (a
|
|
415
|
+
* user-message text block whose content matches a bare-prefix or a
|
|
416
|
+
* `{ prefix, suffix }` wrapper matcher), so presence detection stays in
|
|
417
|
+
* lockstep with what compaction strips: a block is present here exactly when
|
|
418
|
+
* compaction would strip it.
|
|
419
|
+
*/
|
|
420
|
+
function hasInjectedUserTextBlock(
|
|
421
|
+
runMessages: Message[] | undefined,
|
|
422
|
+
matchers: readonly InjectionMatcher[],
|
|
423
|
+
): boolean {
|
|
424
|
+
if (!runMessages) return false;
|
|
425
|
+
return runMessages.some(
|
|
426
|
+
(message) =>
|
|
427
|
+
message.role === "user" &&
|
|
428
|
+
message.content.some(
|
|
429
|
+
(block) =>
|
|
430
|
+
block.type === "text" &&
|
|
431
|
+
matchers.some((m) =>
|
|
432
|
+
typeof m === "string"
|
|
433
|
+
? block.text.startsWith(m)
|
|
434
|
+
: block.text.startsWith(m.prefix) &&
|
|
435
|
+
block.text.endsWith(m.suffix),
|
|
436
|
+
),
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
294
441
|
/**
|
|
295
442
|
* Render the PKB context block — wraps the raw content in
|
|
296
443
|
* `<knowledge_base>...</knowledge_base>` while escaping any closing tags
|
|
@@ -305,38 +452,40 @@ function buildPkbContextBlock(content: string): string {
|
|
|
305
452
|
}
|
|
306
453
|
|
|
307
454
|
/**
|
|
308
|
-
* Build the PKB `<system_reminder>` text. When a dense query vector
|
|
309
|
-
*
|
|
455
|
+
* Build the PKB `<system_reminder>` text. When a dense query vector and the
|
|
456
|
+
* turn's working messages are available, run the hybrid PKB search to
|
|
310
457
|
* surface up to three relevance hints; fall back to the flat static
|
|
311
458
|
* reminder on empty results or any error.
|
|
459
|
+
*
|
|
460
|
+
* The dense/sparse query pair is read off the conversation's live graph
|
|
461
|
+
* handle ({@link getLiveGraphMemory}) — the memory-retrieval hook records it
|
|
462
|
+
* there during the turn's retrieval. In-context PKB paths are computed from
|
|
463
|
+
* the turn's working messages (`runMessages`, supplied by the injector chain)
|
|
464
|
+
* resolved against the workspace working directory, so the reminder sources
|
|
465
|
+
* its inputs itself rather than having them threaded through the agent loop.
|
|
312
466
|
*/
|
|
313
467
|
async function buildPkbReminderWithHints(
|
|
314
|
-
|
|
468
|
+
ctx: TurnContext,
|
|
469
|
+
runMessages?: Message[],
|
|
315
470
|
): Promise<string> {
|
|
316
471
|
let hints: string[] = [];
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
queryVector.length > 0 &&
|
|
321
|
-
inputs.pkbScopeId &&
|
|
322
|
-
inputs.pkbConversation &&
|
|
323
|
-
inputs.pkbRoot
|
|
324
|
-
) {
|
|
472
|
+
const graphMemory = getLiveGraphMemory(ctx.conversationId);
|
|
473
|
+
const queryVector = graphMemory?.pkbQueryVector;
|
|
474
|
+
if (queryVector && queryVector.length > 0 && runMessages) {
|
|
325
475
|
try {
|
|
476
|
+
const pkbRoot = getPkbRoot();
|
|
326
477
|
const results = await searchPkbFiles(
|
|
327
478
|
queryVector,
|
|
328
|
-
|
|
479
|
+
graphMemory?.pkbSparseVector,
|
|
329
480
|
8,
|
|
330
|
-
[
|
|
481
|
+
[PKB_WORKSPACE_SCOPE],
|
|
331
482
|
);
|
|
332
|
-
const workingDir = inputs.pkbWorkingDir ?? inputs.pkbRoot;
|
|
333
483
|
const inContext = getInContextPkbPaths(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
484
|
+
{ messages: runMessages },
|
|
485
|
+
getPkbAutoInjectList(pkbRoot),
|
|
486
|
+
pkbRoot,
|
|
487
|
+
getSandboxWorkingDir(),
|
|
338
488
|
);
|
|
339
|
-
const pkbRoot = inputs.pkbRoot;
|
|
340
489
|
// Gate on `denseScore` (cosine, [0, 1]) so the quality bar is stable
|
|
341
490
|
// regardless of whether sparse was provided. Rank by `hybridScore`
|
|
342
491
|
// (RRF) when available — that captures the sparse signal for
|
|
@@ -395,18 +544,30 @@ async function buildPkbReminderWithHints(
|
|
|
395
544
|
* the memory prefix so `now-md` (40) splices after it.
|
|
396
545
|
*
|
|
397
546
|
* Gating:
|
|
398
|
-
* - `mode === "full"
|
|
399
|
-
* -
|
|
547
|
+
* - `mode === "full"` (skipped in minimal mode).
|
|
548
|
+
* - The personal-memory trust gate admits the actor and v2 static memory has
|
|
549
|
+
* content (see {@link readGatedMemoryV2Static}).
|
|
550
|
+
* - The `<info>` block is not already present in the turn's working messages.
|
|
551
|
+
* Like `<knowledge_base>`, the block is injected once and then persists in
|
|
552
|
+
* history, so it only needs (re)injecting on the first turn and right after
|
|
553
|
+
* compaction strips it — both of which leave the working messages without
|
|
554
|
+
* the block. Skipping when it is present keeps the conversation prefix
|
|
555
|
+
* stable for Anthropic's prefix caching and avoids a duplicate splice.
|
|
400
556
|
*/
|
|
401
557
|
const memoryV2StaticInjector: Injector = {
|
|
402
558
|
name: "memory-v2-static",
|
|
403
559
|
order: DEFAULT_INJECTOR_ORDER.memoryV2Static,
|
|
404
|
-
async produce(
|
|
560
|
+
async produce(
|
|
561
|
+
ctx: TurnContext,
|
|
562
|
+
runMessages?: Message[],
|
|
563
|
+
): Promise<InjectionBlock | null> {
|
|
405
564
|
const inputs = readInjectionInputs(ctx);
|
|
406
565
|
const mode = inputs.mode ?? "full";
|
|
407
566
|
if (mode !== "full") return null;
|
|
408
|
-
const content =
|
|
567
|
+
const content = readGatedMemoryV2Static(ctx.trust);
|
|
409
568
|
if (!content) return null;
|
|
569
|
+
if (hasInjectedUserTextBlock(runMessages, MEMORY_V2_STATIC_BLOCK_MATCHERS))
|
|
570
|
+
return null;
|
|
410
571
|
return {
|
|
411
572
|
id: "memory-v2-static",
|
|
412
573
|
text: buildMemoryV2StaticBlock(content),
|
|
@@ -438,17 +599,30 @@ function buildMemoryV2StaticBlock(content: string): string {
|
|
|
438
599
|
*
|
|
439
600
|
* Gating:
|
|
440
601
|
* - `mode === "full"` (skipped in minimal mode).
|
|
441
|
-
* -
|
|
602
|
+
* - The personal-memory trust gate admits the actor, the `scratchpadInjection`
|
|
603
|
+
* config toggle is on, and NOW.md has content (see
|
|
604
|
+
* {@link readGatedNowScratchpad}).
|
|
605
|
+
* - The NOW.md block is not already present in the turn's working messages.
|
|
606
|
+
* Like `<knowledge_base>`, the block is injected once and then persists in
|
|
607
|
+
* history, so it only needs (re)injecting on the first turn and right after
|
|
608
|
+
* compaction strips it — both of which leave the working messages without
|
|
609
|
+
* the block. Skipping when it is present keeps the conversation prefix
|
|
610
|
+
* stable for Anthropic's prefix caching and avoids a duplicate splice.
|
|
442
611
|
*/
|
|
443
612
|
const nowMdInjector: Injector = {
|
|
444
613
|
name: "now-md",
|
|
445
614
|
order: DEFAULT_INJECTOR_ORDER.nowMd,
|
|
446
|
-
async produce(
|
|
615
|
+
async produce(
|
|
616
|
+
ctx: TurnContext,
|
|
617
|
+
runMessages?: Message[],
|
|
618
|
+
): Promise<InjectionBlock | null> {
|
|
447
619
|
const inputs = readInjectionInputs(ctx);
|
|
448
620
|
const mode = inputs.mode ?? "full";
|
|
449
621
|
if (mode !== "full") return null;
|
|
450
|
-
const content =
|
|
622
|
+
const content = readGatedNowScratchpad(ctx.trust);
|
|
451
623
|
if (!content) return null;
|
|
624
|
+
if (hasInjectedUserTextBlock(runMessages, NOW_MD_BLOCK_PREFIXES))
|
|
625
|
+
return null;
|
|
452
626
|
const text = `<NOW.md Always keep this up to date; keep under 10 lines>\n${content}\n</NOW.md>`;
|
|
453
627
|
return {
|
|
454
628
|
id: "now-md",
|
|
@@ -675,31 +849,26 @@ const threadFocusInjector: Injector = {
|
|
|
675
849
|
};
|
|
676
850
|
|
|
677
851
|
/**
|
|
678
|
-
*
|
|
679
|
-
*
|
|
852
|
+
* Every default injector in ascending `order`. This is the canonical
|
|
853
|
+
* first-party injection sequence consumed by `applyRuntimeInjections` via the
|
|
854
|
+
* assembled injector chain.
|
|
680
855
|
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
856
|
+
* `order` is the source of truth for sequencing (see {@link DEFAULT_INJECTOR_ORDER});
|
|
857
|
+
* the chain assembler sorts by it, so this array's literal order is only a
|
|
858
|
+
* readability convenience.
|
|
684
859
|
*/
|
|
685
|
-
export const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
documentCommentsInjector,
|
|
701
|
-
subagentStatusInjector,
|
|
702
|
-
slackMessagesInjector,
|
|
703
|
-
threadFocusInjector,
|
|
704
|
-
],
|
|
705
|
-
};
|
|
860
|
+
export const defaultInjectors: Injector[] = [
|
|
861
|
+
diskPressureWarningInjector,
|
|
862
|
+
workspaceContextInjector,
|
|
863
|
+
backgroundTurnInjector,
|
|
864
|
+
unifiedTurnContextInjector,
|
|
865
|
+
pkbContextInjector,
|
|
866
|
+
pkbReminderInjector,
|
|
867
|
+
memoryV2StaticInjector,
|
|
868
|
+
nowMdInjector,
|
|
869
|
+
activeDocumentsInjector,
|
|
870
|
+
documentCommentsInjector,
|
|
871
|
+
subagentStatusInjector,
|
|
872
|
+
slackMessagesInjector,
|
|
873
|
+
threadFocusInjector,
|
|
874
|
+
];
|