@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
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
* turn after turn. The trailing recent-context / current-message block changes
|
|
15
15
|
* every turn and carries no breakpoint. This mirrors `./router.ts`.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
17
|
+
* Failure handling. A deliberate "select everything" and a model-call failure
|
|
18
|
+
* are different events with different outcomes:
|
|
19
|
+
* - explicit `ids` → select exactly those pages,
|
|
20
|
+
* - explicit empty `ids: []` → select nothing (deliberate abstention),
|
|
21
|
+
* - omitted `ids` → select ALL members of the leaf (the recall-safe "this
|
|
22
|
+
* whole leaf is relevant" signal, e.g. "give me all of X"); bounded to one
|
|
23
|
+
* leaf, so unlike the router this stays a select-all,
|
|
24
|
+
* - infrastructure failure (provider unavailable, a throw that survived the
|
|
25
|
+
* provider's own retries, no usable `tool_use`, or a schema mismatch) →
|
|
26
|
+
* select nothing after a short re-prompt retry, degrading to the
|
|
27
|
+
* deterministic recall lanes (core, needle, carry-forward working set) the
|
|
28
|
+
* orchestrator unions in regardless.
|
|
22
29
|
*/
|
|
23
30
|
|
|
24
31
|
import { z } from "zod";
|
|
@@ -30,6 +37,7 @@ import {
|
|
|
30
37
|
import type { Message, ToolDefinition } from "../../providers/types.js";
|
|
31
38
|
import { getLogger } from "../../util/logger.js";
|
|
32
39
|
import { mapLimit } from "../../util/map-limit.js";
|
|
40
|
+
import { retryForResult } from "./llm-retry.js";
|
|
33
41
|
import { cachedTextBlock } from "./provider-blocks.js";
|
|
34
42
|
import { membersOf } from "./tree.js";
|
|
35
43
|
import type { LeafPath, LeafTree, Slug, TurnContext } from "./types.js";
|
|
@@ -55,11 +63,13 @@ const SelectPagesSchema = z.object({
|
|
|
55
63
|
const SELECT_PAGES_TOOL: ToolDefinition = {
|
|
56
64
|
name: SELECT_PAGES_TOOL_NAME,
|
|
57
65
|
description:
|
|
58
|
-
"Select the pages in this leaf whose content
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"conversation is centrally about. Omit
|
|
62
|
-
"
|
|
66
|
+
"Select the pages in this leaf whose content the reply would directly " +
|
|
67
|
+
"draw on. Be selective — prefer a few precisely-relevant pages over many " +
|
|
68
|
+
"loosely-related ones; a leaf opened on a weak signal may yield none. " +
|
|
69
|
+
"Pass `pinned_ids` for pages the conversation is centrally about. Omit " +
|
|
70
|
+
"`ids` only as a recall-safe fallback when you cannot judge the leaf " +
|
|
71
|
+
"(selects every page); return `[]` when pages are present but none are " +
|
|
72
|
+
"directly relevant.",
|
|
63
73
|
input_schema: {
|
|
64
74
|
type: "object",
|
|
65
75
|
properties: {
|
|
@@ -75,11 +85,13 @@ const SELECT_PAGES_TOOL: ToolDefinition = {
|
|
|
75
85
|
},
|
|
76
86
|
};
|
|
77
87
|
|
|
78
|
-
const SYSTEM_PROMPT = `This leaf of the topic tree is potentially relevant to the conversation. Select the pages whose content
|
|
88
|
+
const SYSTEM_PROMPT = `This leaf of the topic tree is potentially relevant to the conversation. Select ONLY the pages whose content the reply to THIS message would directly draw on.
|
|
79
89
|
|
|
80
|
-
Be
|
|
90
|
+
Be selective: exclude pages that are merely topically adjacent, part of the ever-present background, or only loosely related. Most opened leaves should contribute a few precisely-relevant pages, not most of their contents — a leaf opened on a weak signal may yield none.
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
A page can also be directly relevant because of the current situation — the date or the live scratchpad — not only the message: keep a page the situation makes pertinent (e.g. a person whose anniversary is today).
|
|
93
|
+
|
|
94
|
+
If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the leaf (selects every page); return \`[]\` when the pages are present but none are directly relevant.`;
|
|
83
95
|
|
|
84
96
|
/**
|
|
85
97
|
* Render the STATIC numbered `<pages>` block for a leaf from its member slugs.
|
|
@@ -103,8 +115,10 @@ async function renderPagesBlock(
|
|
|
103
115
|
/**
|
|
104
116
|
* Run the L2 selector for a single opened leaf. Returns the pages to inject.
|
|
105
117
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
118
|
+
* An omitted `ids` selects ALL members (the recall-safe "whole leaf is
|
|
119
|
+
* relevant" signal); an explicit `[]` selects none; an infrastructure failure
|
|
120
|
+
* (after a short re-prompt retry) selects none, degrading to the deterministic
|
|
121
|
+
* recall lanes the orchestrator unions in.
|
|
108
122
|
*/
|
|
109
123
|
export async function selectFromLeaf(
|
|
110
124
|
leaf: LeafPath,
|
|
@@ -120,8 +134,11 @@ export async function selectFromLeaf(
|
|
|
120
134
|
|
|
121
135
|
const provider = await getConfiguredProvider("memoryV3SelectL2");
|
|
122
136
|
if (!provider) {
|
|
123
|
-
log.warn(
|
|
124
|
-
|
|
137
|
+
log.warn(
|
|
138
|
+
{ leaf },
|
|
139
|
+
"L2 selector provider unavailable; degrading to deterministic lanes",
|
|
140
|
+
);
|
|
141
|
+
return [];
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
const userMsg: Message = {
|
|
@@ -134,15 +151,21 @@ export async function selectFromLeaf(
|
|
|
134
151
|
{
|
|
135
152
|
type: "text",
|
|
136
153
|
text:
|
|
154
|
+
(turn.situationalContext
|
|
155
|
+
? `<situation>${turn.situationalContext}</situation>\n`
|
|
156
|
+
: "") +
|
|
137
157
|
`<recent_context>${turn.recentContext}</recent_context>\n` +
|
|
138
158
|
`<current_message>${turn.currentMessage}</current_message>`,
|
|
139
159
|
},
|
|
140
160
|
],
|
|
141
161
|
};
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
// One forced-tool call, retried a few times so a transient malformed response
|
|
164
|
+
// (no usable tool_use, or tool input that fails the schema) re-prompts before
|
|
165
|
+
// we give up. `null` from an attempt means "unusable, retry"; the provider
|
|
166
|
+
// layer already backs off transient throws, so this loop adds no delay.
|
|
167
|
+
const parsed = await retryForResult(async () => {
|
|
168
|
+
const response = await provider.sendMessage([userMsg], {
|
|
146
169
|
tools: [SELECT_PAGES_TOOL],
|
|
147
170
|
systemPrompt: SYSTEM_PROMPT,
|
|
148
171
|
config: {
|
|
@@ -150,39 +173,31 @@ export async function selectFromLeaf(
|
|
|
150
173
|
tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
|
|
151
174
|
},
|
|
152
175
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const toolBlock = extractToolUse(response);
|
|
159
|
-
if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) {
|
|
160
|
-
log.warn(
|
|
161
|
-
{ stopReason: response.stopReason, leaf },
|
|
162
|
-
"L2 selector returned no select_pages tool_use; selecting all",
|
|
163
|
-
);
|
|
164
|
-
return allMembers();
|
|
165
|
-
}
|
|
176
|
+
const toolBlock = extractToolUse(response);
|
|
177
|
+
if (!toolBlock || toolBlock.name !== SELECT_PAGES_TOOL_NAME) return null;
|
|
178
|
+
const result = SelectPagesSchema.safeParse(toolBlock.input);
|
|
179
|
+
return result.success ? result.data : null;
|
|
180
|
+
});
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
if (!parsed.success) {
|
|
182
|
+
if (parsed === null) {
|
|
169
183
|
log.warn(
|
|
170
|
-
{
|
|
171
|
-
"L2 selector
|
|
184
|
+
{ leaf },
|
|
185
|
+
"L2 selector could not obtain a selection after retries; degrading to deterministic lanes",
|
|
172
186
|
);
|
|
173
|
-
return
|
|
187
|
+
return [];
|
|
174
188
|
}
|
|
175
189
|
|
|
176
|
-
// Omitted `ids` is the recall-safe "
|
|
177
|
-
|
|
190
|
+
// Omitted `ids` is the recall-safe "this whole leaf is relevant" signal.
|
|
191
|
+
// Bounded to one leaf, so it stays a select-all (unlike the L1 router).
|
|
192
|
+
if (parsed.ids === undefined) return allMembers();
|
|
178
193
|
|
|
179
|
-
const pinned = new Set(parsed.
|
|
194
|
+
const pinned = new Set(parsed.pinned_ids ?? []);
|
|
180
195
|
|
|
181
196
|
// Map 1-based IDs back to member slugs, dropping out-of-range IDs without
|
|
182
197
|
// throwing. De-duplicate while preserving model-returned order.
|
|
183
198
|
const seen = new Set<number>();
|
|
184
199
|
const selected: SelectedPage[] = [];
|
|
185
|
-
for (const id of parsed.
|
|
200
|
+
for (const id of parsed.ids) {
|
|
186
201
|
if (id < 1 || id > members.length || seen.has(id)) continue;
|
|
187
202
|
seen.add(id);
|
|
188
203
|
selected.push({ slug: members[id - 1]!, pinned: pinned.has(id) });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory v3 — flag-gated shadow/live
|
|
2
|
+
* Memory v3 — flag-gated shadow/live injector.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* An {@link Injector} that runs the v3 orchestrator each turn and
|
|
5
5
|
* records its selection set to `memory_v3_selections`. Two flags gate its
|
|
6
6
|
* injection behavior:
|
|
7
7
|
*
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
* empty selection) falls back to v2 memory rather than dropping all memory.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
33
|
+
|
|
32
34
|
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
33
35
|
import { getConfig } from "../../config/loader.js";
|
|
34
36
|
import type { AssistantConfig } from "../../config/schema.js";
|
|
@@ -38,11 +40,14 @@ import { stringifyMessageContent } from "../../memory/message-content.js";
|
|
|
38
40
|
import {
|
|
39
41
|
type InjectionBlock,
|
|
40
42
|
type Injector,
|
|
41
|
-
type Plugin,
|
|
42
43
|
type TurnContext as PluginTurnContext,
|
|
43
44
|
} from "../../plugins/types.js";
|
|
44
45
|
import { getLogger } from "../../util/logger.js";
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
getWorkspaceDir,
|
|
48
|
+
getWorkspacePromptPath,
|
|
49
|
+
} from "../../util/platform.js";
|
|
50
|
+
import { stripCommentLines } from "../../util/strip-comment-lines.js";
|
|
46
51
|
import { getPageIndex } from "../v2/page-index.js";
|
|
47
52
|
import { injectCapabilitiesLeaf, isCapabilitySlug } from "./capabilities.js";
|
|
48
53
|
import { loadCore } from "./core.js";
|
|
@@ -165,10 +170,42 @@ function getLanes(config: AssistantConfig): Promise<ShadowLanes> {
|
|
|
165
170
|
return lanesPromise;
|
|
166
171
|
}
|
|
167
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Read the live NOW.md scratchpad (the user's short "what's salient right now"
|
|
175
|
+
* file), stripped of its comment lines. Mirrors `readNowScratchpad` but reads
|
|
176
|
+
* through the light platform / strip utilities directly, keeping the v3
|
|
177
|
+
* plugin's load (and its test) free of heavier module graphs. Returns `null`
|
|
178
|
+
* when absent, empty, or unreadable.
|
|
179
|
+
*/
|
|
180
|
+
function readNowContext(): string | null {
|
|
181
|
+
const nowPath = getWorkspacePromptPath("NOW.md");
|
|
182
|
+
if (!existsSync(nowPath)) return null;
|
|
183
|
+
try {
|
|
184
|
+
const stripped = stripCommentLines(readFileSync(nowPath, "utf-8")).trim();
|
|
185
|
+
return stripped.length > 0 ? stripped : null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Compose the situational signal threaded into L1 routing and L2 selection: the
|
|
193
|
+
* current date plus the live NOW.md scratchpad. The date alone is a weak signal,
|
|
194
|
+
* but together with the scratchpad it lets retrieval surface a leaf the message
|
|
195
|
+
* never names (e.g. an anniversary that falls today). Always returns at least
|
|
196
|
+
* the date line — this mirrors the `c_now`/NOW.md signal the v2 retriever uses.
|
|
197
|
+
*/
|
|
198
|
+
function buildSituationalContext(): string {
|
|
199
|
+
const now = readNowContext();
|
|
200
|
+
const today = `Today is ${new Date().toDateString()}.`;
|
|
201
|
+
return now ? `${today}\n\n${now}` : today;
|
|
202
|
+
}
|
|
203
|
+
|
|
168
204
|
/**
|
|
169
205
|
* Build a v3 {@link TurnContext} from the conversation's persisted messages.
|
|
170
206
|
* `currentMessage` is the latest user message; `recentContext` is the tail of
|
|
171
|
-
* the recent transcript
|
|
207
|
+
* the recent transcript; `situationalContext` carries the current date and the
|
|
208
|
+
* live NOW.md scratchpad. Returns `null` when there is no user message to route
|
|
172
209
|
* on (nothing to shadow this turn).
|
|
173
210
|
*/
|
|
174
211
|
function buildShadowTurn(
|
|
@@ -198,6 +235,7 @@ function buildShadowTurn(
|
|
|
198
235
|
turnNumber: turnIndex,
|
|
199
236
|
currentMessage,
|
|
200
237
|
recentContext,
|
|
238
|
+
situationalContext: buildSituationalContext(),
|
|
201
239
|
};
|
|
202
240
|
}
|
|
203
241
|
|
|
@@ -280,13 +318,15 @@ async function observeTurn(
|
|
|
280
318
|
const turn = buildShadowTurn(conversationId, turnIndex);
|
|
281
319
|
if (!turn) return null;
|
|
282
320
|
|
|
283
|
-
const
|
|
321
|
+
const cfg = getConfig();
|
|
322
|
+
const lanes = await getLanes(cfg);
|
|
284
323
|
const result = await orchestrate(turn, {
|
|
285
324
|
tree: lanes.tree,
|
|
286
325
|
core: lanes.core,
|
|
287
326
|
needle: lanes.needle,
|
|
288
327
|
workingSet: lanes.workingSet,
|
|
289
328
|
pageSummary,
|
|
329
|
+
l2Concurrency: cfg.memory.v3.l2Concurrency,
|
|
290
330
|
});
|
|
291
331
|
|
|
292
332
|
const rows = attributeSelections(lanes.tree, lanes.core, result);
|
|
@@ -329,7 +369,7 @@ export async function runShadowObservation(
|
|
|
329
369
|
* (failure or empty selection) falls back to v2 memory rather than dropping all
|
|
330
370
|
* memory.
|
|
331
371
|
*/
|
|
332
|
-
const memoryV3Injector: Injector = {
|
|
372
|
+
export const memoryV3Injector: Injector = {
|
|
333
373
|
name: "memory-v3-shadow",
|
|
334
374
|
// High order so it sorts last; the live `<memory>` block uses the
|
|
335
375
|
// after-memory-prefix placement so it lands at the memory boundary regardless
|
|
@@ -369,11 +409,3 @@ const memoryV3Injector: Injector = {
|
|
|
369
409
|
}
|
|
370
410
|
},
|
|
371
411
|
};
|
|
372
|
-
|
|
373
|
-
export const memoryV3ShadowPlugin: Plugin = {
|
|
374
|
-
manifest: {
|
|
375
|
-
name: "memory-v3-shadow",
|
|
376
|
-
version: "0.0.1",
|
|
377
|
-
},
|
|
378
|
-
injectors: [memoryV3Injector],
|
|
379
|
-
};
|
package/src/memory/v3/types.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface TurnContext {
|
|
|
46
46
|
turnNumber: number;
|
|
47
47
|
currentMessage: string;
|
|
48
48
|
recentContext: string;
|
|
49
|
+
/**
|
|
50
|
+
* Optional situational signal — the current date plus the live NOW.md
|
|
51
|
+
* scratchpad — so a leaf or page can be routed/selected on a date or
|
|
52
|
+
* live-state cue the message itself never names (e.g. a person whose
|
|
53
|
+
* anniversary is today). Omitted when unavailable; the router and selector
|
|
54
|
+
* render nothing for an undefined value.
|
|
55
|
+
*/
|
|
56
|
+
situationalContext?: string;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export type SelectionSource = "l1+l2" | "core+l2" | "needle" | "carry-forward";
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
import type { ConversationStrategy } from "../channels/config.js";
|
|
22
22
|
import { getConversationStrategy } from "../channels/config.js";
|
|
23
23
|
import type { ChannelId } from "../channels/types.js";
|
|
24
|
-
import { isHomePageEnabled } from "../home/feature-gate.js";
|
|
25
24
|
import {
|
|
26
25
|
addMessage,
|
|
27
26
|
createConversation,
|
|
@@ -120,20 +119,14 @@ export async function pairDeliveryWithConversation(
|
|
|
120
119
|
// Passive vellum notifications surface via the home feed alone and link
|
|
121
120
|
// back to the originating conversation via `signal.sourceContextId`.
|
|
122
121
|
// Materializing a fresh per-notification conversation just to host the
|
|
123
|
-
// seed message leaves a graveyard entry in the sidebar; skip it
|
|
124
|
-
// the producer
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
strategy === "start_new_conversation" &&
|
|
133
|
-
!signal.requiresConversation &&
|
|
134
|
-
conversationAction?.action !== "reuse_existing" &&
|
|
135
|
-
isHomePageEnabled()
|
|
136
|
-
) {
|
|
122
|
+
// seed message leaves a graveyard entry in the sidebar; skip it
|
|
123
|
+
// unconditionally when the producer did not opt in via
|
|
124
|
+
// `requiresConversation`. The decision engine's `reuse_existing` hint is
|
|
125
|
+
// also ignored here: even a successful reuse would append a seed message
|
|
126
|
+
// to a conversation that the user didn't ask for, and a failed reuse
|
|
127
|
+
// (stale target / source mismatch) falls through to `createConversation`
|
|
128
|
+
// — producing exactly the graveyard entry we want to avoid.
|
|
129
|
+
if (strategy === "start_new_conversation" && !signal.requiresConversation) {
|
|
137
130
|
return {
|
|
138
131
|
conversationId: null,
|
|
139
132
|
messageId: null,
|
|
@@ -149,8 +149,8 @@ function buildSystemPrompt(
|
|
|
149
149
|
` - Avoid meta-send phrasing (e.g. "I'd like to send a notification", "May I go ahead with that?"). Write the recipient-facing message directly.`,
|
|
150
150
|
` - Avoid intermediary-instruction phrasing like "consider telling the guardian", "ask the recipient to", or "the assistant should remind them". Rewrite it as final copy the recipient can act on directly.`,
|
|
151
151
|
` - For telegram: 1-2 concise sentences.`,
|
|
152
|
-
`- \`conversationSeedMessage\` is the opening message in the internal notification conversation
|
|
153
|
-
` - For vellum (desktop):
|
|
152
|
+
`- \`conversationSeedMessage\` is the opening message in the internal notification conversation and also the expanded detail shown in the home feed. It should be richer and more contextual than the popup body.`,
|
|
153
|
+
` - For vellum (desktop): use structured markdown for readability. Break content into bullet points, numbered lists, or short sections with **bold** labels. Avoid long unbroken paragraphs — scan-friendly formatting is preferred.`,
|
|
154
154
|
` - Never dump raw JSON. Include only human-readable context.`,
|
|
155
155
|
``,
|
|
156
156
|
`Conversation reuse guidelines:`,
|
|
@@ -838,7 +838,10 @@ export async function evaluateSignal(
|
|
|
838
838
|
selectedChannels,
|
|
839
839
|
reasoningSummary: "assistant_tool pass-through",
|
|
840
840
|
renderedCopy: Object.fromEntries(
|
|
841
|
-
availableChannels.map((ch) => [
|
|
841
|
+
availableChannels.map((ch) => [
|
|
842
|
+
ch,
|
|
843
|
+
{ title, body, conversationSeedMessage: body },
|
|
844
|
+
]),
|
|
842
845
|
) as NotificationDecision["renderedCopy"],
|
|
843
846
|
conversationActions: Object.fromEntries(
|
|
844
847
|
availableChannels.map((ch) => [ch, { action: "start_new" as const }]),
|
|
@@ -21,6 +21,7 @@ import { appendFeedItem } from "../home/feed-writer.js";
|
|
|
21
21
|
import { getConversation } from "../memory/conversation-crud.js";
|
|
22
22
|
import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
23
23
|
import { getLogger } from "../util/logger.js";
|
|
24
|
+
import { isConversationSeedSane } from "./conversation-seed-composer.js";
|
|
24
25
|
import type { NotificationSignal } from "./signal.js";
|
|
25
26
|
import type { NotificationDecision, RenderedChannelCopy } from "./types.js";
|
|
26
27
|
|
|
@@ -77,8 +78,18 @@ export async function writeHomeFeedItemForSignal(
|
|
|
77
78
|
// against `summary` in the row. Leave undefined when absent; renderers
|
|
78
79
|
// fall back to `summary`.
|
|
79
80
|
const resolvedTitle = payloadTitle?.trim() || undefined;
|
|
81
|
+
// Prefer conversationSeedMessage over body for the home feed: the seed
|
|
82
|
+
// message is richer and may contain structured markdown (lists, headers,
|
|
83
|
+
// bold) that the detail panel renders. The popup-oriented `body` is
|
|
84
|
+
// intentionally short (≤ 2 sentences) and loses formatting.
|
|
85
|
+
const seedCandidate = renderedCopy?.conversationSeedMessage;
|
|
80
86
|
const resolvedSummary =
|
|
81
|
-
|
|
87
|
+
(isConversationSeedSane(seedCandidate)
|
|
88
|
+
? seedCandidate.trim()
|
|
89
|
+
: undefined) ||
|
|
90
|
+
renderedCopy?.body?.trim() ||
|
|
91
|
+
payloadBody?.trim() ||
|
|
92
|
+
"";
|
|
82
93
|
if (!resolvedSummary) {
|
|
83
94
|
log.warn(
|
|
84
95
|
{ signalId: signal.signalId, sourceEventName: signal.sourceEventName },
|
|
@@ -118,6 +118,10 @@ export class PermissionPrompter {
|
|
|
118
118
|
label: o.label,
|
|
119
119
|
scope: o.scope,
|
|
120
120
|
})),
|
|
121
|
+
directoryScopeOptions: directoryScopeOptions?.map((o) => ({
|
|
122
|
+
label: o.label,
|
|
123
|
+
scope: o.scope,
|
|
124
|
+
})),
|
|
121
125
|
persistentDecisionsAllowed: persistentDecisionsAllowed ?? true,
|
|
122
126
|
},
|
|
123
127
|
rpcResolve: resolve as (value: unknown) => void,
|
|
@@ -20,6 +20,10 @@ export const HOOKS = {
|
|
|
20
20
|
SHUTDOWN: "shutdown",
|
|
21
21
|
/** Fires once per user turn, immediately before the agent loop receives `runMessages`. */
|
|
22
22
|
USER_PROMPT_SUBMIT: "user-prompt-submit",
|
|
23
|
+
/** Fires once per tool result, after the tool returns and before the result is sent to the provider. */
|
|
24
|
+
POST_TOOL_USE: "post-tool-use",
|
|
25
|
+
/** Fires when the model yields a response with no tool calls — the run's stop boundary. Decides whether to stop or continue with a follow-up turn. */
|
|
26
|
+
STOP: "stop",
|
|
23
27
|
} as const;
|
|
24
28
|
|
|
25
29
|
/** Union of every hook name declared in {@link HOOKS}. */
|
package/src/plugin-api/index.ts
CHANGED
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
* - {@link PluginShutdownContext} — passed to `shutdown` hook at teardown
|
|
25
25
|
* - {@link UserPromptSubmitContext} — passed to `user-prompt-submit` hook,
|
|
26
26
|
* fired immediately before the agent loop receives a user's prompt
|
|
27
|
+
* - {@link PostToolUseContext} — passed to `post-tool-use` hook, fired once
|
|
28
|
+
* per tool result before it joins the provider-bound history
|
|
29
|
+
* - {@link StopContext} — passed to `stop` hook, fired when the model yields
|
|
30
|
+
* a response with no tool calls
|
|
27
31
|
* - {@link PluginHookFn} — signature every lifecycle hook implements
|
|
28
32
|
* - {@link PluginLogger} — pino-compatible logger shape on the contexts
|
|
29
33
|
* - {@link ToolDefinition} — author-facing tool spec (default-export shape
|
|
@@ -31,7 +35,7 @@
|
|
|
31
35
|
* - {@link ToolContext} — passed to a plugin tool's `execute` method
|
|
32
36
|
* - {@link ToolExecutionResult} — return shape of a plugin tool's `execute`
|
|
33
37
|
*
|
|
34
|
-
* Pipeline-argument types (`
|
|
38
|
+
* Pipeline-argument types (`MemoryArgs`, `CompactionArgs`, etc.) currently
|
|
35
39
|
* live in `assistant/src/plugins/types.ts` and have not yet migrated into
|
|
36
40
|
* this package. A follow-up PR will move them into this surface as the
|
|
37
41
|
* per-pipeline schemas stabilize.
|
|
@@ -44,6 +48,9 @@ export type {
|
|
|
44
48
|
PluginInitContext,
|
|
45
49
|
PluginLogger,
|
|
46
50
|
PluginShutdownContext,
|
|
51
|
+
PostToolUseContext,
|
|
52
|
+
StopContext,
|
|
53
|
+
StopDecision,
|
|
47
54
|
ToolContext,
|
|
48
55
|
ToolDefinition,
|
|
49
56
|
ToolExecutionResult,
|
package/src/plugin-api/types.ts
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* removing is breaking and gated on a major bump.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
ContentBlock,
|
|
9
|
+
Message,
|
|
10
|
+
ToolResultContent,
|
|
11
|
+
} from "../providers/types.js";
|
|
8
12
|
|
|
9
13
|
export type {
|
|
10
14
|
ToolContext,
|
|
@@ -47,6 +51,8 @@ export interface PluginLogger {
|
|
|
47
51
|
* - `init` — {@link PluginInitContext}
|
|
48
52
|
* - `shutdown` — {@link PluginShutdownContext}
|
|
49
53
|
* - `user-prompt-submit` — {@link UserPromptSubmitContext}
|
|
54
|
+
* - `post-tool-use` — {@link PostToolUseContext}
|
|
55
|
+
* - `stop` — {@link StopContext}
|
|
50
56
|
*/
|
|
51
57
|
export type PluginHookFn<TCtx = unknown> = (ctx: TCtx) => Promise<TCtx | void>;
|
|
52
58
|
|
|
@@ -121,6 +127,16 @@ export interface PluginShutdownContext {
|
|
|
121
127
|
export interface UserPromptSubmitContext {
|
|
122
128
|
/** Conversation ID the user prompt was submitted on. */
|
|
123
129
|
readonly conversationId: string;
|
|
130
|
+
/**
|
|
131
|
+
* The text of the user prompt that triggered this turn — the resolved
|
|
132
|
+
* user message (after slash-command expansion), independent of any
|
|
133
|
+
* internal rewriting applied to the message that flows into the model.
|
|
134
|
+
* Mirrors the `prompt` field Claude Code / Codex pass to their
|
|
135
|
+
* `UserPromptSubmit` hooks, so hooks that key off the submitted text
|
|
136
|
+
* (e.g. title generation) read it directly rather than reconstructing
|
|
137
|
+
* it from the message arrays.
|
|
138
|
+
*/
|
|
139
|
+
readonly prompt: string;
|
|
124
140
|
/**
|
|
125
141
|
* The user's original message list, immutable for the hook. Plugins
|
|
126
142
|
* may snapshot or compare against this but MUST NOT mutate it.
|
|
@@ -138,3 +154,137 @@ export interface UserPromptSubmitContext {
|
|
|
138
154
|
*/
|
|
139
155
|
readonly logger: PluginLogger;
|
|
140
156
|
}
|
|
157
|
+
|
|
158
|
+
// ─── Post-tool-use hook context ──────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Context passed to the `post-tool-use` hook. Fires once per tool result —
|
|
162
|
+
* after the tool returns and before the result is appended to the message
|
|
163
|
+
* history sent to the provider. With several tools dispatched in a single
|
|
164
|
+
* turn, the hook fires once per result, in tool-use order.
|
|
165
|
+
*
|
|
166
|
+
* The hook may transform the result either by mutating `toolResponse` in
|
|
167
|
+
* place (e.g. reassigning `toolResponse.content`) or by returning a new
|
|
168
|
+
* context with a fresh `toolResponse` — see {@link PluginHookFn}'s
|
|
169
|
+
* polymorphic return shape. The daemon threads the final `toolResponse`
|
|
170
|
+
* into the provider-bound history.
|
|
171
|
+
*
|
|
172
|
+
* Multiple plugins' hooks chain in registration order — each plugin's hook
|
|
173
|
+
* sees the previous plugin's mutations. The default tool-result-truncate
|
|
174
|
+
* plugin contributes a hook here that tail-drops oversized output to fit the
|
|
175
|
+
* model's context window; the default tool-error plugin sets
|
|
176
|
+
* {@link additionalContext} with retry coaching for failed results. User hooks
|
|
177
|
+
* can swap in a smarter strategy (e.g. a summarizer) or observe results for
|
|
178
|
+
* side effects.
|
|
179
|
+
*/
|
|
180
|
+
export interface PostToolUseContext {
|
|
181
|
+
/** Conversation ID the tool ran on. */
|
|
182
|
+
readonly conversationId: string;
|
|
183
|
+
/**
|
|
184
|
+
* The tool result block. Plugins may mutate its `content` in place or
|
|
185
|
+
* replace the block by returning a new context.
|
|
186
|
+
*/
|
|
187
|
+
toolResponse: ToolResultContent;
|
|
188
|
+
/**
|
|
189
|
+
* Conversation history up to and including the assistant turn that issued
|
|
190
|
+
* this tool call. The current result is not in it yet — it lives in
|
|
191
|
+
* {@link toolResponse}. A hook reasoning about prior tool outcomes (e.g.
|
|
192
|
+
* how many times a tool has failed in a row) derives that from the history
|
|
193
|
+
* content rather than a precomputed counter, so the signal survives mid-run
|
|
194
|
+
* compaction rewriting the array. Read-only: hooks transform the result via
|
|
195
|
+
* {@link toolResponse}, not by mutating history.
|
|
196
|
+
*/
|
|
197
|
+
readonly messages: ReadonlyArray<Message>;
|
|
198
|
+
/**
|
|
199
|
+
* Extra guidance for the model that is not part of the tool's output. A hook
|
|
200
|
+
* sets this to surface provider-only context — e.g. retry coaching for a
|
|
201
|
+
* failed result — and the daemon appends it to the provider-bound history as
|
|
202
|
+
* a separate block *after* emitting the tool_result, so it reaches the model
|
|
203
|
+
* without polluting the client-facing or persisted tool output. Mirrors
|
|
204
|
+
* Claude Code's PostToolUse `hookSpecificOutput.additionalContext` and the
|
|
205
|
+
* singular of Codex's `additional_contexts`. Unset means no extra context.
|
|
206
|
+
*/
|
|
207
|
+
additionalContext?: string;
|
|
208
|
+
/**
|
|
209
|
+
* The model's context-window size in tokens. Plugins derive their own
|
|
210
|
+
* character budget from this (e.g. a share of the window) rather than
|
|
211
|
+
* receiving a precomputed limit.
|
|
212
|
+
*/
|
|
213
|
+
readonly maxInputTokens: number;
|
|
214
|
+
/**
|
|
215
|
+
* Logger scoped to the current turn. The same instance is shared by
|
|
216
|
+
* every hook in the chain, so plugins should tag their structured log
|
|
217
|
+
* fields (e.g. `{ plugin: "<name>" }`) for attribution.
|
|
218
|
+
*/
|
|
219
|
+
readonly logger: PluginLogger;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Stop hook context ───────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Binary outcome of the `stop` hook. The agent loop seeds it to `"stop"`
|
|
226
|
+
* and acts on the value the chain settles on:
|
|
227
|
+
*
|
|
228
|
+
* - `"stop"` — let the turn end; the loop yields the assistant response
|
|
229
|
+
* to the user. This is the default.
|
|
230
|
+
* - `"continue"` — re-query the model. The hook is responsible for appending
|
|
231
|
+
* the follow-up turn it wants the model to see to
|
|
232
|
+
* {@link StopContext.messages} before returning.
|
|
233
|
+
*
|
|
234
|
+
* To abort with an error a hook should throw — the loop's error handler
|
|
235
|
+
* surfaces it. There is intentionally no error decision value.
|
|
236
|
+
*/
|
|
237
|
+
export type StopDecision = "continue" | "stop";
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Context passed to the `stop` hook. Fires when the model yields a response
|
|
241
|
+
* with no tool calls — the run's stop boundary, where the loop is about to
|
|
242
|
+
* hand the turn back to the user. The default empty-response plugin uses it
|
|
243
|
+
* to re-query the model when a turn came back empty or as a provider refusal.
|
|
244
|
+
*
|
|
245
|
+
* The hook decides the outcome by setting {@link decision}. When it sets
|
|
246
|
+
* `"continue"` it must also append the follow-up turn (e.g. a nudge `user`
|
|
247
|
+
* message) to {@link messages}; the loop threads those messages into the next
|
|
248
|
+
* iteration. {@link messages} is the full conversation history, carried back
|
|
249
|
+
* verbatim. A hook that needs to reason about just the current response cycle
|
|
250
|
+
* (e.g. whether an earlier turn already delivered visible text) derives that
|
|
251
|
+
* boundary from the history itself — the messages after the last genuine user
|
|
252
|
+
* prompt — rather than an index, since mid-run compaction can rewrite the
|
|
253
|
+
* array.
|
|
254
|
+
*
|
|
255
|
+
* Multiple plugins' hooks chain in registration order — each sees the
|
|
256
|
+
* previous hook's `decision` and `messages` mutations.
|
|
257
|
+
*/
|
|
258
|
+
export interface StopContext {
|
|
259
|
+
/** Conversation ID the run belongs to. */
|
|
260
|
+
readonly conversationId: string;
|
|
261
|
+
/**
|
|
262
|
+
* Full conversation history: the inbound conversation followed by every
|
|
263
|
+
* message produced this run. A hook that sets `decision` to `"continue"`
|
|
264
|
+
* appends its follow-up turn here; the loop carries the result into the
|
|
265
|
+
* next iteration.
|
|
266
|
+
*/
|
|
267
|
+
messages: Message[];
|
|
268
|
+
/**
|
|
269
|
+
* Content blocks of the assistant turn that triggered the stop. Guaranteed
|
|
270
|
+
* to contain no `tool_use` blocks — the hook only fires at the boundary
|
|
271
|
+
* where the model stopped requesting tools.
|
|
272
|
+
*/
|
|
273
|
+
readonly responseContent: ReadonlyArray<ContentBlock>;
|
|
274
|
+
/**
|
|
275
|
+
* Provider-reported stop reason for the assistant turn (e.g. `"refusal"`,
|
|
276
|
+
* `"end_turn"`). `null`/`undefined` when the provider didn't report one.
|
|
277
|
+
*/
|
|
278
|
+
readonly stopReason: string | null | undefined;
|
|
279
|
+
/**
|
|
280
|
+
* Seeded to `"stop"`. A hook sets it to `"continue"` to force another loop
|
|
281
|
+
* iteration; later hooks in the chain may override it.
|
|
282
|
+
*/
|
|
283
|
+
decision: StopDecision;
|
|
284
|
+
/**
|
|
285
|
+
* Logger scoped to the current turn. The same instance is shared by
|
|
286
|
+
* every hook in the chain, so plugins should tag their structured log
|
|
287
|
+
* fields (e.g. `{ plugin: "<name>" }`) for attribution.
|
|
288
|
+
*/
|
|
289
|
+
readonly logger: PluginLogger;
|
|
290
|
+
}
|