@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
|
@@ -5,13 +5,21 @@
|
|
|
5
5
|
* before it is sent to the provider. They are pure (no side effects).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { join
|
|
8
|
+
import { statSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
10
|
|
|
11
11
|
import { type ChannelId, parseInterfaceId } from "../channels/types.js";
|
|
12
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
12
13
|
import { getConfig } from "../config/loader.js";
|
|
14
|
+
import { stripUserTextBlocksByPrefix } from "../context/strip-injections.js";
|
|
13
15
|
import { createContextSummaryMessage } from "../context/window-manager.js";
|
|
14
|
-
import {
|
|
16
|
+
import { getDocumentsForConversation } from "../documents/document-store.js";
|
|
17
|
+
import {
|
|
18
|
+
getApp,
|
|
19
|
+
getAppDirPath,
|
|
20
|
+
listAppFiles,
|
|
21
|
+
resolveAppDir,
|
|
22
|
+
} from "../memory/app-store.js";
|
|
15
23
|
import {
|
|
16
24
|
getMessages as defaultGetMessages,
|
|
17
25
|
type MessageRow,
|
|
@@ -21,7 +29,6 @@ import {
|
|
|
21
29
|
extractMemoryPrefixBlocks,
|
|
22
30
|
stripAllMemoryInjections,
|
|
23
31
|
} from "../memory/graph/conversation-graph-memory.js";
|
|
24
|
-
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
25
32
|
import { MEMORY_V3_BLOCK_ID } from "../memory/v3/types.js";
|
|
26
33
|
import {
|
|
27
34
|
readSlackMetadata,
|
|
@@ -35,7 +42,7 @@ import {
|
|
|
35
42
|
type RenderedSlackTranscriptMessage,
|
|
36
43
|
renderSlackTranscriptWithProvenance,
|
|
37
44
|
} from "../messaging/providers/slack/render-transcript.js";
|
|
38
|
-
import {
|
|
45
|
+
import { getInjectorChain } from "../plugins/defaults/memory-retrieval/injector-chain.js";
|
|
39
46
|
import type {
|
|
40
47
|
DiskPressureInjectionContext,
|
|
41
48
|
InjectionBlock,
|
|
@@ -53,12 +60,18 @@ import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/ac
|
|
|
53
60
|
import type { SubagentState } from "../subagent/types.js";
|
|
54
61
|
import { TERMINAL_STATUSES } from "../subagent/types.js";
|
|
55
62
|
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
56
|
-
import {
|
|
57
|
-
|
|
63
|
+
import type {
|
|
64
|
+
DynamicPageSurfaceData,
|
|
65
|
+
SurfaceData,
|
|
66
|
+
SurfaceType,
|
|
67
|
+
} from "./message-protocol.js";
|
|
58
68
|
import { filterMessagesForUntrustedActor } from "./message-provenance.js";
|
|
59
|
-
import { type PkbContextConversation } from "./pkb-context-tracker.js";
|
|
60
69
|
import type { TrustContext } from "./trust-context.js";
|
|
61
70
|
|
|
71
|
+
// The compaction strip lives in the compaction layer (`context/`) so the agent
|
|
72
|
+
// loop can own it; re-exported here for this module's existing consumers.
|
|
73
|
+
export { stripInjectionsForCompaction } from "../context/strip-injections.js";
|
|
74
|
+
|
|
62
75
|
/**
|
|
63
76
|
* Describes the capabilities of the channel through which the user is
|
|
64
77
|
* interacting. Used to gate UI-specific references and permission asks.
|
|
@@ -271,8 +284,90 @@ export interface ActiveSurfaceContext {
|
|
|
271
284
|
appPages?: Record<string, string>;
|
|
272
285
|
/** The page currently displayed in the WebView (e.g. "settings.html"). */
|
|
273
286
|
currentPage?: string;
|
|
274
|
-
|
|
275
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Resolve the conversation's active workspace surface into the context block
|
|
291
|
+
* consumed by {@link applyRuntimeInjections}, or `null` when no dynamic-page
|
|
292
|
+
* surface is active. App-backed surfaces are enriched with their persisted app
|
|
293
|
+
* metadata; the file tree is listed on demand by the injector.
|
|
294
|
+
*/
|
|
295
|
+
export function buildActiveSurfaceContext(params: {
|
|
296
|
+
currentActiveSurfaceId: string | undefined;
|
|
297
|
+
currentPage: string | undefined;
|
|
298
|
+
surfaceState: ReadonlyMap<
|
|
299
|
+
string,
|
|
300
|
+
{ surfaceType: SurfaceType; data: SurfaceData }
|
|
301
|
+
>;
|
|
302
|
+
}): ActiveSurfaceContext | null {
|
|
303
|
+
const { currentActiveSurfaceId, currentPage, surfaceState } = params;
|
|
304
|
+
if (!currentActiveSurfaceId) return null;
|
|
305
|
+
|
|
306
|
+
const stored = surfaceState.get(currentActiveSurfaceId);
|
|
307
|
+
if (!stored || stored.surfaceType !== "dynamic_page") return null;
|
|
308
|
+
|
|
309
|
+
const data = stored.data as DynamicPageSurfaceData;
|
|
310
|
+
const activeSurface: ActiveSurfaceContext = {
|
|
311
|
+
surfaceId: currentActiveSurfaceId,
|
|
312
|
+
html: data.html,
|
|
313
|
+
currentPage,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
if (data.appId) {
|
|
317
|
+
const app = getApp(data.appId);
|
|
318
|
+
if (app) {
|
|
319
|
+
activeSurface.appId = app.id;
|
|
320
|
+
activeSurface.appName = app.name;
|
|
321
|
+
activeSurface.appDirName = resolveAppDir(app.id).dirName;
|
|
322
|
+
activeSurface.appSchemaJson = app.schemaJson;
|
|
323
|
+
if (app.pages && Object.keys(app.pages).length > 0) {
|
|
324
|
+
activeSurface.appPages = app.pages;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return activeSurface;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Lists the conversation's active documents as the lightweight summaries the
|
|
334
|
+
* `active-documents` injector surfaces to the assistant — letting it target
|
|
335
|
+
* existing documents with `document_update` instead of issuing duplicate
|
|
336
|
+
* `document_create` calls. Returns `null` when the conversation has none.
|
|
337
|
+
*/
|
|
338
|
+
export function buildActiveDocuments(conversationId: string): Array<{
|
|
339
|
+
surfaceId: string;
|
|
340
|
+
title: string;
|
|
341
|
+
wordCount: number;
|
|
342
|
+
updatedAt: number;
|
|
343
|
+
}> | null {
|
|
344
|
+
const conversationDocs = getDocumentsForConversation(conversationId);
|
|
345
|
+
return conversationDocs.length > 0
|
|
346
|
+
? conversationDocs.map((d) => ({
|
|
347
|
+
surfaceId: d.surfaceId,
|
|
348
|
+
title: d.title,
|
|
349
|
+
wordCount: d.wordCount,
|
|
350
|
+
updatedAt: d.updatedAt,
|
|
351
|
+
}))
|
|
352
|
+
: null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Resolves the `<workspace>` top-level block for the runtime injector, or
|
|
357
|
+
* `null` when the turn isn't injecting it. The refresh runs every turn so a
|
|
358
|
+
* workspace-mutating tool's `markWorkspaceTopLevelDirty` is picked up on the
|
|
359
|
+
* following turn; it is dirty-guarded, so it only rescans when the cache is
|
|
360
|
+
* stale.
|
|
361
|
+
*/
|
|
362
|
+
export function buildWorkspaceTopLevelContext(
|
|
363
|
+
ctx: {
|
|
364
|
+
refreshWorkspaceTopLevelContextIfNeeded(): void;
|
|
365
|
+
workspaceTopLevelContext: string | null;
|
|
366
|
+
},
|
|
367
|
+
shouldInject: boolean,
|
|
368
|
+
): string | null {
|
|
369
|
+
ctx.refreshWorkspaceTopLevelContextIfNeeded();
|
|
370
|
+
return shouldInject ? ctx.workspaceTopLevelContext : null;
|
|
276
371
|
}
|
|
277
372
|
|
|
278
373
|
const MAX_CONTEXT_LENGTH = 100_000;
|
|
@@ -315,7 +410,7 @@ function injectActiveSurfaceContext(
|
|
|
315
410
|
);
|
|
316
411
|
|
|
317
412
|
// File tree with sizes (capped at 50 files to bound prompt size)
|
|
318
|
-
const files =
|
|
413
|
+
const files = listAppFiles(ctx.appId);
|
|
319
414
|
const MAX_FILE_TREE_ENTRIES = 50;
|
|
320
415
|
const displayFiles = files.slice(0, MAX_FILE_TREE_ENTRIES);
|
|
321
416
|
lines.push("", "App files:");
|
|
@@ -514,30 +609,7 @@ function injectVoiceCallControlContext(
|
|
|
514
609
|
// NOW.md scratchpad injection
|
|
515
610
|
// ---------------------------------------------------------------------------
|
|
516
611
|
|
|
517
|
-
/**
|
|
518
|
-
* Read the NOW.md scratchpad from the workspace prompt directory.
|
|
519
|
-
*
|
|
520
|
-
* Returns the trimmed content with `_`-prefixed comment lines stripped,
|
|
521
|
-
* or `null` if the file is missing, empty, or unreadable.
|
|
522
|
-
*/
|
|
523
|
-
export function readNowScratchpad(): string | null {
|
|
524
|
-
const nowPath = getWorkspacePromptPath("NOW.md");
|
|
525
|
-
if (!existsSync(nowPath)) return null;
|
|
526
|
-
try {
|
|
527
|
-
const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
|
|
528
|
-
return stripped.length > 0 ? stripped : null;
|
|
529
|
-
} catch {
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* The `<NOW.md>` block is emitted by the `now-md` default injector
|
|
536
|
-
* (`plugins/defaults/injectors/register.ts`) as an `after-memory-prefix` placement.
|
|
537
|
-
* Use {@link applyRuntimeInjections} with `options.nowScratchpad` set.
|
|
538
|
-
*/
|
|
539
|
-
|
|
540
|
-
/** Strip `<NOW.md>` blocks injected by `injectNowScratchpad`. */
|
|
612
|
+
/** Strip `<NOW.md>` blocks injected by the `now-md` default injector. */
|
|
541
613
|
export function stripNowScratchpad(messages: Message[]): Message[] {
|
|
542
614
|
return stripUserTextBlocksByPrefix(messages, [
|
|
543
615
|
// Shared prefix catches both the current tag and any pre-line-limit
|
|
@@ -547,102 +619,6 @@ export function stripNowScratchpad(messages: Message[]): Message[] {
|
|
|
547
619
|
]);
|
|
548
620
|
}
|
|
549
621
|
|
|
550
|
-
// ---------------------------------------------------------------------------
|
|
551
|
-
// PKB (Personal Knowledge Base) injection
|
|
552
|
-
// ---------------------------------------------------------------------------
|
|
553
|
-
|
|
554
|
-
const PKB_DEFAULT_FILES = [
|
|
555
|
-
"INDEX.md",
|
|
556
|
-
"essentials.md",
|
|
557
|
-
"threads.md",
|
|
558
|
-
"buffer.md",
|
|
559
|
-
];
|
|
560
|
-
|
|
561
|
-
const AUTOINJECT_FILENAME = "_autoinject.md";
|
|
562
|
-
|
|
563
|
-
/** Max buffer.md lines injected into prompts — keeps context bounded even when filing is off. */
|
|
564
|
-
const MAX_BUFFER_LINES = 50;
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Read `_autoinject.md` from the PKB directory and return the list of
|
|
568
|
-
* filenames to inject.
|
|
569
|
-
*
|
|
570
|
-
* - Returns `null` when the file is missing or unreadable — callers
|
|
571
|
-
* should fall back to the hardcoded defaults.
|
|
572
|
-
* - Returns `[]` when the file exists but has no entries (empty or
|
|
573
|
-
* comments only) — an explicit opt-out meaning "inject nothing."
|
|
574
|
-
*/
|
|
575
|
-
export function readAutoinjectList(pkbDir: string): string[] | null {
|
|
576
|
-
const filePath = join(pkbDir, AUTOINJECT_FILENAME);
|
|
577
|
-
if (!existsSync(filePath)) return null;
|
|
578
|
-
try {
|
|
579
|
-
const raw = stripCommentLines(readFileSync(filePath, "utf-8"));
|
|
580
|
-
const files = raw
|
|
581
|
-
.split("\n")
|
|
582
|
-
.map((l) => l.trim())
|
|
583
|
-
.filter((l) => l.length > 0);
|
|
584
|
-
return files.length > 0 ? files : [];
|
|
585
|
-
} catch {
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Resolve the effective list of auto-inject filenames for a PKB directory.
|
|
592
|
-
*
|
|
593
|
-
* This is the single source of truth used both by `readPkbContext` (which
|
|
594
|
-
* actually injects the files) and by the PKB reminder-hint tracker in
|
|
595
|
-
* `conversation-agent-loop.ts` (which needs to know what's already in
|
|
596
|
-
* context so it doesn't redundantly recommend those files).
|
|
597
|
-
*
|
|
598
|
-
* Returns `PKB_DEFAULT_FILES` when `_autoinject.md` is missing/unreadable,
|
|
599
|
-
* or the parsed list (possibly empty) when it is present.
|
|
600
|
-
*/
|
|
601
|
-
export function getPkbAutoInjectList(pkbRoot: string): string[] {
|
|
602
|
-
return readAutoinjectList(pkbRoot) ?? PKB_DEFAULT_FILES;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Read the always-loaded PKB files and append a nudge encouraging the
|
|
607
|
-
* assistant to proactively read topic files and use `remember` aggressively.
|
|
608
|
-
*
|
|
609
|
-
* Which files are loaded is determined by `pkb/_autoinject.md` (one filename
|
|
610
|
-
* per line). Falls back to the built-in defaults when that file is absent.
|
|
611
|
-
*
|
|
612
|
-
* Returns the concatenated content ready for injection, or `null` if all
|
|
613
|
-
* files are missing or empty.
|
|
614
|
-
*/
|
|
615
|
-
export function readPkbContext(): string | null {
|
|
616
|
-
const pkbDir = join(getWorkspaceDir(), "pkb");
|
|
617
|
-
if (!existsSync(pkbDir)) return null;
|
|
618
|
-
|
|
619
|
-
const filesToInject = getPkbAutoInjectList(pkbDir);
|
|
620
|
-
|
|
621
|
-
const parts: string[] = [];
|
|
622
|
-
for (const file of filesToInject) {
|
|
623
|
-
// Path traversal guard: reject entries that escape the pkb directory
|
|
624
|
-
const filePath = resolve(pkbDir, file);
|
|
625
|
-
if (!filePath.startsWith(pkbDir + "/")) continue;
|
|
626
|
-
|
|
627
|
-
if (!existsSync(filePath)) continue;
|
|
628
|
-
try {
|
|
629
|
-
let content = stripCommentLines(readFileSync(filePath, "utf-8")).trim();
|
|
630
|
-
if (file === "buffer.md" && content.length > 0) {
|
|
631
|
-
// Cap buffer entries to prevent unbounded growth when filing is disabled
|
|
632
|
-
const lines = content.split("\n");
|
|
633
|
-
if (lines.length > MAX_BUFFER_LINES) {
|
|
634
|
-
content = lines.slice(-MAX_BUFFER_LINES).join("\n");
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
if (content.length > 0) parts.push(content);
|
|
638
|
-
} catch {
|
|
639
|
-
// Skip unreadable files
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return parts.length > 0 ? parts.join("\n\n") : null;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
622
|
/**
|
|
647
623
|
* Prepend channel capability context to the last user message so the
|
|
648
624
|
* model knows what the current channel can and cannot do.
|
|
@@ -994,53 +970,6 @@ export function buildUnifiedTurnContextBlock(
|
|
|
994
970
|
return lines.join("\n");
|
|
995
971
|
}
|
|
996
972
|
|
|
997
|
-
// ---------------------------------------------------------------------------
|
|
998
|
-
// Prefix-based stripping primitive
|
|
999
|
-
// ---------------------------------------------------------------------------
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* A matcher for an injected text block. A plain string matches by prefix
|
|
1003
|
-
* (`startsWith`). A `{ prefix, suffix }` wrapper requires BOTH the opening
|
|
1004
|
-
* prefix and the closing suffix, so user-authored content that merely begins
|
|
1005
|
-
* with an injection-like opening tag (e.g. a message discussing `<info>`
|
|
1006
|
-
* markup) is not mistaken for an injected block and dropped. This mirrors
|
|
1007
|
-
* `countMemoryPrefixBlocks`, which only treats `<memory>…</memory>` /
|
|
1008
|
-
* `<info>…</info>` blocks as injected when the full wrapper is present.
|
|
1009
|
-
*/
|
|
1010
|
-
type InjectionMatcher = string | { prefix: string; suffix: string };
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* Remove text blocks from user messages that match any of the given matchers.
|
|
1014
|
-
* If stripping removes all content blocks from a message, the message itself
|
|
1015
|
-
* is dropped.
|
|
1016
|
-
*
|
|
1017
|
-
* This is the shared primitive behind the individual strip* functions and
|
|
1018
|
-
* the `stripInjectionsForCompaction` pipeline.
|
|
1019
|
-
*/
|
|
1020
|
-
function stripUserTextBlocksByPrefix(
|
|
1021
|
-
messages: Message[],
|
|
1022
|
-
matchers: InjectionMatcher[],
|
|
1023
|
-
): Message[] {
|
|
1024
|
-
return messages
|
|
1025
|
-
.map((message) => {
|
|
1026
|
-
if (message.role !== "user") return message;
|
|
1027
|
-
const nextContent = message.content.filter((block) => {
|
|
1028
|
-
if (block.type !== "text") return true;
|
|
1029
|
-
return !matchers.some((m) =>
|
|
1030
|
-
typeof m === "string"
|
|
1031
|
-
? block.text.startsWith(m)
|
|
1032
|
-
: block.text.startsWith(m.prefix) && block.text.endsWith(m.suffix),
|
|
1033
|
-
);
|
|
1034
|
-
});
|
|
1035
|
-
if (nextContent.length === message.content.length) return message;
|
|
1036
|
-
if (nextContent.length === 0) return null;
|
|
1037
|
-
return { ...message, content: nextContent };
|
|
1038
|
-
})
|
|
1039
|
-
.filter(
|
|
1040
|
-
(message): message is NonNullable<typeof message> => message != null,
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
973
|
// ---------------------------------------------------------------------------
|
|
1045
974
|
// Individual strip functions (thin wrappers around the primitive)
|
|
1046
975
|
// ---------------------------------------------------------------------------
|
|
@@ -1726,98 +1655,6 @@ export function loadSlackActiveThreadFocusBlock(
|
|
|
1726
1655
|
return assembleSlackActiveThreadFocusBlock(rows, capabilities);
|
|
1727
1656
|
}
|
|
1728
1657
|
|
|
1729
|
-
/** Matchers stripped by the pipeline (order doesn't matter — single pass). */
|
|
1730
|
-
const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
|
|
1731
|
-
"<channel_capabilities>",
|
|
1732
|
-
"<channel_command_context>",
|
|
1733
|
-
"<disk_pressure_warning>",
|
|
1734
|
-
"<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
|
|
1735
|
-
"<guardian_context>",
|
|
1736
|
-
"<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
|
|
1737
|
-
"<interface_turn_context>", // backward-compat: strip legacy separate interface blocks
|
|
1738
|
-
// NOTE: <turn_context> is intentionally NOT stripped — unified turn context
|
|
1739
|
-
// blocks persist in history so the assistant retains temporal/actor grounding.
|
|
1740
|
-
"<background_turn>",
|
|
1741
|
-
"<memory_context __injected>",
|
|
1742
|
-
"<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
|
|
1743
|
-
// The static `memory-v2-static` block (`<info>\n…</info>`) and the
|
|
1744
|
-
// dynamic activation block (`<memory>\n…</memory>`, plus legacy
|
|
1745
|
-
// `<memory __injected>…`) are both stripped so each compaction
|
|
1746
|
-
// re-injects the freshest essentials/threads/recent/buffer view and
|
|
1747
|
-
// re-runs the activation pipeline, matching the `<knowledge_base>`
|
|
1748
|
-
// cadence. The activation pipeline dedupes via `everInjected`, and
|
|
1749
|
-
// compaction handles aggregate growth, so accumulation does not cause
|
|
1750
|
-
// unbounded context growth. Both wrappers may appear in persisted rows.
|
|
1751
|
-
//
|
|
1752
|
-
// These two use the full `{ prefix, suffix }` wrapper shape (not a bare
|
|
1753
|
-
// prefix) so that user-authored text merely starting with `<memory>\n` or
|
|
1754
|
-
// `<info>\n` is never silently dropped during compaction/`/clean`. This
|
|
1755
|
-
// matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
|
|
1756
|
-
{ prefix: "<memory>\n", suffix: "\n</memory>" },
|
|
1757
|
-
{ prefix: "<info>\n", suffix: "\n</info>" },
|
|
1758
|
-
"<voice_call_control>",
|
|
1759
|
-
"<workspace_top_level>", // backward-compat: strip legacy workspace blocks
|
|
1760
|
-
// NOTE: <workspace> is intentionally NOT stripped — workspace context
|
|
1761
|
-
// persists in history so the assistant retains workspace grounding.
|
|
1762
|
-
"<temporal_context>\nToday:", // backward-compat: strip legacy temporal blocks
|
|
1763
|
-
"<active_subagents>",
|
|
1764
|
-
"<active_workspace>",
|
|
1765
|
-
"<active_dynamic_page>",
|
|
1766
|
-
"<non_interactive_context>",
|
|
1767
|
-
// Shared prefix catches both the current NOW.md tag and any pre-line-limit
|
|
1768
|
-
// variant that may linger in in-flight histories during a rolling deploy.
|
|
1769
|
-
"<NOW.md Always keep this up to date",
|
|
1770
|
-
"<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
|
|
1771
|
-
"<knowledge_base>",
|
|
1772
|
-
"<pkb>", // backward-compat: strip legacy tag from pre-rename history
|
|
1773
|
-
"<system_reminder>",
|
|
1774
|
-
"<transport_hints>",
|
|
1775
|
-
// The Slack active-thread focus block is non-persisted and injected on
|
|
1776
|
-
// the FINAL user turn only. Strip it here so re-assembly during compaction
|
|
1777
|
-
// and overflow recovery does not duplicate it across turns.
|
|
1778
|
-
"<active_thread>",
|
|
1779
|
-
"<system_notice>One or more tool calls returned an error.",
|
|
1780
|
-
];
|
|
1781
|
-
|
|
1782
|
-
/**
|
|
1783
|
-
* Strip all runtime-injected context from message history in a single pass.
|
|
1784
|
-
*
|
|
1785
|
-
* Used only during compaction and overflow recovery — not on normal turns.
|
|
1786
|
-
* Runtime injections persist in history to keep the conversation prefix
|
|
1787
|
-
* stable for Anthropic's prefix caching. Stripping is only needed when
|
|
1788
|
-
* compaction rewrites the message array (cache miss is expected anyway).
|
|
1789
|
-
*/
|
|
1790
|
-
export function stripInjectionsForCompaction(messages: Message[]): Message[] {
|
|
1791
|
-
return stripUserTextBlocksByPrefix(messages, RUNTIME_INJECTION_PREFIXES);
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
* Extract the most recently injected NOW.md content from the message history.
|
|
1796
|
-
* Returns null if no NOW.md injection is found.
|
|
1797
|
-
*/
|
|
1798
|
-
export function findLastInjectedNowContent(messages: Message[]): string | null {
|
|
1799
|
-
// Matches every NOW.md opening tag we emit (the tag text may evolve over
|
|
1800
|
-
// time, e.g. adding a line-limit hint), so in-flight histories with older
|
|
1801
|
-
// tag variants remain discoverable during a rolling deploy.
|
|
1802
|
-
const openTagPrefix = "<NOW.md Always keep this up to date";
|
|
1803
|
-
const suffix = "\n</NOW.md>";
|
|
1804
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1805
|
-
const msg = messages[i];
|
|
1806
|
-
if (msg.role !== "user") continue;
|
|
1807
|
-
for (const block of msg.content) {
|
|
1808
|
-
if (block.type !== "text" || !block.text.startsWith(openTagPrefix)) {
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
const tagEnd = block.text.indexOf(">\n");
|
|
1812
|
-
if (tagEnd < 0) continue;
|
|
1813
|
-
const contentStart = tagEnd + ">\n".length;
|
|
1814
|
-
const end = block.text.lastIndexOf(suffix);
|
|
1815
|
-
if (end > contentStart) return block.text.slice(contentStart, end);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
return null;
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1658
|
/**
|
|
1822
1659
|
* Controls which runtime injections are applied.
|
|
1823
1660
|
*
|
|
@@ -1865,8 +1702,14 @@ export interface RuntimeInjectionResult {
|
|
|
1865
1702
|
}
|
|
1866
1703
|
|
|
1867
1704
|
/**
|
|
1868
|
-
* Run every
|
|
1869
|
-
* and return every non-null block
|
|
1705
|
+
* Run every {@link Injector} in the chain ({@link getInjectorChain}, already
|
|
1706
|
+
* sorted by ascending `order`) and return every non-null block it produced.
|
|
1707
|
+
*
|
|
1708
|
+
* `runMessages` is the turn's working message array, forwarded to each
|
|
1709
|
+
* injector so producers that need the current prompt contents read it from a
|
|
1710
|
+
* parameter rather than the shared {@link TurnContext}. Omitted by text-only
|
|
1711
|
+
* callers ({@link composeInjectorChain}) that drive the chain without a
|
|
1712
|
+
* message array.
|
|
1870
1713
|
*
|
|
1871
1714
|
* Injectors returning `null` are omitted from the result. The returned array
|
|
1872
1715
|
* preserves ascending-`order` sort so downstream callers (notably
|
|
@@ -1875,12 +1718,11 @@ export interface RuntimeInjectionResult {
|
|
|
1875
1718
|
*/
|
|
1876
1719
|
async function collectInjectorBlocks(
|
|
1877
1720
|
ctx: TurnContext,
|
|
1721
|
+
runMessages?: Message[],
|
|
1878
1722
|
): Promise<InjectionBlock[]> {
|
|
1879
|
-
const injectors = getInjectors();
|
|
1880
|
-
if (injectors.length === 0) return [];
|
|
1881
1723
|
const out: InjectionBlock[] = [];
|
|
1882
|
-
for (const injector of
|
|
1883
|
-
const block = await injector.produce(ctx);
|
|
1724
|
+
for (const injector of getInjectorChain()) {
|
|
1725
|
+
const block = await injector.produce(ctx, runMessages);
|
|
1884
1726
|
if (block) out.push(block);
|
|
1885
1727
|
}
|
|
1886
1728
|
return out;
|
|
@@ -2017,44 +1859,6 @@ export interface RuntimeInjectionOptions {
|
|
|
2017
1859
|
channelCommandContext?: ChannelCommandContext | null;
|
|
2018
1860
|
unifiedTurnContext?: string | null;
|
|
2019
1861
|
voiceCallControlPrompt?: string | null;
|
|
2020
|
-
pkbContext?: string | null;
|
|
2021
|
-
pkbActive?: boolean;
|
|
2022
|
-
/**
|
|
2023
|
-
* Dense query vector surfaced from the graph memory retriever.
|
|
2024
|
-
* When present together with `pkbActive`, used to run `searchPkbFiles`
|
|
2025
|
-
* to surface relevance hints in the PKB system reminder. When missing,
|
|
2026
|
-
* the reminder falls back to the flat static text.
|
|
2027
|
-
*/
|
|
2028
|
-
pkbQueryVector?: number[];
|
|
2029
|
-
/** Optional sparse vector accompanying `pkbQueryVector`. */
|
|
2030
|
-
pkbSparseVector?: QdrantSparseVector;
|
|
2031
|
-
/** Memory scope id used to filter PKB search results. */
|
|
2032
|
-
pkbScopeId?: string;
|
|
2033
|
-
/**
|
|
2034
|
-
* The live conversation (or a minimal shape containing `messages`) used
|
|
2035
|
-
* to compute which PKB paths are already "in context" and therefore
|
|
2036
|
-
* suppressed from hint suggestions.
|
|
2037
|
-
*/
|
|
2038
|
-
pkbConversation?: PkbContextConversation;
|
|
2039
|
-
/** Auto-injected PKB filenames (resolved relative to `pkbRoot`). */
|
|
2040
|
-
pkbAutoInjectList?: string[];
|
|
2041
|
-
/** Absolute path to the PKB directory (e.g. `<workspace>/pkb`). */
|
|
2042
|
-
pkbRoot?: string;
|
|
2043
|
-
/**
|
|
2044
|
-
* Working directory against which relative `file_read` tool paths
|
|
2045
|
-
* resolve, used to detect workspace-relative reads like
|
|
2046
|
-
* `pkb/threads.md`. Falls back to `pkbRoot` when omitted.
|
|
2047
|
-
*/
|
|
2048
|
-
pkbWorkingDir?: string;
|
|
2049
|
-
/**
|
|
2050
|
-
* Pre-rendered v2 static memory content (essentials/threads/recent/buffer
|
|
2051
|
-
* concatenated, header-wrapped). When non-null on full-mode turns the
|
|
2052
|
-
* `memory-v2-static` injector wraps it in `<info>` and splices it onto
|
|
2053
|
-
* the user message; subsequent turns leave the prior block cached on its
|
|
2054
|
-
* original user message.
|
|
2055
|
-
*/
|
|
2056
|
-
memoryV2Static?: string | null;
|
|
2057
|
-
nowScratchpad?: string | null;
|
|
2058
1862
|
subagentStatusBlock?: string | null;
|
|
2059
1863
|
isNonInteractive?: boolean;
|
|
2060
1864
|
/**
|
|
@@ -2101,20 +1905,6 @@ export interface RuntimeInjectionOptions {
|
|
|
2101
1905
|
slackActiveThreadFocusBlock?: string | null;
|
|
2102
1906
|
activeDocuments?: TurnInjectionInputs["activeDocuments"];
|
|
2103
1907
|
mode?: InjectionMode;
|
|
2104
|
-
/**
|
|
2105
|
-
* memory-v3-live: when true AND the v3 injector produced a `<memory>` block
|
|
2106
|
-
* this turn (placement `after-memory-prefix`, id `memory-v3`), the v2
|
|
2107
|
-
* `<memory>` injection that `graphMemory.prepareMemory` prepended to the
|
|
2108
|
-
* tail is stripped from EVERY user message before the v3 block is spliced —
|
|
2109
|
-
* so v3 becomes the sole `<memory>` source and history stays byte-stable for
|
|
2110
|
-
* prompt caching.
|
|
2111
|
-
*
|
|
2112
|
-
* The strip is keyed off whether v3 ACTUALLY produced a block, not off the
|
|
2113
|
-
* flag alone: when v3 errors or selects nothing (its injector returns
|
|
2114
|
-
* `null`), v2's block is left in place so the turn still ships memory rather
|
|
2115
|
-
* than dropping it (fallback-to-v2). Default false — v2 untouched.
|
|
2116
|
-
*/
|
|
2117
|
-
suppressV2MemoryForV3?: boolean;
|
|
2118
1908
|
/**
|
|
2119
1909
|
* Per-turn {@link TurnContext} forwarded to plugin-registered
|
|
2120
1910
|
* {@link Injector}s via {@link collectInjectorBlocks}. When omitted,
|
|
@@ -2147,17 +1937,6 @@ function buildTurnInjectionInputs(
|
|
|
2147
1937
|
diskPressureContext: options.diskPressureContext,
|
|
2148
1938
|
workspaceTopLevelContext: options.workspaceTopLevelContext,
|
|
2149
1939
|
unifiedTurnContext: options.unifiedTurnContext,
|
|
2150
|
-
pkbContext: options.pkbContext,
|
|
2151
|
-
pkbActive: options.pkbActive,
|
|
2152
|
-
pkbQueryVector: options.pkbQueryVector,
|
|
2153
|
-
pkbSparseVector: options.pkbSparseVector,
|
|
2154
|
-
pkbScopeId: options.pkbScopeId,
|
|
2155
|
-
pkbConversation: options.pkbConversation,
|
|
2156
|
-
pkbAutoInjectList: options.pkbAutoInjectList,
|
|
2157
|
-
pkbRoot: options.pkbRoot,
|
|
2158
|
-
pkbWorkingDir: options.pkbWorkingDir,
|
|
2159
|
-
memoryV2Static: options.memoryV2Static,
|
|
2160
|
-
nowScratchpad: options.nowScratchpad,
|
|
2161
1940
|
subagentStatusBlock: options.subagentStatusBlock,
|
|
2162
1941
|
channelCapabilities: options.channelCapabilities,
|
|
2163
1942
|
slackChronologicalMessages: options.slackChronologicalMessages,
|
|
@@ -2245,7 +2024,7 @@ export async function applyRuntimeInjections(
|
|
|
2245
2024
|
? { ...options.turnContext, injectionInputs }
|
|
2246
2025
|
: synthesizeFallbackTurnContext(injectionInputs);
|
|
2247
2026
|
|
|
2248
|
-
const chainBlocks = await collectInjectorBlocks(turnCtx);
|
|
2027
|
+
const chainBlocks = await collectInjectorBlocks(turnCtx, runMessages);
|
|
2249
2028
|
|
|
2250
2029
|
// Split the chain output by placement so the downstream assembly can
|
|
2251
2030
|
// process each slot with the correct ordering rule.
|
|
@@ -2325,21 +2104,25 @@ export async function applyRuntimeInjections(
|
|
|
2325
2104
|
: undefined;
|
|
2326
2105
|
|
|
2327
2106
|
// ── Step 0: memory-v3-live v2 suppression ──
|
|
2328
|
-
// When v3
|
|
2329
|
-
// turn, v3 is the sole `<memory>` source. v2's `prepareMemory`
|
|
2330
|
-
// prepended its own `<memory>` block to the tail user message (and
|
|
2331
|
-
// turns may carry v2 blocks from earlier turns). Strip every user
|
|
2332
|
-
// memory prefix here so:
|
|
2107
|
+
// When the `memory-v3-live` flag is on AND the v3 injector actually produced
|
|
2108
|
+
// a block this turn, v3 is the sole `<memory>` source. v2's `prepareMemory`
|
|
2109
|
+
// already prepended its own `<memory>` block to the tail user message (and
|
|
2110
|
+
// historical turns may carry v2 blocks from earlier turns). Strip every user
|
|
2111
|
+
// message's memory prefix here so:
|
|
2333
2112
|
// 1. The v3 `after-memory-prefix` block (Step 2) lands at the top of the
|
|
2334
2113
|
// tail with no v2 prefix ahead of it — exactly one `<memory>` block.
|
|
2335
2114
|
// 2. History is byte-stable across turns for prompt caching.
|
|
2336
2115
|
// Keyed off the v3 block being present (not the flag alone) so a v3 failure
|
|
2337
2116
|
// (`produce()` → null) leaves v2's block intact — fallback rather than a
|
|
2338
2117
|
// memory-less turn. Idempotent: re-injection sites that already stripped
|
|
2339
|
-
// see no change.
|
|
2118
|
+
// see no change. Flag off → bit-for-bit identical to the v2 path.
|
|
2119
|
+
const suppressV2MemoryForV3 = isAssistantFeatureFlagEnabled(
|
|
2120
|
+
"memory-v3-live",
|
|
2121
|
+
getConfig(),
|
|
2122
|
+
);
|
|
2340
2123
|
const v3ProducedBlock = afterMemory.some((b) => b.id === MEMORY_V3_BLOCK_ID);
|
|
2341
2124
|
let runMessagesForAssembly = runMessages;
|
|
2342
|
-
if (
|
|
2125
|
+
if (suppressV2MemoryForV3 && v3ProducedBlock) {
|
|
2343
2126
|
runMessagesForAssembly = stripAllMemoryInjections(runMessages);
|
|
2344
2127
|
}
|
|
2345
2128
|
|
|
@@ -14,43 +14,24 @@ import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibil
|
|
|
14
14
|
export type SlashResolution =
|
|
15
15
|
| { kind: "passthrough"; content: string }
|
|
16
16
|
| { kind: "unknown"; message: string }
|
|
17
|
-
| { kind: "compact"
|
|
17
|
+
| { kind: "compact" }
|
|
18
18
|
| { kind: "clean" };
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
"Usage: `/compact [<tokens>]` (e.g. `/compact 30000`, `/compact 30k`, `/compact 1m`).";
|
|
20
|
+
type CompactParse = { kind: "compact" } | { kind: "unknown"; message: string };
|
|
22
21
|
|
|
23
|
-
type CompactParse =
|
|
24
|
-
| { kind: "compact"; targetInputTokensOverride?: number }
|
|
25
|
-
| { kind: "unknown"; message: string };
|
|
26
|
-
|
|
27
|
-
const TOKEN_COUNT_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
|
|
28
22
|
const COMPACT_COMMAND_PATTERN = /^\/compact(?:\s+(.+?))?\s*$/i;
|
|
29
23
|
|
|
30
|
-
function parseTokenCount(input: string): number | null {
|
|
31
|
-
const match = input.match(TOKEN_COUNT_PATTERN);
|
|
32
|
-
if (!match) return null;
|
|
33
|
-
const value = Number.parseFloat(match[1]);
|
|
34
|
-
if (!Number.isFinite(value) || value <= 0) return null;
|
|
35
|
-
const suffix = match[2]?.toLowerCase();
|
|
36
|
-
const multiplier = suffix === "m" ? 1_000_000 : suffix === "k" ? 1_000 : 1;
|
|
37
|
-
const tokens = Math.floor(value * multiplier);
|
|
38
|
-
return tokens > 0 ? tokens : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
24
|
function parseCompactCommand(trimmed: string): CompactParse | null {
|
|
42
25
|
const match = trimmed.match(COMPACT_COMMAND_PATTERN);
|
|
43
26
|
if (!match) return null;
|
|
44
27
|
const rest = match[1]?.trim();
|
|
45
|
-
if (
|
|
46
|
-
const tokens = parseTokenCount(rest);
|
|
47
|
-
if (tokens == null) {
|
|
28
|
+
if (rest) {
|
|
48
29
|
return {
|
|
49
30
|
kind: "unknown",
|
|
50
|
-
message:
|
|
31
|
+
message: `\`/compact\` does not take arguments. Usage: \`/compact\`.`,
|
|
51
32
|
};
|
|
52
33
|
}
|
|
53
|
-
return { kind: "compact"
|
|
34
|
+
return { kind: "compact" };
|
|
54
35
|
}
|
|
55
36
|
|
|
56
37
|
type CleanParse = { kind: "clean" } | { kind: "unknown"; message: string };
|
|
@@ -448,7 +429,7 @@ export async function resolveSlash(
|
|
|
448
429
|
return await resolveModelList();
|
|
449
430
|
}
|
|
450
431
|
|
|
451
|
-
// Handle /compact command (
|
|
432
|
+
// Handle /compact command (summarize history; takes no arguments).
|
|
452
433
|
const compactParse = parseCompactCommand(trimmed);
|
|
453
434
|
if (compactParse) return compactParse;
|
|
454
435
|
|