@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
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
createAssistantMessage,
|
|
11
11
|
createUserMessage,
|
|
12
12
|
} from "../../agent/message-types.js";
|
|
13
|
+
import { ConversationMessageSchema } from "../../api/responses/conversation-message.js";
|
|
13
14
|
import {
|
|
14
15
|
CHANNEL_IDS,
|
|
15
16
|
INTERFACE_IDS,
|
|
@@ -46,7 +47,11 @@ import {
|
|
|
46
47
|
getCannedFirstGreeting,
|
|
47
48
|
isWakeUpGreeting,
|
|
48
49
|
} from "../../daemon/first-greeting.js";
|
|
49
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
collectAttachmentRefs,
|
|
52
|
+
type HistoryAttachmentRef,
|
|
53
|
+
renderHistoryContent,
|
|
54
|
+
} from "../../daemon/handlers/shared.js";
|
|
50
55
|
import { HostAppControlProxy } from "../../daemon/host-app-control-proxy.js";
|
|
51
56
|
import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
|
|
52
57
|
import {
|
|
@@ -105,10 +110,14 @@ import type { Provider } from "../../providers/types.js";
|
|
|
105
110
|
import { checkIngressForSecrets } from "../../security/secret-ingress.js";
|
|
106
111
|
import { getSubagentManager } from "../../subagent/index.js";
|
|
107
112
|
import { getLogger } from "../../util/logger.js";
|
|
108
|
-
import {
|
|
113
|
+
import {
|
|
114
|
+
getWorkspaceDir,
|
|
115
|
+
getWorkspacePromptPath,
|
|
116
|
+
} from "../../util/platform.js";
|
|
109
117
|
import { silentlyWithLog } from "../../util/silently.js";
|
|
110
118
|
import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
|
|
111
119
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
120
|
+
import { getPersistedSeq } from "../assistant-stream-state.js";
|
|
112
121
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
113
122
|
import { routeGuardianReply } from "../guardian-reply-router.js";
|
|
114
123
|
import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
|
|
@@ -134,6 +143,10 @@ import {
|
|
|
134
143
|
NotFoundError,
|
|
135
144
|
RouteError,
|
|
136
145
|
} from "./errors.js";
|
|
146
|
+
import {
|
|
147
|
+
collectPendingConfirmations,
|
|
148
|
+
enrichToolCallsWithConfirmation,
|
|
149
|
+
} from "./tool-call-confirmation-enrichment.js";
|
|
137
150
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
138
151
|
import { RouteResponse } from "./types.js";
|
|
139
152
|
|
|
@@ -143,6 +156,125 @@ const log = getLogger("conversation-routes");
|
|
|
143
156
|
const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/g;
|
|
144
157
|
const ATTACHMENT_ENTRY_RE = /^attachment:(\d+)$/;
|
|
145
158
|
|
|
159
|
+
/** Rewrites a rendered `contentOrder` to reflect attachment alignment. */
|
|
160
|
+
type ContentOrderRewrite = (contentOrder: string[]) => string[];
|
|
161
|
+
|
|
162
|
+
interface AlignedAttachments {
|
|
163
|
+
/** Hydrated rows, reordered to match the inline file-block order. */
|
|
164
|
+
attachments: RuntimeAttachmentMetadata[];
|
|
165
|
+
/**
|
|
166
|
+
* Resolves a content-walk attachment ref index to its hydrated DB row,
|
|
167
|
+
* mirroring the inline placement `rewriteContentOrder` encodes. Refs with no
|
|
168
|
+
* inline placement (unmatched ids, count mismatch, no DB rows) are absent, so
|
|
169
|
+
* `renderHistoryContent` emits no `attachment` block for them — the row still
|
|
170
|
+
* ships via the flat `attachments` array.
|
|
171
|
+
*/
|
|
172
|
+
refIndexToAttachment: Map<number, RuntimeAttachmentMetadata>;
|
|
173
|
+
rewriteContentOrder: ContentOrderRewrite;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Align DB-hydrated attachment rows with the file-block refs `renderHistoryContent`
|
|
178
|
+
* captured. When a file block was persisted with `_attachmentId` (user-message
|
|
179
|
+
* uploads) we join on that id to position the chip inline; DB rows without a
|
|
180
|
+
* matching ref go to the tail as orphan chips, and unmatched refs drop their
|
|
181
|
+
* `attachment:N` entry. Assistant-authored file blocks carry no `_attachmentId`,
|
|
182
|
+
* so when no ids match we fall back to positional alignment if the ref and row
|
|
183
|
+
* counts agree; otherwise we strip the markers and let chips fall to the tail.
|
|
184
|
+
*/
|
|
185
|
+
function alignAttachments(
|
|
186
|
+
attachmentRefs: HistoryAttachmentRef[],
|
|
187
|
+
attachments: RuntimeAttachmentMetadata[],
|
|
188
|
+
): AlignedAttachments {
|
|
189
|
+
const refIndexToAttachment = new Map<number, RuntimeAttachmentMetadata>();
|
|
190
|
+
const identity: ContentOrderRewrite = (contentOrder) => contentOrder;
|
|
191
|
+
const stripAttachmentEntries: ContentOrderRewrite = (contentOrder) =>
|
|
192
|
+
contentOrder.filter((entry) => !ATTACHMENT_ENTRY_RE.test(entry));
|
|
193
|
+
|
|
194
|
+
if (attachmentRefs.length === 0) {
|
|
195
|
+
return { attachments, refIndexToAttachment, rewriteContentOrder: identity };
|
|
196
|
+
}
|
|
197
|
+
if (attachments.length === 0) {
|
|
198
|
+
// Refs were captured but no DB rows came back — drop the contentOrder
|
|
199
|
+
// entries to avoid out-of-bounds renders.
|
|
200
|
+
return {
|
|
201
|
+
attachments,
|
|
202
|
+
refIndexToAttachment,
|
|
203
|
+
rewriteContentOrder: stripAttachmentEntries,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const byId = new Map<string, number>();
|
|
208
|
+
attachments.forEach((att, idx) => {
|
|
209
|
+
if (att.id) byId.set(att.id, idx);
|
|
210
|
+
});
|
|
211
|
+
const consumed = new Set<number>();
|
|
212
|
+
const orderedRowIdx: Array<number | null> = attachmentRefs.map((ref) => {
|
|
213
|
+
if (!ref.attachmentId) return null;
|
|
214
|
+
const idx = byId.get(ref.attachmentId);
|
|
215
|
+
if (idx === undefined || consumed.has(idx)) return null;
|
|
216
|
+
consumed.add(idx);
|
|
217
|
+
return idx;
|
|
218
|
+
});
|
|
219
|
+
const matchedRows = orderedRowIdx.filter(
|
|
220
|
+
(idx): idx is number => idx !== null,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (matchedRows.length > 0) {
|
|
224
|
+
const orphanRows: number[] = [];
|
|
225
|
+
for (let i = 0; i < attachments.length; i++) {
|
|
226
|
+
if (!consumed.has(i)) orphanRows.push(i);
|
|
227
|
+
}
|
|
228
|
+
const reordered = [
|
|
229
|
+
...matchedRows.map((i) => attachments[i]),
|
|
230
|
+
...orphanRows.map((i) => attachments[i]),
|
|
231
|
+
];
|
|
232
|
+
const refToNewIdx = new Map<number, number>();
|
|
233
|
+
let nextIdx = 0;
|
|
234
|
+
orderedRowIdx.forEach((rowIdx, refIdx) => {
|
|
235
|
+
if (rowIdx !== null) {
|
|
236
|
+
refToNewIdx.set(refIdx, nextIdx);
|
|
237
|
+
refIndexToAttachment.set(refIdx, reordered[nextIdx]);
|
|
238
|
+
nextIdx++;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
const rewriteContentOrder: ContentOrderRewrite = (contentOrder) =>
|
|
242
|
+
contentOrder
|
|
243
|
+
.map((entry) => {
|
|
244
|
+
const match = entry.match(ATTACHMENT_ENTRY_RE);
|
|
245
|
+
if (!match) return entry;
|
|
246
|
+
const remapped = refToNewIdx.get(Number(match[1]));
|
|
247
|
+
return remapped !== undefined ? `attachment:${remapped}` : undefined;
|
|
248
|
+
})
|
|
249
|
+
.filter((e): e is string => e !== undefined);
|
|
250
|
+
return {
|
|
251
|
+
attachments: reordered,
|
|
252
|
+
refIndexToAttachment,
|
|
253
|
+
rewriteContentOrder,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (attachmentRefs.length !== attachments.length) {
|
|
258
|
+
// No ref carried an attachmentId we could match and the counts disagree, so
|
|
259
|
+
// positional mapping can't be trusted — strip any attachment:N entries so
|
|
260
|
+
// the client doesn't position attachments inline against a misaligned array
|
|
261
|
+
// (they fall to the tail instead).
|
|
262
|
+
return {
|
|
263
|
+
attachments,
|
|
264
|
+
refIndexToAttachment,
|
|
265
|
+
rewriteContentOrder: stripAttachmentEntries,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// No ref matched an id but the counts agree (the assistant-authored case):
|
|
270
|
+
// the Nth marker maps to the Nth row positionally, so the original
|
|
271
|
+
// contentOrder is left untouched.
|
|
272
|
+
attachmentRefs.forEach((_ref, refIdx) => {
|
|
273
|
+
refIndexToAttachment.set(refIdx, attachments[refIdx]);
|
|
274
|
+
});
|
|
275
|
+
return { attachments, refIndexToAttachment, rewriteContentOrder: identity };
|
|
276
|
+
}
|
|
277
|
+
|
|
146
278
|
/** Feature flag gating the self-intro first message (see first-greeting.ts). */
|
|
147
279
|
const SELF_INTRO_GREETING_FLAG = "self-intro-greeting" as const;
|
|
148
280
|
|
|
@@ -509,6 +641,7 @@ export function handleListMessages({
|
|
|
509
641
|
hasMore: false,
|
|
510
642
|
oldestTimestamp: null,
|
|
511
643
|
oldestMessageId: null,
|
|
644
|
+
seq: null,
|
|
512
645
|
};
|
|
513
646
|
}
|
|
514
647
|
return { messages: [] };
|
|
@@ -581,7 +714,10 @@ export function handleListMessages({
|
|
|
581
714
|
mergeConsecutiveAssistantMessages(mergedMessages);
|
|
582
715
|
const assistantSlackDisplayName = getAssistantName()?.trim() || undefined;
|
|
583
716
|
|
|
584
|
-
// Parse content
|
|
717
|
+
// Parse each row's stored content and per-message metadata. Rendering is
|
|
718
|
+
// deferred to the serializer pass below so it runs after attachment
|
|
719
|
+
// alignment, letting renderHistoryContent inline `attachment` blocks during
|
|
720
|
+
// its single content walk.
|
|
585
721
|
const parsed = consolidatedMessages.map((msg) => {
|
|
586
722
|
let content: unknown;
|
|
587
723
|
try {
|
|
@@ -589,7 +725,6 @@ export function handleListMessages({
|
|
|
589
725
|
} catch {
|
|
590
726
|
content = msg.content;
|
|
591
727
|
}
|
|
592
|
-
const rendered = renderHistoryContent(content);
|
|
593
728
|
|
|
594
729
|
// Extract sentAt from metadata for display timestamps. When a message
|
|
595
730
|
// was queued or its persistence was delayed (long assistant generation),
|
|
@@ -637,95 +772,45 @@ export function handleListMessages({
|
|
|
637
772
|
},
|
|
638
773
|
);
|
|
639
774
|
|
|
640
|
-
// Strip <no_response/> markers from assistant messages so web/API
|
|
641
|
-
// clients never see the raw sentinel. Only assistant messages produce
|
|
642
|
-
// this marker; user messages are left untouched.
|
|
643
|
-
if (msg.role === "assistant") {
|
|
644
|
-
const originalSegments = rendered.textSegments;
|
|
645
|
-
const keepIndices: number[] = [];
|
|
646
|
-
const filteredSegments: string[] = [];
|
|
647
|
-
for (let i = 0; i < originalSegments.length; i++) {
|
|
648
|
-
const cleaned = originalSegments[i]
|
|
649
|
-
.replace(NO_RESPONSE_INLINE_RE, "")
|
|
650
|
-
.trim();
|
|
651
|
-
if (cleaned.length > 0) {
|
|
652
|
-
keepIndices.push(i);
|
|
653
|
-
filteredSegments.push(cleaned);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
// Remap contentOrder text:N indices to account for removed segments
|
|
657
|
-
const indexMap = new Map<number, number>();
|
|
658
|
-
keepIndices.forEach((oldIdx, newIdx) => indexMap.set(oldIdx, newIdx));
|
|
659
|
-
const filteredContentOrder = rendered.contentOrder
|
|
660
|
-
.map((entry) => {
|
|
661
|
-
const m = entry.match(/^text:(\d+)$/);
|
|
662
|
-
if (!m) return entry;
|
|
663
|
-
const newIdx = indexMap.get(Number(m[1]));
|
|
664
|
-
return newIdx !== undefined ? `text:${newIdx}` : undefined;
|
|
665
|
-
})
|
|
666
|
-
.filter((e): e is string => e !== undefined);
|
|
667
|
-
|
|
668
|
-
return {
|
|
669
|
-
role: msg.role,
|
|
670
|
-
text: rendered.text.replace(NO_RESPONSE_INLINE_RE, "").trim(),
|
|
671
|
-
timestamp: msg.createdAt,
|
|
672
|
-
sentAt,
|
|
673
|
-
toolCalls: rendered.toolCalls,
|
|
674
|
-
toolCallsBeforeText: rendered.toolCallsBeforeText,
|
|
675
|
-
textSegments: filteredSegments,
|
|
676
|
-
contentOrder: filteredContentOrder,
|
|
677
|
-
surfaces: rendered.surfaces,
|
|
678
|
-
attachmentRefs: rendered.attachments,
|
|
679
|
-
slackMessage,
|
|
680
|
-
...(rendered.thinkingSegments.length > 0
|
|
681
|
-
? { thinkingSegments: rendered.thinkingSegments }
|
|
682
|
-
: {}),
|
|
683
|
-
id: msg.id,
|
|
684
|
-
subagentNotification,
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
775
|
return {
|
|
776
|
+
id: msg.id,
|
|
689
777
|
role: msg.role,
|
|
690
|
-
|
|
691
|
-
|
|
778
|
+
content,
|
|
779
|
+
createdAt: msg.createdAt,
|
|
692
780
|
sentAt,
|
|
693
|
-
toolCalls: rendered.toolCalls,
|
|
694
|
-
toolCallsBeforeText: rendered.toolCallsBeforeText,
|
|
695
|
-
textSegments: rendered.textSegments,
|
|
696
|
-
contentOrder: rendered.contentOrder,
|
|
697
|
-
surfaces: rendered.surfaces,
|
|
698
|
-
attachmentRefs: rendered.attachments,
|
|
699
|
-
slackMessage,
|
|
700
|
-
...(rendered.thinkingSegments.length > 0
|
|
701
|
-
? { thinkingSegments: rendered.thinkingSegments }
|
|
702
|
-
: {}),
|
|
703
|
-
id: msg.id,
|
|
704
781
|
subagentNotification,
|
|
782
|
+
slackMessage,
|
|
705
783
|
};
|
|
706
784
|
});
|
|
707
785
|
|
|
786
|
+
// Confirmation context layered onto rendered tool calls at render time: the
|
|
787
|
+
// derived scope ladder for scope-aware tools, and any in-flight prompt read
|
|
788
|
+
// from the pending-interactions registry. Both are computed once per request
|
|
789
|
+
// and applied per message below.
|
|
790
|
+
const workspaceDir = getWorkspaceDir();
|
|
791
|
+
const pendingConfirmations = collectPendingConfirmations(
|
|
792
|
+
resolvedConversationId,
|
|
793
|
+
);
|
|
794
|
+
|
|
708
795
|
const messages: RuntimeMessagePayload[] = parsed.map((m) => {
|
|
709
796
|
const mergedMessageIds = m.id ? (mergedIdMap.get(m.id) ?? []) : [];
|
|
797
|
+
|
|
798
|
+
// Hydrate the row's attachments from the DB. A metadata-only query avoids
|
|
799
|
+
// loading large base64 blobs for non-image attachments (documents, audio);
|
|
800
|
+
// full data is fetched only for images so the client can generate
|
|
801
|
+
// thumbnails for inline display on history restore. Merged messages
|
|
802
|
+
// (consecutive assistant merge) are queried too so their attachments
|
|
803
|
+
// aren't lost before DB compaction relinks them.
|
|
710
804
|
let msgAttachments: RuntimeAttachmentMetadata[] = [];
|
|
711
805
|
if (m.id) {
|
|
712
|
-
|
|
713
|
-
// blobs for non-image attachments (documents, audio). Then
|
|
714
|
-
// selectively fetch full data only for images so the client can
|
|
715
|
-
// generate thumbnails for inline display on history restore.
|
|
716
|
-
// Also query attachments for any messages that were merged into
|
|
717
|
-
// this one (consecutive assistant merge), so their attachments
|
|
718
|
-
// aren't lost before DB compaction relinks them.
|
|
719
|
-
const idsToQuery = [m.id, ...(mergedIdMap.get(m.id) ?? [])];
|
|
806
|
+
const idsToQuery = [m.id, ...mergedMessageIds];
|
|
720
807
|
const linked = idsToQuery.flatMap((id) =>
|
|
721
808
|
getAttachmentMetadataForMessage(id),
|
|
722
809
|
);
|
|
723
810
|
if (linked.length > 0) {
|
|
724
811
|
msgAttachments = linked.map((a) => {
|
|
725
812
|
if (a.mimeType.startsWith("image/")) {
|
|
726
|
-
const full = getAttachmentById(a.id, {
|
|
727
|
-
hydrateFileData: true,
|
|
728
|
-
});
|
|
813
|
+
const full = getAttachmentById(a.id, { hydrateFileData: true });
|
|
729
814
|
return {
|
|
730
815
|
id: a.id,
|
|
731
816
|
filename: a.originalFilename,
|
|
@@ -752,107 +837,101 @@ export function handleListMessages({
|
|
|
752
837
|
}
|
|
753
838
|
}
|
|
754
839
|
|
|
755
|
-
// Align
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
840
|
+
// Align the hydrated rows with the file-block refs, then render. Rendering
|
|
841
|
+
// after alignment lets renderHistoryContent inline each `attachment` block
|
|
842
|
+
// during its single content walk, so `contentBlocks` comes back ready to
|
|
843
|
+
// ship with no post-processing. The aligned reorder/rewrite keeps the
|
|
844
|
+
// legacy `attachments` array and `contentOrder` positions consistent.
|
|
845
|
+
const attachmentRefs = collectAttachmentRefs(m.content);
|
|
846
|
+
const aligned = alignAttachments(attachmentRefs, msgAttachments);
|
|
847
|
+
msgAttachments = aligned.attachments;
|
|
848
|
+
const attachmentBlocks = attachmentRefs.map(
|
|
849
|
+
(_ref, refIdx) => aligned.refIndexToAttachment.get(refIdx) ?? null,
|
|
850
|
+
);
|
|
851
|
+
const rendered = renderHistoryContent(
|
|
852
|
+
m.content,
|
|
853
|
+
attachmentBlocks,
|
|
854
|
+
m.id ?? undefined,
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
const toolCalls = enrichToolCallsWithConfirmation(rendered.toolCalls, {
|
|
858
|
+
workspaceDir,
|
|
859
|
+
pendingConfirmations,
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Strip <no_response/> markers from assistant messages so web/API clients
|
|
863
|
+
// never see the raw sentinel. Only assistant messages produce it; user
|
|
864
|
+
// messages are untouched. The filter is applied consistently to the flat
|
|
865
|
+
// text, the segments, the contentOrder text refs, and the text blocks of
|
|
866
|
+
// contentBlocks.
|
|
867
|
+
let text = rendered.text;
|
|
868
|
+
let textSegments = rendered.textSegments;
|
|
869
|
+
let contentOrder = rendered.contentOrder;
|
|
870
|
+
let contentBlocks = rendered.contentBlocks;
|
|
871
|
+
if (m.role === "assistant") {
|
|
872
|
+
const keepIndices: number[] = [];
|
|
873
|
+
const filteredSegments: string[] = [];
|
|
874
|
+
for (let i = 0; i < rendered.textSegments.length; i++) {
|
|
875
|
+
const cleaned = rendered.textSegments[i]
|
|
876
|
+
.replace(NO_RESPONSE_INLINE_RE, "")
|
|
877
|
+
.trim();
|
|
878
|
+
if (cleaned.length > 0) {
|
|
879
|
+
keepIndices.push(i);
|
|
880
|
+
filteredSegments.push(cleaned);
|
|
791
881
|
}
|
|
792
|
-
msgAttachments = [
|
|
793
|
-
...matchedRows.map((i) => msgAttachments[i]),
|
|
794
|
-
...orphanRows.map((i) => msgAttachments[i]),
|
|
795
|
-
];
|
|
796
|
-
const refToNewIdx = new Map<number, number>();
|
|
797
|
-
let nextIdx = 0;
|
|
798
|
-
orderedRowIdx.forEach((rowIdx, refIdx) => {
|
|
799
|
-
if (rowIdx !== null) {
|
|
800
|
-
refToNewIdx.set(refIdx, nextIdx);
|
|
801
|
-
nextIdx++;
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
alignedContentOrder = m.contentOrder
|
|
805
|
-
.map((entry) => {
|
|
806
|
-
const match = entry.match(ATTACHMENT_ENTRY_RE);
|
|
807
|
-
if (!match) return entry;
|
|
808
|
-
const remapped = refToNewIdx.get(Number(match[1]));
|
|
809
|
-
return remapped !== undefined
|
|
810
|
-
? `attachment:${remapped}`
|
|
811
|
-
: undefined;
|
|
812
|
-
})
|
|
813
|
-
.filter((e): e is string => e !== undefined);
|
|
814
|
-
} else if (m.attachmentRefs.length !== msgAttachments.length) {
|
|
815
|
-
// No ref carried an attachmentId we could match and the counts
|
|
816
|
-
// disagree, so positional mapping can't be trusted — strip any
|
|
817
|
-
// attachment:N entries so the client doesn't position attachments
|
|
818
|
-
// inline against a misaligned array (they fall to the tail instead).
|
|
819
|
-
alignedContentOrder = m.contentOrder.filter(
|
|
820
|
-
(entry) => !ATTACHMENT_ENTRY_RE.test(entry),
|
|
821
|
-
);
|
|
822
882
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
883
|
+
const indexMap = new Map<number, number>();
|
|
884
|
+
keepIndices.forEach((oldIdx, newIdx) => indexMap.set(oldIdx, newIdx));
|
|
885
|
+
contentOrder = rendered.contentOrder
|
|
886
|
+
.map((entry) => {
|
|
887
|
+
const tm = entry.match(/^text:(\d+)$/);
|
|
888
|
+
if (!tm) return entry;
|
|
889
|
+
const newIdx = indexMap.get(Number(tm[1]));
|
|
890
|
+
return newIdx !== undefined ? `text:${newIdx}` : undefined;
|
|
891
|
+
})
|
|
892
|
+
.filter((e): e is string => e !== undefined);
|
|
893
|
+
textSegments = filteredSegments;
|
|
894
|
+
text = rendered.text.replace(NO_RESPONSE_INLINE_RE, "").trim();
|
|
895
|
+
contentBlocks = rendered.contentBlocks
|
|
896
|
+
.map((block) =>
|
|
897
|
+
block.type === "text"
|
|
898
|
+
? {
|
|
899
|
+
type: "text" as const,
|
|
900
|
+
text: block.text.replace(NO_RESPONSE_INLINE_RE, "").trim(),
|
|
901
|
+
}
|
|
902
|
+
: block,
|
|
903
|
+
)
|
|
904
|
+
.filter((block) => block.type !== "text" || block.text.length > 0);
|
|
832
905
|
}
|
|
833
906
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
907
|
+
const alignedContentOrder = aligned.rewriteContentOrder(contentOrder);
|
|
908
|
+
|
|
909
|
+
// Use sentAt (actual event time) for the display timestamp when available,
|
|
910
|
+
// falling back to createdAt (persistence time). Clients use this display
|
|
911
|
+
// timestamp as their pagination cursor after memory-pressure trimming,
|
|
912
|
+
// while server-side pagination filters on createdAt. The mismatch is
|
|
913
|
+
// benign — it may return slightly extra data on a page boundary but never
|
|
914
|
+
// loses messages.
|
|
915
|
+
const displayTimestamp = m.sentAt ?? m.createdAt;
|
|
841
916
|
return {
|
|
842
917
|
id: m.id ?? "",
|
|
843
918
|
...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
|
|
844
919
|
role: m.role,
|
|
920
|
+
// Flat plain-text body the legacy Swift client reads directly; see the
|
|
921
|
+
// `content` field on ConversationMessageSchema for why this must stay.
|
|
922
|
+
content: text,
|
|
845
923
|
timestamp: new Date(displayTimestamp).toISOString(),
|
|
846
924
|
attachments: msgAttachments,
|
|
847
|
-
...(
|
|
848
|
-
...(
|
|
849
|
-
...(
|
|
850
|
-
...(
|
|
851
|
-
? { thinkingSegments:
|
|
925
|
+
...(toolCalls.length > 0 ? { toolCalls } : {}),
|
|
926
|
+
...(rendered.surfaces.length > 0 ? { surfaces: rendered.surfaces } : {}),
|
|
927
|
+
...(textSegments.length > 0 ? { textSegments } : {}),
|
|
928
|
+
...(rendered.thinkingSegments.length > 0
|
|
929
|
+
? { thinkingSegments: rendered.thinkingSegments }
|
|
852
930
|
: {}),
|
|
853
931
|
...(alignedContentOrder.length > 0
|
|
854
932
|
? { contentOrder: alignedContentOrder }
|
|
855
933
|
: {}),
|
|
934
|
+
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
856
935
|
...(m.subagentNotification
|
|
857
936
|
? { subagentNotification: m.subagentNotification }
|
|
858
937
|
: {}),
|
|
@@ -860,6 +939,13 @@ export function handleListMessages({
|
|
|
860
939
|
};
|
|
861
940
|
});
|
|
862
941
|
|
|
942
|
+
// Snapshot↔stream alignment token: the `seq` of the last event whose
|
|
943
|
+
// content is durably persisted for this conversation in the current
|
|
944
|
+
// daemon process. Returned on every resolved-conversation response so a
|
|
945
|
+
// client can apply only stream events with a higher `seq`. Null when
|
|
946
|
+
// nothing has been persisted in-process (cold/aged-out/post-restart).
|
|
947
|
+
const persistedSeq = getPersistedSeq(resolvedConversationId);
|
|
948
|
+
|
|
863
949
|
if (isPaginated) {
|
|
864
950
|
// Prefer the page's oldest visible row (the documented cursor semantic).
|
|
865
951
|
// When a scan-cap-truncated page comes back empty there's no visible row
|
|
@@ -881,6 +967,7 @@ export function handleListMessages({
|
|
|
881
967
|
hasMore,
|
|
882
968
|
oldestTimestamp: oldestTimestamp ?? null,
|
|
883
969
|
oldestMessageId: oldestMessageId ?? null,
|
|
970
|
+
seq: persistedSeq,
|
|
884
971
|
};
|
|
885
972
|
}
|
|
886
973
|
|
|
@@ -889,10 +976,11 @@ export function handleListMessages({
|
|
|
889
976
|
hasMore,
|
|
890
977
|
...(oldestTimestamp != null ? { oldestTimestamp } : {}),
|
|
891
978
|
...(oldestMessageId != null ? { oldestMessageId } : {}),
|
|
979
|
+
seq: persistedSeq,
|
|
892
980
|
};
|
|
893
981
|
}
|
|
894
982
|
|
|
895
|
-
return { messages };
|
|
983
|
+
return { messages, seq: persistedSeq };
|
|
896
984
|
}
|
|
897
985
|
|
|
898
986
|
/**
|
|
@@ -1446,7 +1534,7 @@ export async function handleSendMessage(
|
|
|
1446
1534
|
} else if (isWakeUp) {
|
|
1447
1535
|
const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
|
|
1448
1536
|
|
|
1449
|
-
conversation.
|
|
1537
|
+
conversation.setProcessing(true);
|
|
1450
1538
|
let cleanupDeferred = false;
|
|
1451
1539
|
try {
|
|
1452
1540
|
const rawContent = content ?? "";
|
|
@@ -1522,7 +1610,7 @@ export async function handleSendMessage(
|
|
|
1522
1610
|
persistedAssistant.id,
|
|
1523
1611
|
);
|
|
1524
1612
|
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1525
|
-
conversation.
|
|
1613
|
+
conversation.setProcessing(false);
|
|
1526
1614
|
silentlyWithLog(
|
|
1527
1615
|
conversation.drainQueue(),
|
|
1528
1616
|
"canned-greeting queue drain",
|
|
@@ -1538,8 +1626,8 @@ export async function handleSendMessage(
|
|
|
1538
1626
|
cleanupDeferred = true;
|
|
1539
1627
|
return response;
|
|
1540
1628
|
} finally {
|
|
1541
|
-
if (!cleanupDeferred && conversation.
|
|
1542
|
-
conversation.
|
|
1629
|
+
if (!cleanupDeferred && conversation.isProcessing()) {
|
|
1630
|
+
conversation.setProcessing(false);
|
|
1543
1631
|
silentlyWithLog(conversation.drainQueue(), "error-path queue drain");
|
|
1544
1632
|
}
|
|
1545
1633
|
}
|
|
@@ -1752,7 +1840,7 @@ export async function handleSendMessage(
|
|
|
1752
1840
|
const slashResult = await resolveSlash(rawContent, slashContext);
|
|
1753
1841
|
|
|
1754
1842
|
if (slashResult.kind === "unknown") {
|
|
1755
|
-
conversation.
|
|
1843
|
+
conversation.setProcessing(true);
|
|
1756
1844
|
let cleanupDeferred = false;
|
|
1757
1845
|
try {
|
|
1758
1846
|
const slashMeta = {
|
|
@@ -1833,7 +1921,7 @@ export async function handleSendMessage(
|
|
|
1833
1921
|
persistedAssistant.id,
|
|
1834
1922
|
);
|
|
1835
1923
|
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1836
|
-
conversation.
|
|
1924
|
+
conversation.setProcessing(false);
|
|
1837
1925
|
silentlyWithLog(conversation.drainQueue(), "slash-command queue drain");
|
|
1838
1926
|
}, 0);
|
|
1839
1927
|
|
|
@@ -1842,15 +1930,15 @@ export async function handleSendMessage(
|
|
|
1842
1930
|
} finally {
|
|
1843
1931
|
// No-op for the slash-command early-return path (handled inside
|
|
1844
1932
|
// setTimeout above), but still needed for error paths.
|
|
1845
|
-
if (!cleanupDeferred && conversation.
|
|
1846
|
-
conversation.
|
|
1933
|
+
if (!cleanupDeferred && conversation.isProcessing()) {
|
|
1934
|
+
conversation.setProcessing(false);
|
|
1847
1935
|
silentlyWithLog(conversation.drainQueue(), "error-path queue drain");
|
|
1848
1936
|
}
|
|
1849
1937
|
}
|
|
1850
1938
|
}
|
|
1851
1939
|
|
|
1852
1940
|
if (slashResult.kind === "compact") {
|
|
1853
|
-
conversation.
|
|
1941
|
+
conversation.setProcessing(true);
|
|
1854
1942
|
const slashMeta = {
|
|
1855
1943
|
userMessageChannel: sourceChannel,
|
|
1856
1944
|
assistantMessageChannel: sourceChannel,
|
|
@@ -1870,12 +1958,12 @@ export async function handleSendMessage(
|
|
|
1870
1958
|
// The fire-and-forget compaction below owns clearing `processing`, but a
|
|
1871
1959
|
// throw from this initial persist never reaches it — reset here so the
|
|
1872
1960
|
// conversation isn't stranded in queued mode.
|
|
1873
|
-
conversation.
|
|
1961
|
+
conversation.setProcessing(false);
|
|
1874
1962
|
silentlyWithLog(conversation.drainQueue(), "compact-command queue drain");
|
|
1875
1963
|
throw err;
|
|
1876
1964
|
}
|
|
1877
1965
|
if (persisted.deduplicated) {
|
|
1878
|
-
conversation.
|
|
1966
|
+
conversation.setProcessing(false);
|
|
1879
1967
|
silentlyWithLog(conversation.drainQueue(), "compact-dedup queue drain");
|
|
1880
1968
|
return {
|
|
1881
1969
|
accepted: true,
|
|
@@ -1904,9 +1992,7 @@ export async function handleSendMessage(
|
|
|
1904
1992
|
});
|
|
1905
1993
|
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1906
1994
|
conversation.emitActivityState("thinking", "context_compacting");
|
|
1907
|
-
const result = await conversation.forceCompact(
|
|
1908
|
-
targetInputTokensOverride: slashResult.targetInputTokensOverride,
|
|
1909
|
-
});
|
|
1995
|
+
const result = await conversation.forceCompact();
|
|
1910
1996
|
const responseText = formatCompactResult(result);
|
|
1911
1997
|
|
|
1912
1998
|
const assistantMsg = createAssistantMessage(responseText);
|
|
@@ -1943,7 +2029,7 @@ export async function handleSendMessage(
|
|
|
1943
2029
|
retryable: true,
|
|
1944
2030
|
});
|
|
1945
2031
|
} finally {
|
|
1946
|
-
conversation.
|
|
2032
|
+
conversation.setProcessing(false);
|
|
1947
2033
|
silentlyWithLog(
|
|
1948
2034
|
conversation.drainQueue(),
|
|
1949
2035
|
"compact-command queue drain",
|
|
@@ -1959,7 +2045,7 @@ export async function handleSendMessage(
|
|
|
1959
2045
|
}
|
|
1960
2046
|
|
|
1961
2047
|
if (slashResult.kind === "clean") {
|
|
1962
|
-
conversation.
|
|
2048
|
+
conversation.setProcessing(true);
|
|
1963
2049
|
const conversationId = mapping.conversationId;
|
|
1964
2050
|
// Outer try/finally guarantees the processing flag is cleared (and the
|
|
1965
2051
|
// queue drained) on every failure path — including a throw from the
|
|
@@ -2045,7 +2131,7 @@ export async function handleSendMessage(
|
|
|
2045
2131
|
conversationId,
|
|
2046
2132
|
};
|
|
2047
2133
|
} finally {
|
|
2048
|
-
conversation.
|
|
2134
|
+
conversation.setProcessing(false);
|
|
2049
2135
|
silentlyWithLog(conversation.drainQueue(), "clean-command queue drain");
|
|
2050
2136
|
}
|
|
2051
2137
|
}
|
|
@@ -2465,8 +2551,46 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2465
2551
|
description:
|
|
2466
2552
|
"Return messages for a conversation, including attachments and interface file metadata.",
|
|
2467
2553
|
tags: ["messages"],
|
|
2554
|
+
queryParams: [
|
|
2555
|
+
{
|
|
2556
|
+
name: "conversationId",
|
|
2557
|
+
type: "string",
|
|
2558
|
+
required: false,
|
|
2559
|
+
description:
|
|
2560
|
+
"Conversation UUID. One of conversationId or conversationKey is required.",
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
name: "conversationKey",
|
|
2564
|
+
type: "string",
|
|
2565
|
+
required: false,
|
|
2566
|
+
description:
|
|
2567
|
+
"Channel/external conversation key. One of conversationId or conversationKey is required.",
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
name: "page",
|
|
2571
|
+
type: "string",
|
|
2572
|
+
required: false,
|
|
2573
|
+
description:
|
|
2574
|
+
"When set to 'latest', returns the most recent page of messages with pagination metadata.",
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
name: "beforeTimestamp",
|
|
2578
|
+
type: "integer",
|
|
2579
|
+
required: false,
|
|
2580
|
+
description:
|
|
2581
|
+
"Return messages older than this timestamp (ms since epoch). Used for paging older history.",
|
|
2582
|
+
},
|
|
2583
|
+
{
|
|
2584
|
+
name: "limit",
|
|
2585
|
+
type: "integer",
|
|
2586
|
+
required: false,
|
|
2587
|
+
description: "Maximum number of messages to return.",
|
|
2588
|
+
},
|
|
2589
|
+
],
|
|
2468
2590
|
responseBody: z.object({
|
|
2469
|
-
messages: z
|
|
2591
|
+
messages: z
|
|
2592
|
+
.array(ConversationMessageSchema)
|
|
2593
|
+
.describe("Array of message objects"),
|
|
2470
2594
|
hasMore: z
|
|
2471
2595
|
.boolean()
|
|
2472
2596
|
.optional()
|
|
@@ -2483,6 +2607,13 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2483
2607
|
.nullable()
|
|
2484
2608
|
.optional()
|
|
2485
2609
|
.describe("ID of the oldest message in this page"),
|
|
2610
|
+
seq: z
|
|
2611
|
+
.number()
|
|
2612
|
+
.nullable()
|
|
2613
|
+
.optional()
|
|
2614
|
+
.describe(
|
|
2615
|
+
"Global SSE `seq` of the last event whose content is durably persisted for this conversation in the current daemon process. A client can align this snapshot with the `/events` stream by applying only events with `seq` greater than this value. Null when no events have been persisted in this process (cold conversation, after a daemon restart, or when the conversation has aged out of the in-memory map) — clients should cold-start in that case. Absent on older daemons that predate this field.",
|
|
2616
|
+
),
|
|
2486
2617
|
}),
|
|
2487
2618
|
handler: (args) => handleListMessages(args),
|
|
2488
2619
|
},
|
|
@@ -2500,17 +2631,60 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2500
2631
|
tags: ["messages"],
|
|
2501
2632
|
responseStatus: "202",
|
|
2502
2633
|
requestBody: z.object({
|
|
2503
|
-
|
|
2634
|
+
conversationId: z
|
|
2635
|
+
.string()
|
|
2636
|
+
.nullable()
|
|
2637
|
+
.optional()
|
|
2638
|
+
.describe(
|
|
2639
|
+
"Internal conversation id (0.8.6+ strict lookup). Omit both id and key to mint a new conversation server-side.",
|
|
2640
|
+
),
|
|
2641
|
+
conversationKey: z.string().nullable().optional(),
|
|
2504
2642
|
content: z.string().describe("Message text content"),
|
|
2505
2643
|
attachments: z
|
|
2506
2644
|
.array(z.unknown())
|
|
2507
|
-
.describe("Optional file attachments")
|
|
2645
|
+
.describe("Optional inline file attachments")
|
|
2646
|
+
.optional(),
|
|
2647
|
+
attachmentIds: z
|
|
2648
|
+
.array(z.string())
|
|
2649
|
+
.describe("Ids of previously uploaded attachments to attach")
|
|
2508
2650
|
.optional(),
|
|
2651
|
+
sourceChannel: z
|
|
2652
|
+
.string()
|
|
2653
|
+
.describe('Originating channel id (e.g. "vellum")'),
|
|
2654
|
+
interface: z
|
|
2655
|
+
.string()
|
|
2656
|
+
.describe('Originating interface id (e.g. "vellum")'),
|
|
2509
2657
|
conversationType: z.string().optional(),
|
|
2510
2658
|
slashCommand: z.string().optional(),
|
|
2511
2659
|
clientTimezone: z.string().optional(),
|
|
2512
2660
|
inferenceProfile: z.string().nullable().optional(),
|
|
2513
2661
|
riskThreshold: z.enum(VALID_RISK_THRESHOLDS).optional(),
|
|
2662
|
+
onboarding: z
|
|
2663
|
+
.object({
|
|
2664
|
+
tools: z.array(z.string()),
|
|
2665
|
+
tasks: z.array(z.string()),
|
|
2666
|
+
tone: z.string(),
|
|
2667
|
+
userName: z.string().optional(),
|
|
2668
|
+
assistantName: z.string().optional(),
|
|
2669
|
+
googleConnected: z.boolean().optional(),
|
|
2670
|
+
googleScopes: z.array(z.string()).optional(),
|
|
2671
|
+
priorAssistants: z.array(z.string()).optional(),
|
|
2672
|
+
cohort: z.string().optional(),
|
|
2673
|
+
websiteUrl: z.string().optional(),
|
|
2674
|
+
contentSourceUrl: z.string().optional(),
|
|
2675
|
+
bootstrapTemplate: z.string().optional(),
|
|
2676
|
+
initialMessage: z.string().optional(),
|
|
2677
|
+
skills: z.array(z.string()).optional(),
|
|
2678
|
+
})
|
|
2679
|
+
.describe("PreChat onboarding context, sent on the first message only")
|
|
2680
|
+
.optional(),
|
|
2681
|
+
}),
|
|
2682
|
+
responseBody: z.object({
|
|
2683
|
+
accepted: z.boolean(),
|
|
2684
|
+
conversationId: z.string().optional(),
|
|
2685
|
+
messageId: z.string().optional(),
|
|
2686
|
+
queued: z.boolean().optional(),
|
|
2687
|
+
requestId: z.string().optional(),
|
|
2514
2688
|
}),
|
|
2515
2689
|
handler: async (args) =>
|
|
2516
2690
|
handleSendMessage(args, {
|