@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
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the
|
|
3
|
-
* `agent-plugin-system` plan).
|
|
2
|
+
* Tests for the static runtime-injection chain.
|
|
4
3
|
*
|
|
5
4
|
* Covers:
|
|
6
5
|
*
|
|
7
|
-
* 1. The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* 1. The default injectors ({@link defaultInjectors}) are listed in the
|
|
7
|
+
* documented order (disk-pressure-warning → workspace-context →
|
|
8
|
+
* background-turn → unified-turn-context → pkb-context → pkb-reminder →
|
|
9
|
+
* memory-v2-static → now-md → active-documents → document-comments →
|
|
11
10
|
* subagent-status → slack-messages → thread-focus).
|
|
12
|
-
* 2.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 3. `composeInjectorChain`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `blocks.injectorChainBlock` undefined, preserving the existing snapshot
|
|
21
|
-
* for conversations that don't opt into the chain.
|
|
22
|
-
* 5. `applyRuntimeInjections` surfaces the composed chain output on
|
|
23
|
-
* `blocks.injectorChainBlock` when a third-party injector contributes
|
|
24
|
-
* content.
|
|
11
|
+
* 2. The assembled {@link injectorChain} sorts the defaults together with the
|
|
12
|
+
* memory-v3 injector by ascending `order`, so memory-v3 (order 1000) lands
|
|
13
|
+
* last.
|
|
14
|
+
* 3. `composeInjectorChain` yields an empty string when every injector opts out
|
|
15
|
+
* — the golden-path conversation state where all defaults return `null`.
|
|
16
|
+
* 4. `applyRuntimeInjections` splices each default injector's block into the
|
|
17
|
+
* correct position in the per-turn message array, and gates blocks by
|
|
18
|
+
* injection mode.
|
|
25
19
|
*/
|
|
26
20
|
|
|
21
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
27
23
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
28
24
|
|
|
29
25
|
// This test exercises v1 PKB injection. `config.memory.v2.enabled`
|
|
@@ -44,20 +40,15 @@ mock.module("../config/loader.js", () => ({
|
|
|
44
40
|
|
|
45
41
|
const { applyRuntimeInjections, composeInjectorChain } =
|
|
46
42
|
await import("../daemon/conversation-runtime-assembly.js");
|
|
47
|
-
const { DEFAULT_INJECTOR_ORDER,
|
|
43
|
+
const { DEFAULT_INJECTOR_ORDER, defaultInjectors } =
|
|
48
44
|
await import("../plugins/defaults/injectors/register.js");
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} from "../plugins/
|
|
54
|
-
import type {
|
|
55
|
-
InjectionBlock,
|
|
56
|
-
Injector,
|
|
57
|
-
Plugin,
|
|
58
|
-
TurnContext,
|
|
59
|
-
} from "../plugins/types.js";
|
|
45
|
+
const { getInjectorChain } =
|
|
46
|
+
await import("../plugins/defaults/memory-retrieval/injector-chain.js");
|
|
47
|
+
import { buildPkbReminder } from "../daemon/pkb-reminder-builder.js";
|
|
48
|
+
import { getPkbRoot } from "../memory/pkb/types.js";
|
|
49
|
+
import type { TurnContext } from "../plugins/types.js";
|
|
60
50
|
import type { Message } from "../providers/types.js";
|
|
51
|
+
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
61
52
|
|
|
62
53
|
/** A fake TurnContext sufficient for driving `composeInjectorChain`. */
|
|
63
54
|
function makeTurnContext(): TurnContext {
|
|
@@ -72,26 +63,45 @@ function makeTurnContext(): TurnContext {
|
|
|
72
63
|
};
|
|
73
64
|
}
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
66
|
+
// The pkb-context and pkb-reminder injectors both derive PKB-active state from
|
|
67
|
+
// the workspace itself — `readPkbContext()` returning content behind the
|
|
68
|
+
// personal-memory trust gate — rather than from a threaded flag. Seed the file
|
|
69
|
+
// with exactly the content the test expects so the `<knowledge_base>` block
|
|
70
|
+
// renders deterministically; clear it between tests so suites that assert the
|
|
71
|
+
// PKB injectors are absent stay unaffected.
|
|
72
|
+
function seedPkbContent(content: string): void {
|
|
73
|
+
const root = getPkbRoot();
|
|
74
|
+
mkdirSync(root, { recursive: true });
|
|
75
|
+
writeFileSync(join(root, "INDEX.md"), content, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function clearPkbContent(): void {
|
|
79
|
+
rmSync(getPkbRoot(), { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The now-md injector sources NOW.md from the workspace itself — behind the
|
|
83
|
+
// personal-memory trust gate and the `scratchpadInjection` config toggle —
|
|
84
|
+
// rather than from a threaded option. Seed the file so the injector fires;
|
|
85
|
+
// clear it between tests so suites that assert NOW.md is absent stay
|
|
86
|
+
// unaffected.
|
|
87
|
+
function seedNowScratchpad(content: string): void {
|
|
88
|
+
const nowPath = getWorkspacePromptPath("NOW.md");
|
|
89
|
+
mkdirSync(dirname(nowPath), { recursive: true });
|
|
90
|
+
writeFileSync(nowPath, content, "utf-8");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clearNowScratchpad(): void {
|
|
94
|
+
rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
describe("injector chain", () => {
|
|
87
98
|
beforeEach(() => {
|
|
88
|
-
|
|
99
|
+
clearPkbContent();
|
|
100
|
+
clearNowScratchpad();
|
|
89
101
|
});
|
|
90
102
|
|
|
91
|
-
test("
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const names = getInjectors().map((i) => i.name);
|
|
103
|
+
test("defaultInjectors lists the defaults in the documented order", () => {
|
|
104
|
+
const names = defaultInjectors.map((i) => i.name);
|
|
95
105
|
expect(names).toEqual([
|
|
96
106
|
"disk-pressure-warning",
|
|
97
107
|
"workspace-context",
|
|
@@ -109,10 +119,8 @@ describe("injector chain", () => {
|
|
|
109
119
|
]);
|
|
110
120
|
});
|
|
111
121
|
|
|
112
|
-
test("default injector order constants match the
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const byName = new Map(getInjectors().map((i) => [i.name, i.order]));
|
|
122
|
+
test("default injector order constants match the listed order values", () => {
|
|
123
|
+
const byName = new Map(defaultInjectors.map((i) => [i.name, i.order]));
|
|
116
124
|
expect(byName.get("disk-pressure-warning")).toBe(
|
|
117
125
|
DEFAULT_INJECTOR_ORDER.diskPressureWarning,
|
|
118
126
|
);
|
|
@@ -143,114 +151,38 @@ describe("injector chain", () => {
|
|
|
143
151
|
expect(byName.get("thread-focus")).toBe(DEFAULT_INJECTOR_ORDER.threadFocus);
|
|
144
152
|
});
|
|
145
153
|
|
|
146
|
-
test("
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
registerPlugin(wrapInPlugin("third-party", [middleInjector]));
|
|
157
|
-
|
|
158
|
-
const names = getInjectors().map((i) => i.name);
|
|
159
|
-
expect(names).toEqual([
|
|
160
|
-
"disk-pressure-warning", // 5
|
|
161
|
-
"workspace-context", // 10
|
|
162
|
-
"background-turn", // 15
|
|
163
|
-
"unified-turn-context", // 20
|
|
164
|
-
"plugin-25", // 25 — slots in
|
|
165
|
-
"pkb-context", // 30
|
|
166
|
-
"pkb-reminder", // 35
|
|
167
|
-
"memory-v2-static", // 38
|
|
168
|
-
"now-md", // 40
|
|
169
|
-
"active-documents", // 45
|
|
170
|
-
"document-comments", // 46
|
|
171
|
-
"subagent-status", // 50
|
|
172
|
-
"slack-messages", // 60
|
|
173
|
-
"thread-focus", // 70
|
|
154
|
+
test("the injector chain sorts the defaults plus memory-v3 by ascending order", () => {
|
|
155
|
+
// The assembled chain merges the defaults with the memory-v3 injector and
|
|
156
|
+
// sorts by `order`, so memory-v3 (order 1000) sits last.
|
|
157
|
+
const chain = getInjectorChain();
|
|
158
|
+
const orders = chain.map((i) => i.order);
|
|
159
|
+
expect(orders).toEqual([...orders].sort((a, b) => a - b));
|
|
160
|
+
expect(chain[chain.length - 1]?.name).toBe("memory-v3-shadow");
|
|
161
|
+
expect(chain.map((i) => i.name)).toEqual([
|
|
162
|
+
...defaultInjectors.map((i) => i.name),
|
|
163
|
+
"memory-v3-shadow",
|
|
174
164
|
]);
|
|
175
165
|
});
|
|
176
166
|
|
|
177
167
|
test("composeInjectorChain returns empty string when every injector opts out", async () => {
|
|
178
|
-
// The default chain is the golden-path:
|
|
179
|
-
//
|
|
180
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
181
|
-
|
|
182
|
-
const composed = await composeInjectorChain(makeTurnContext());
|
|
183
|
-
expect(composed).toBe("");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("composeInjectorChain returns empty string when registry is empty", async () => {
|
|
187
|
-
// No plugins registered — the chain is a no-op and must return an empty
|
|
188
|
-
// string (not throw, not undefined). Callers rely on this to treat the
|
|
189
|
-
// chain as purely additive.
|
|
168
|
+
// The default chain is the golden-path: every default returns `null` on an
|
|
169
|
+
// empty turn context, so the composed block is an empty string.
|
|
190
170
|
const composed = await composeInjectorChain(makeTurnContext());
|
|
191
171
|
expect(composed).toBe("");
|
|
192
172
|
});
|
|
193
173
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
const second: Injector = {
|
|
203
|
-
name: "b",
|
|
204
|
-
order: 15,
|
|
205
|
-
async produce(): Promise<InjectionBlock> {
|
|
206
|
-
return { id: "b", text: "BLOCK_B" };
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
const skipped: Injector = {
|
|
210
|
-
name: "c",
|
|
211
|
-
order: 25,
|
|
212
|
-
async produce() {
|
|
213
|
-
return null;
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
// Register the higher-order one first to prove the chain sorts by `order`
|
|
217
|
-
// rather than registration order.
|
|
218
|
-
registerPlugin(wrapInPlugin("higher", [second]));
|
|
219
|
-
registerPlugin(wrapInPlugin("lower", [first]));
|
|
220
|
-
registerPlugin(wrapInPlugin("opts-out", [skipped]));
|
|
221
|
-
|
|
222
|
-
const composed = await composeInjectorChain(makeTurnContext());
|
|
223
|
-
expect(composed).toBe("BLOCK_A\n\nBLOCK_B");
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("composeInjectorChain skips blocks with empty text", async () => {
|
|
227
|
-
const emitEmpty: Injector = {
|
|
228
|
-
name: "empty",
|
|
229
|
-
order: 10,
|
|
230
|
-
async produce(): Promise<InjectionBlock> {
|
|
231
|
-
return { id: "empty", text: "" };
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
const emitReal: Injector = {
|
|
235
|
-
name: "real",
|
|
236
|
-
order: 20,
|
|
237
|
-
async produce(): Promise<InjectionBlock> {
|
|
238
|
-
return { id: "real", text: "CONTENT" };
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
registerPlugin(wrapInPlugin("plugin", [emitEmpty, emitReal]));
|
|
242
|
-
|
|
243
|
-
const composed = await composeInjectorChain(makeTurnContext());
|
|
244
|
-
expect(composed).toBe("CONTENT");
|
|
245
|
-
});
|
|
174
|
+
// ── Integration tests ───────────────────────────────────────────────
|
|
175
|
+
//
|
|
176
|
+
// These assertions exercise the real per-turn injection pipeline with
|
|
177
|
+
// the static chain active, verifying that each default injector emits
|
|
178
|
+
// the expected content in the correct position in the final user-tail
|
|
179
|
+
// content.
|
|
246
180
|
|
|
247
181
|
test("applyRuntimeInjections leaves injectorChainBlock undefined when defaults opt out", async () => {
|
|
248
|
-
// Golden-path snapshot: with
|
|
182
|
+
// Golden-path snapshot: with the static chain (all defaults returning
|
|
249
183
|
// `null`), `applyRuntimeInjections` reports no chain output, so the
|
|
250
184
|
// historical `blocks` shape is preserved byte-for-byte for any
|
|
251
|
-
// conversation that doesn't
|
|
252
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
253
|
-
|
|
185
|
+
// conversation that doesn't drive a known injector.
|
|
254
186
|
const runMessages: Message[] = [
|
|
255
187
|
{ role: "user", content: [{ type: "text", text: "hello" }] },
|
|
256
188
|
];
|
|
@@ -265,68 +197,27 @@ describe("injector chain", () => {
|
|
|
265
197
|
expect(result.messages).toEqual(runMessages);
|
|
266
198
|
});
|
|
267
199
|
|
|
268
|
-
test("applyRuntimeInjections
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
order: 25,
|
|
275
|
-
async produce(): Promise<InjectionBlock> {
|
|
276
|
-
return { id: "plugin-25", text: "THIRD_PARTY_BLOCK" };
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
]),
|
|
280
|
-
);
|
|
281
|
-
|
|
200
|
+
test("applyRuntimeInjections without turnContext still runs the chain under a synthesized context", async () => {
|
|
201
|
+
// The static chain is the canonical injection path, so
|
|
202
|
+
// `applyRuntimeInjections` must drive it even when the caller doesn't
|
|
203
|
+
// pass a `turnContext`. Call sites that rely on option fields to opt
|
|
204
|
+
// into injections continue to work because the synthesized fallback
|
|
205
|
+
// exposes `injectionInputs` built from `options`.
|
|
282
206
|
const runMessages: Message[] = [
|
|
283
207
|
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
284
208
|
];
|
|
285
209
|
|
|
286
210
|
const result = await applyRuntimeInjections(runMessages, {
|
|
287
|
-
|
|
211
|
+
unifiedTurnContext: "<turn_context>\nsynthesized\n</turn_context>",
|
|
288
212
|
});
|
|
289
213
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// Post-G2.1 semantics: the default chain is the canonical injection
|
|
295
|
-
// path, so `applyRuntimeInjections` must drive it even when the caller
|
|
296
|
-
// doesn't pass a `turnContext`. Test/legacy call sites that rely on
|
|
297
|
-
// option fields to opt into injections continue to work because the
|
|
298
|
-
// synthesized fallback exposes `injectionInputs` built from `options`.
|
|
299
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
300
|
-
registerPlugin(
|
|
301
|
-
wrapInPlugin("third-party-25", [
|
|
302
|
-
{
|
|
303
|
-
name: "plugin-25",
|
|
304
|
-
order: 25,
|
|
305
|
-
async produce(): Promise<InjectionBlock> {
|
|
306
|
-
return { id: "plugin-25", text: "THIRD_PARTY_BLOCK" };
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
]),
|
|
214
|
+
// The unified-turn-context injector fires even without a caller-supplied
|
|
215
|
+
// turnContext, proving the chain runs under the synthesized context.
|
|
216
|
+
expect(result.blocks.unifiedTurnContext).toBe(
|
|
217
|
+
"<turn_context>\nsynthesized\n</turn_context>",
|
|
310
218
|
);
|
|
311
|
-
|
|
312
|
-
const runMessages: Message[] = [
|
|
313
|
-
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
314
|
-
];
|
|
315
|
-
|
|
316
|
-
const result = await applyRuntimeInjections(runMessages, {});
|
|
317
|
-
|
|
318
|
-
// Third-party injector runs even without a caller-supplied turnContext.
|
|
319
|
-
expect(result.blocks.injectorChainBlock).toBe("THIRD_PARTY_BLOCK");
|
|
320
219
|
});
|
|
321
220
|
|
|
322
|
-
// ── Integration tests ───────────────────────────────────────────────
|
|
323
|
-
//
|
|
324
|
-
// These assertions exercise the real per-turn injection pipeline with
|
|
325
|
-
// the default chain active, verifying that each default injector emits
|
|
326
|
-
// the expected content and that a third-party injector registered at a
|
|
327
|
-
// fractional `order` slots into the correct position in the final
|
|
328
|
-
// user-tail content.
|
|
329
|
-
|
|
330
221
|
test("golden-path: default chain injects workspace + unified-turn + PKB + NOW + subagent in the correct positions", async () => {
|
|
331
222
|
// Canonical golden-path conversation state: full mode, non-Slack
|
|
332
223
|
// channel, workspace context + unified-turn + PKB + NOW + subagent
|
|
@@ -335,14 +226,21 @@ describe("injector chain", () => {
|
|
|
335
226
|
// [workspace] ← prepend order 10 (topmost)
|
|
336
227
|
// [unified-turn] ← prepend order 20
|
|
337
228
|
// [now-md] ← after-memory-prefix order 40 (highest order, closest to memory)
|
|
338
|
-
// [pkb-reminder] ← after-memory-prefix order 35
|
|
229
|
+
// [pkb-reminder] ← after-memory-prefix order 35
|
|
339
230
|
// [pkb-context] ← after-memory-prefix order 30
|
|
340
231
|
// [user text]
|
|
341
232
|
// [subagent] ← append order 50
|
|
342
233
|
//
|
|
343
234
|
// No memory prefix blocks in this scenario, so after-memory-prefix
|
|
344
|
-
// lands right at the head of the user-text cluster.
|
|
345
|
-
|
|
235
|
+
// lands right at the head of the user-text cluster. The pkb-context and
|
|
236
|
+
// pkb-reminder injectors both fire off the seeded PKB content under the
|
|
237
|
+
// guardian trust on `makeTurnContext()` — pkb-context renders the seeded
|
|
238
|
+
// `<knowledge_base>` body, pkb-reminder the flat `<system_reminder>`
|
|
239
|
+
// (no graph handle is registered, so it has no search hints).
|
|
240
|
+
const pkbContent = "essentials of the project";
|
|
241
|
+
seedPkbContent(pkbContent);
|
|
242
|
+
const nowContent = "Current focus: shipping G2.1";
|
|
243
|
+
seedNowScratchpad(nowContent);
|
|
346
244
|
|
|
347
245
|
const runMessages: Message[] = [
|
|
348
246
|
{ role: "user", content: [{ type: "text", text: "What next?" }] },
|
|
@@ -352,8 +250,6 @@ describe("injector chain", () => {
|
|
|
352
250
|
"<workspace>\nRoot: /sandbox\nDirectories: src, lib\n</workspace>";
|
|
353
251
|
const unifiedTurn =
|
|
354
252
|
"<turn_context>\ncurrent_time: 2026-04-22\ninterface: macos\n</turn_context>";
|
|
355
|
-
const pkbContent = "essentials of the project";
|
|
356
|
-
const nowContent = "Current focus: shipping G2.1";
|
|
357
253
|
const subagentBlock =
|
|
358
254
|
'<active_subagents>\n- [running] "worker" (sub-1) | elapsed: 5s\n</active_subagents>';
|
|
359
255
|
|
|
@@ -361,9 +257,6 @@ describe("injector chain", () => {
|
|
|
361
257
|
turnContext: makeTurnContext(),
|
|
362
258
|
workspaceTopLevelContext: workspaceText,
|
|
363
259
|
unifiedTurnContext: unifiedTurn,
|
|
364
|
-
pkbContext: pkbContent,
|
|
365
|
-
pkbActive: false, // disable reminder-branch to keep the snapshot small
|
|
366
|
-
nowScratchpad: nowContent,
|
|
367
260
|
subagentStatusBlock: subagentBlock,
|
|
368
261
|
});
|
|
369
262
|
|
|
@@ -378,14 +271,17 @@ describe("injector chain", () => {
|
|
|
378
271
|
// placement says it does.
|
|
379
272
|
expect(texts[0]).toBe(workspaceText); // prepend order 10
|
|
380
273
|
expect(texts[1]).toBe(unifiedTurn); // prepend order 20
|
|
381
|
-
// NOW and
|
|
274
|
+
// NOW, pkb-reminder and pkb-context are all after-memory-prefix; higher
|
|
275
|
+
// order splices closer to the memory prefix, so NOW sits above the
|
|
276
|
+
// reminder, which sits above the knowledge_base.
|
|
382
277
|
expect(texts[2]).toBe(
|
|
383
278
|
`<NOW.md Always keep this up to date; keep under 10 lines>\n${nowContent}\n</NOW.md>`,
|
|
384
279
|
);
|
|
385
|
-
expect(texts[3]).toBe(
|
|
386
|
-
expect(texts[4]).toBe(
|
|
387
|
-
expect(texts[5]).toBe(
|
|
388
|
-
expect(texts).
|
|
280
|
+
expect(texts[3]).toBe(buildPkbReminder([])); // pkb-reminder order 35
|
|
281
|
+
expect(texts[4]).toBe(`<knowledge_base>\n${pkbContent}\n</knowledge_base>`);
|
|
282
|
+
expect(texts[5]).toBe("What next?"); // user's typed text
|
|
283
|
+
expect(texts[6]).toBe(subagentBlock); // append order 50
|
|
284
|
+
expect(texts).toHaveLength(7);
|
|
389
285
|
|
|
390
286
|
// Block metadata captures for DB persistence — one field per default
|
|
391
287
|
// injector whose output the loader rehydrates from message metadata.
|
|
@@ -399,59 +295,6 @@ describe("injector chain", () => {
|
|
|
399
295
|
);
|
|
400
296
|
});
|
|
401
297
|
|
|
402
|
-
test("third-party prepend injector at order 15 lands between workspace (10) and unified-turn-context (20) in the final message", async () => {
|
|
403
|
-
// Proves the extensibility contract end-to-end: a plugin-registered
|
|
404
|
-
// injector at `order: 15` with `placement: "prepend-user-tail"` slots
|
|
405
|
-
// between the workspace prepend (order 10) and the unified-turn
|
|
406
|
-
// prepend (order 20). Because descending-order application for
|
|
407
|
-
// prepends puts the lowest-`order` injector topmost, workspace ends
|
|
408
|
-
// up on top, then plugin@15, then unified-turn.
|
|
409
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
410
|
-
registerPlugin(
|
|
411
|
-
wrapInPlugin("third-party-15-prepend", [
|
|
412
|
-
{
|
|
413
|
-
name: "plugin-15",
|
|
414
|
-
order: 15, // between workspace (10) and unified-turn (20)
|
|
415
|
-
async produce(): Promise<InjectionBlock> {
|
|
416
|
-
return {
|
|
417
|
-
id: "plugin-15",
|
|
418
|
-
text: "<plugin_block_15/>",
|
|
419
|
-
placement: "prepend-user-tail",
|
|
420
|
-
};
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
]),
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
const runMessages: Message[] = [
|
|
427
|
-
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
428
|
-
];
|
|
429
|
-
|
|
430
|
-
const workspaceText = "<workspace>\nRoot: /sandbox\n</workspace>";
|
|
431
|
-
const unifiedTurn =
|
|
432
|
-
"<turn_context>\ncurrent_time: 2026-04-22\n</turn_context>";
|
|
433
|
-
|
|
434
|
-
const result = await applyRuntimeInjections(runMessages, {
|
|
435
|
-
turnContext: makeTurnContext(),
|
|
436
|
-
workspaceTopLevelContext: workspaceText,
|
|
437
|
-
unifiedTurnContext: unifiedTurn,
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
const tail = result.messages[result.messages.length - 1];
|
|
441
|
-
expect(tail.role).toBe("user");
|
|
442
|
-
const texts = tail.content
|
|
443
|
-
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
444
|
-
.map((b) => b.text);
|
|
445
|
-
|
|
446
|
-
// Descending-order application for prepends puts the lowest-`order`
|
|
447
|
-
// injector topmost, so order 10 (workspace) ends up on top, then
|
|
448
|
-
// plugin@15 below it, then unified-turn (order 20) below that.
|
|
449
|
-
expect(texts[0]).toBe(workspaceText);
|
|
450
|
-
expect(texts[1]).toBe("<plugin_block_15/>");
|
|
451
|
-
expect(texts[2]).toBe(unifiedTurn);
|
|
452
|
-
expect(texts[3]).toBe("hi");
|
|
453
|
-
});
|
|
454
|
-
|
|
455
298
|
test("slack-messages injector replaces runMessages when a chronological transcript is provided", async () => {
|
|
456
299
|
// End-to-end verification for the `replace-run-messages` placement:
|
|
457
300
|
// a Slack channel turn with a pre-rendered chronological transcript
|
|
@@ -459,8 +302,6 @@ describe("injector chain", () => {
|
|
|
459
302
|
// after-memory/append placements run. Memory-prefix blocks from the
|
|
460
303
|
// original tail are re-prepended onto the new tail so PKB / NOW
|
|
461
304
|
// splices still find them.
|
|
462
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
463
|
-
|
|
464
305
|
const originalRun: Message[] = [
|
|
465
306
|
{
|
|
466
307
|
role: "user",
|
|
@@ -522,8 +363,6 @@ describe("injector chain", () => {
|
|
|
522
363
|
// opts out in minimal mode, so the tail should carry only the turn
|
|
523
364
|
// context prepend plus any non-injector hardcoded content (none
|
|
524
365
|
// here).
|
|
525
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
526
|
-
|
|
527
366
|
const result = await applyRuntimeInjections(
|
|
528
367
|
[
|
|
529
368
|
{
|
|
@@ -536,9 +375,6 @@ describe("injector chain", () => {
|
|
|
536
375
|
mode: "minimal",
|
|
537
376
|
workspaceTopLevelContext: "<workspace>...</workspace>",
|
|
538
377
|
unifiedTurnContext: "<turn_context>...</turn_context>",
|
|
539
|
-
pkbContext: "kbody",
|
|
540
|
-
pkbActive: true,
|
|
541
|
-
nowScratchpad: "nowbody",
|
|
542
378
|
subagentStatusBlock: "<active_subagents>...</active_subagents>",
|
|
543
379
|
},
|
|
544
380
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
applyRuntimeInjections,
|
|
@@ -6,18 +6,14 @@ import {
|
|
|
6
6
|
} from "../daemon/conversation-runtime-assembly.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_INJECTOR_ORDER,
|
|
9
|
-
|
|
9
|
+
defaultInjectors,
|
|
10
10
|
DISK_PRESSURE_WARNING_PROMPT,
|
|
11
11
|
} from "../plugins/defaults/injectors/register.js";
|
|
12
|
-
import {
|
|
13
|
-
registerPlugin,
|
|
14
|
-
resetPluginRegistryForTests,
|
|
15
|
-
} from "../plugins/registry.js";
|
|
16
12
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
17
13
|
import type { Message } from "../providers/types.js";
|
|
18
14
|
|
|
19
15
|
function findInjector(name: string): Injector {
|
|
20
|
-
const injector =
|
|
16
|
+
const injector = defaultInjectors.find(
|
|
21
17
|
(candidate) => candidate.name === name,
|
|
22
18
|
);
|
|
23
19
|
if (!injector) {
|
|
@@ -50,11 +46,6 @@ const diskPressureInjector = findInjector("disk-pressure-warning");
|
|
|
50
46
|
const cleanupContext = { cleanupModeActive: true };
|
|
51
47
|
|
|
52
48
|
describe("disk-pressure-warning injector", () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
resetPluginRegistryForTests();
|
|
55
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
49
|
test("emits the exact cleanup prompt during disk pressure cleanup mode", async () => {
|
|
59
50
|
const block = await diskPressureInjector.produce(
|
|
60
51
|
makeContext({
|
|
@@ -8,12 +8,12 @@ mock.module("../documents/document-comments-store.js", () => ({
|
|
|
8
8
|
listComments: (...args: unknown[]) => listCommentsMock(...args),
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
|
-
const { DEFAULT_INJECTOR_ORDER,
|
|
11
|
+
const { DEFAULT_INJECTOR_ORDER, defaultInjectors } =
|
|
12
12
|
await import("../plugins/defaults/injectors/register.js");
|
|
13
13
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
14
14
|
|
|
15
15
|
function findInjector(name: string): Injector {
|
|
16
|
-
const injector =
|
|
16
|
+
const injector = defaultInjectors.find(
|
|
17
17
|
(candidate) => candidate.name === name,
|
|
18
18
|
);
|
|
19
19
|
if (!injector) {
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* search so the reminder-with-hints branch can resolve deterministically
|
|
13
13
|
* when called.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
18
|
|
|
17
19
|
let v2Active = false;
|
|
18
20
|
|
|
@@ -20,7 +22,12 @@ const realLoader = await import("../config/loader.js");
|
|
|
20
22
|
|
|
21
23
|
mock.module("../config/loader.js", () => ({
|
|
22
24
|
...realLoader,
|
|
23
|
-
getConfig: () => ({
|
|
25
|
+
getConfig: () => ({
|
|
26
|
+
memory: {
|
|
27
|
+
v2: { enabled: v2Active },
|
|
28
|
+
retrieval: { scratchpadInjection: { enabled: true } },
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
24
31
|
}));
|
|
25
32
|
|
|
26
33
|
mock.module("../memory/pkb/pkb-search.js", () => ({
|
|
@@ -29,12 +36,10 @@ mock.module("../memory/pkb/pkb-search.js", () => ({
|
|
|
29
36
|
|
|
30
37
|
const { applyRuntimeInjections } =
|
|
31
38
|
await import("../daemon/conversation-runtime-assembly.js");
|
|
32
|
-
|
|
33
|
-
await import("../plugins/defaults/injectors/register.js");
|
|
34
|
-
const { registerPlugin, resetPluginRegistryForTests } =
|
|
35
|
-
await import("../plugins/registry.js");
|
|
39
|
+
import { getPkbRoot } from "../memory/pkb/types.js";
|
|
36
40
|
import type { TurnContext } from "../plugins/types.js";
|
|
37
41
|
import type { Message } from "../providers/types.js";
|
|
42
|
+
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
38
43
|
|
|
39
44
|
function makeTurnContext(): TurnContext {
|
|
40
45
|
return {
|
|
@@ -55,28 +60,37 @@ function tailTexts(messages: Message[]): string[] {
|
|
|
55
60
|
.map((b) => b.text);
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
const PKB_CONTEXT = "essentials of the project";
|
|
59
|
-
const NOW_CONTENT = "Current focus: shipping G2.1";
|
|
60
63
|
const RUN_MESSAGES: Message[] = [
|
|
61
64
|
{ role: "user", content: [{ type: "text", text: "What next?" }] },
|
|
62
65
|
];
|
|
63
66
|
|
|
64
67
|
describe("PKB injector v2 cutover behavior", () => {
|
|
68
|
+
// The pkb-reminder gate derives PKB-active state from the workspace
|
|
69
|
+
// (`readPkbContext()` returning content) behind the guardian trust on
|
|
70
|
+
// `makeTurnContext()`, so seed a default auto-injected PKB file rather than
|
|
71
|
+
// passing a flag.
|
|
65
72
|
beforeEach(() => {
|
|
66
|
-
resetPluginRegistryForTests();
|
|
67
|
-
registerPlugin(defaultInjectorsPlugin);
|
|
68
73
|
v2Active = false;
|
|
74
|
+
mkdirSync(getPkbRoot(), { recursive: true });
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(getPkbRoot(), "INDEX.md"),
|
|
77
|
+
"workspace knowledge index",
|
|
78
|
+
"utf-8",
|
|
79
|
+
);
|
|
80
|
+
// now-md sources NOW.md from the workspace behind the same guardian trust,
|
|
81
|
+
// so seed the file rather than passing a flag.
|
|
82
|
+
const nowPath = getWorkspacePromptPath("NOW.md");
|
|
83
|
+
mkdirSync(dirname(nowPath), { recursive: true });
|
|
84
|
+
writeFileSync(nowPath, "Current focus: shipping G2.1", "utf-8");
|
|
85
|
+
});
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
rmSync(getPkbRoot(), { recursive: true, force: true });
|
|
88
|
+
rmSync(getWorkspacePromptPath("NOW.md"), { force: true });
|
|
69
89
|
});
|
|
70
90
|
|
|
71
91
|
test("v2 inactive → pkb-context, pkb-reminder, and now-md all produce blocks", async () => {
|
|
72
92
|
const result = await applyRuntimeInjections(RUN_MESSAGES, {
|
|
73
93
|
turnContext: makeTurnContext(),
|
|
74
|
-
pkbContext: PKB_CONTEXT,
|
|
75
|
-
pkbActive: true,
|
|
76
|
-
pkbScopeId: "scope-default",
|
|
77
|
-
pkbRoot: "/tmp/pkb",
|
|
78
|
-
pkbConversation: { messages: [] },
|
|
79
|
-
nowScratchpad: NOW_CONTENT,
|
|
80
94
|
});
|
|
81
95
|
|
|
82
96
|
const texts = tailTexts(result.messages);
|
|
@@ -89,12 +103,6 @@ describe("PKB injector v2 cutover behavior", () => {
|
|
|
89
103
|
v2Active = true;
|
|
90
104
|
const result = await applyRuntimeInjections(RUN_MESSAGES, {
|
|
91
105
|
turnContext: makeTurnContext(),
|
|
92
|
-
pkbContext: PKB_CONTEXT,
|
|
93
|
-
pkbActive: true,
|
|
94
|
-
pkbScopeId: "scope-default",
|
|
95
|
-
pkbRoot: "/tmp/pkb",
|
|
96
|
-
pkbConversation: { messages: [] },
|
|
97
|
-
nowScratchpad: NOW_CONTENT,
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
const texts = tailTexts(result.messages);
|