@vellumai/assistant 0.8.4 → 0.8.6
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/AGENTS.md +33 -1
- package/ARCHITECTURE.md +3 -3
- package/bunfig.toml +6 -1
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/knip.json +2 -1
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +2748 -216
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +12 -28
- package/src/__tests__/audit-log-rotation.test.ts +72 -18
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +8 -11
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +5 -5
- package/src/__tests__/call-controller.test.ts +3 -3
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +6 -5
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +18 -27
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +215 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -16
- package/src/__tests__/computer-use-tools.test.ts +14 -18
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +231 -2
- package/src/__tests__/conversation-agent-loop.test.ts +581 -54
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +31 -24
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +61 -0
- package/src/__tests__/conversation-fork-crud.test.ts +239 -15
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +53 -11
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +14 -13
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +112 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +68 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +40 -147
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +218 -35
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -1
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +19 -19
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +6 -5
- package/src/__tests__/credential-vault-unit.test.ts +21 -21
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +236 -0
- package/src/__tests__/gemini-provider.test.ts +104 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +10 -7
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +5 -0
- package/src/__tests__/heartbeat-service.test.ts +5 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +6 -5
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +408 -9
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +16 -2
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +4 -6
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +4 -3
- package/src/__tests__/openai-provider.test.ts +54 -9
- package/src/__tests__/openai-responses-provider.test.ts +176 -14
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +11 -13
- package/src/__tests__/plugin-tool-contribution.test.ts +50 -40
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +84 -84
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +16 -18
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +48 -37
- package/src/__tests__/skill-projection.benchmark.test.ts +7 -13
- package/src/__tests__/skill-tool-factory.test.ts +97 -96
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +6 -5
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +183 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +35 -12
- package/src/__tests__/tool-executor.test.ts +64 -72
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +3 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +6 -7
- package/src/agent/loop.ts +88 -0
- package/src/api/README.md +127 -0
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/api/events/relationship-state-updated.ts +25 -0
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +129 -0
- package/src/api/package.json +10 -0
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +868 -0
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +491 -0
- package/src/cli/commands/memory-v3.ts +567 -0
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +4 -0
- package/src/config/assistant-feature-flags.ts +39 -46
- package/src/config/bundled-skills/document-editor/SKILL.md +16 -3
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +8 -7
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +33 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +228 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +102 -2
- package/src/config/schemas/memory-v2.ts +272 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/compactor.ts +52 -0
- package/src/context/token-estimator.ts +10 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +2 -3
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +390 -80
- package/src/daemon/conversation-agent-loop.ts +244 -90
- package/src/daemon/conversation-error.ts +64 -6
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +38 -17
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +77 -32
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +40 -75
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +39 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +29 -26
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +11 -24
- package/src/daemon/message-types/conversations.ts +22 -27
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -14
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +25 -48
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +62 -0
- package/src/daemon/tool-setup-types.ts +7 -0
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +30 -1
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +27 -154
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +35 -40
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +242 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +423 -182
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +14 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +66 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +122 -2
- package/src/memory/llm-request-log-source-local.ts +31 -0
- package/src/memory/llm-request-log-source.ts +40 -2
- package/src/memory/llm-request-log-store.ts +228 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +110 -7
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +19 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +10 -2
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +83 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +230 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +135 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +128 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +240 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +112 -0
- package/src/memory/v2/harness/trace.ts +64 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +466 -0
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +706 -0
- package/src/memory/v3/__tests__/filter.test.ts +560 -0
- package/src/memory/v3/__tests__/gate.test.ts +637 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +291 -0
- package/src/memory/v3/__tests__/loop.test.ts +775 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +489 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +398 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +508 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +784 -0
- package/src/memory/v3/__tests__/validate.test.ts +277 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +286 -0
- package/src/memory/v3/filter.ts +286 -0
- package/src/memory/v3/gate.ts +349 -0
- package/src/memory/v3/index-composition.ts +126 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +430 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +431 -0
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +347 -0
- package/src/memory/v3/traversal.ts +211 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +356 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +323 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +40 -50
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +20 -19
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +68 -29
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +84 -221
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +9 -13
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +233 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +42 -6
- package/src/providers/connection-model-compat.ts +61 -0
- package/src/providers/connection-resolution.ts +47 -14
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +70 -6
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +78 -1
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +116 -15
- package/src/providers/openai/codex-models.ts +20 -0
- package/src/providers/openai/responses-provider.ts +87 -30
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +20 -5
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +50 -7
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/types.ts +9 -0
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +46 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +12 -4
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +204 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +76 -9
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +356 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +4 -10
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +159 -4
- package/src/runtime/routes/conversation-management-routes.ts +108 -26
- package/src/runtime/routes/conversation-query-routes.ts +200 -44
- package/src/runtime/routes/conversation-routes.ts +409 -521
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +35 -8
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +10 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +474 -0
- package/src/runtime/routes/migration-routes.ts +32 -28
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +36 -28
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +282 -336
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +189 -7
- package/src/tools/execution-target.ts +18 -23
- package/src/tools/executor.ts +24 -56
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -10
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +224 -76
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +150 -123
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +18 -44
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +32 -101
- package/src/tools/ui-surface/definitions.ts +104 -108
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/browser.ts +7 -2
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
|
@@ -20,8 +20,13 @@ import {
|
|
|
20
20
|
} from "../../channels/types.js";
|
|
21
21
|
import { isHttpAuthDisabled } from "../../config/env.js";
|
|
22
22
|
import { getConfig } from "../../config/loader.js";
|
|
23
|
+
import {
|
|
24
|
+
mergeConsecutiveAssistantMessages,
|
|
25
|
+
mergeToolResultsIntoAssistantMessages,
|
|
26
|
+
} from "../../conversations/message-consolidation.js";
|
|
23
27
|
import { createApprovalConversationGenerator } from "../../daemon/approval-generators.js";
|
|
24
28
|
import type { Conversation } from "../../daemon/conversation.js";
|
|
29
|
+
import { persistQueuedMessageBody } from "../../daemon/conversation-messaging.js";
|
|
25
30
|
import {
|
|
26
31
|
buildModelInfoEvent,
|
|
27
32
|
formatCleanResult,
|
|
@@ -71,6 +76,7 @@ import {
|
|
|
71
76
|
} from "../../memory/canonical-guardian-store.js";
|
|
72
77
|
import {
|
|
73
78
|
addMessage,
|
|
79
|
+
extractImageSourcePaths,
|
|
74
80
|
getConversation,
|
|
75
81
|
getMessages,
|
|
76
82
|
getMessagesPaginated,
|
|
@@ -78,8 +84,6 @@ import {
|
|
|
78
84
|
type MessageRow,
|
|
79
85
|
provenanceFromTrustContext,
|
|
80
86
|
setConversationInferenceProfile,
|
|
81
|
-
setConversationOriginChannelIfUnset,
|
|
82
|
-
setConversationOriginInterfaceIfUnset,
|
|
83
87
|
} from "../../memory/conversation-crud.js";
|
|
84
88
|
import {
|
|
85
89
|
getConversationByKey,
|
|
@@ -121,7 +125,12 @@ import {
|
|
|
121
125
|
resolveTrustContext,
|
|
122
126
|
withSourceChannel,
|
|
123
127
|
} from "../trust-context-resolver.js";
|
|
124
|
-
import {
|
|
128
|
+
import {
|
|
129
|
+
BadRequestError,
|
|
130
|
+
InternalError,
|
|
131
|
+
NotFoundError,
|
|
132
|
+
RouteError,
|
|
133
|
+
} from "./errors.js";
|
|
125
134
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
126
135
|
import { RouteResponse } from "./types.js";
|
|
127
136
|
|
|
@@ -142,6 +151,34 @@ function isValidRiskThreshold(value: unknown): value is RiskThreshold {
|
|
|
142
151
|
);
|
|
143
152
|
}
|
|
144
153
|
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Temporary fix — remove when #31994 lands
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
//
|
|
158
|
+
// The canned-response paths in this file (canned greeting, inline approval
|
|
159
|
+
// reply, slash command, /compact, /clean) bypass the agent loop and so don't
|
|
160
|
+
// pick up the per-turn anchor id allocated in conversation-agent-loop.ts.
|
|
161
|
+
// Their `message_complete` events therefore went out without `messageId`,
|
|
162
|
+
// and the macOS client filter at ChatActionHandler.swift:507 dropped those
|
|
163
|
+
// events when they raced past the 50 ms streaming-buffer flush — leaving
|
|
164
|
+
// `isSending` stuck for the full 60 s watchdog window.
|
|
165
|
+
//
|
|
166
|
+
// Centralized so the patch surface is one helper + N one-line callers rather
|
|
167
|
+
// than N duplicated literals. When #31994 lands and stamps these sites with
|
|
168
|
+
// `state.assistantTurnId` directly, grep for `emitCannedMessageComplete` to
|
|
169
|
+
// find every call site and inline-then-delete.
|
|
170
|
+
function emitCannedMessageComplete(
|
|
171
|
+
send: (msg: ServerMessage) => void,
|
|
172
|
+
conversationId: string,
|
|
173
|
+
persistedAssistantId: string,
|
|
174
|
+
): void {
|
|
175
|
+
send({
|
|
176
|
+
type: "message_complete",
|
|
177
|
+
conversationId,
|
|
178
|
+
messageId: persistedAssistantId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
145
182
|
/**
|
|
146
183
|
* True when a message's persisted metadata explicitly flags it as hidden.
|
|
147
184
|
* Used to suppress internal scaffolding messages from UI history while
|
|
@@ -283,6 +320,8 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
283
320
|
verifiedActorExternalUserId?: string;
|
|
284
321
|
/** Verified actor principal ID for principal-based authorization. */
|
|
285
322
|
verifiedActorPrincipalId?: string;
|
|
323
|
+
/** Originating client identifier for sync_changed self-echo suppression. */
|
|
324
|
+
originClientId?: string;
|
|
286
325
|
}): Promise<{ consumed: boolean; messageId?: string }> {
|
|
287
326
|
const {
|
|
288
327
|
conversationId,
|
|
@@ -295,6 +334,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
295
334
|
approvalConversationGenerator,
|
|
296
335
|
verifiedActorExternalUserId,
|
|
297
336
|
verifiedActorPrincipalId,
|
|
337
|
+
originClientId,
|
|
298
338
|
} = params;
|
|
299
339
|
const trimmedContent = content.trim();
|
|
300
340
|
|
|
@@ -355,23 +395,10 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
355
395
|
// is not re-processed as a new user turn.
|
|
356
396
|
let messageId: string | undefined;
|
|
357
397
|
try {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
guardianImageSourcePaths[`${i}:${a.filename}`] = a.filePath;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const channelMeta = {
|
|
366
|
-
userMessageChannel: sourceChannel,
|
|
367
|
-
assistantMessageChannel: sourceChannel,
|
|
368
|
-
userMessageInterface: sourceInterface,
|
|
369
|
-
assistantMessageInterface: sourceInterface,
|
|
370
|
-
provenanceTrustClass: "guardian" as const,
|
|
371
|
-
...(Object.keys(guardianImageSourcePaths).length > 0
|
|
372
|
-
? { imageSourcePaths: guardianImageSourcePaths }
|
|
373
|
-
: {}),
|
|
374
|
-
};
|
|
398
|
+
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
399
|
+
provenanceOverride: { provenanceTrustClass: "guardian" },
|
|
400
|
+
attachments,
|
|
401
|
+
});
|
|
375
402
|
|
|
376
403
|
const cleanUserMessage = createUserMessage(content, attachments);
|
|
377
404
|
const llmUserMessage = enrichMessageWithSourcePaths(
|
|
@@ -392,7 +419,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
392
419
|
? "Decision applied."
|
|
393
420
|
: "Request already resolved.");
|
|
394
421
|
const assistantMessage = createAssistantMessage(replyText);
|
|
395
|
-
await addMessage(
|
|
422
|
+
const persistedAssistant = await addMessage(
|
|
396
423
|
conversationId,
|
|
397
424
|
"assistant",
|
|
398
425
|
JSON.stringify(assistantMessage.content),
|
|
@@ -407,9 +434,9 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
407
434
|
text: replyText,
|
|
408
435
|
conversationId: conversationId,
|
|
409
436
|
});
|
|
410
|
-
onEvent
|
|
437
|
+
emitCannedMessageComplete(onEvent, conversationId, persistedAssistant.id);
|
|
411
438
|
}
|
|
412
|
-
publishConversationMessagesChanged(conversationId);
|
|
439
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
413
440
|
} catch (err) {
|
|
414
441
|
log.warn(
|
|
415
442
|
{ err, conversationId },
|
|
@@ -498,6 +525,10 @@ export function handleListMessages({
|
|
|
498
525
|
|
|
499
526
|
let rawMessages: MessageRow[];
|
|
500
527
|
let hasMore = false;
|
|
528
|
+
// Resume cursor surfaced when the paginated scan stops on its row cap with a
|
|
529
|
+
// (possibly empty) page — lets us still emit an oldest cursor so the client
|
|
530
|
+
// can request the next window instead of stalling.
|
|
531
|
+
let scanResumeCursor: { createdAt: number; id: string } | undefined;
|
|
501
532
|
|
|
502
533
|
// Drop messages flagged as hidden in metadata (e.g. internal scaffolding
|
|
503
534
|
// like retrospective instructions). The LLM-side history loader
|
|
@@ -521,6 +552,7 @@ export function handleListMessages({
|
|
|
521
552
|
);
|
|
522
553
|
rawMessages = result.messages;
|
|
523
554
|
hasMore = result.hasMore;
|
|
555
|
+
scanResumeCursor = result.nextCursor;
|
|
524
556
|
} else {
|
|
525
557
|
rawMessages = getMessages(resolvedConversationId).filter(visibleFilter);
|
|
526
558
|
}
|
|
@@ -715,10 +747,13 @@ export function handleListMessages({
|
|
|
715
747
|
|
|
716
748
|
// Align msgAttachments order with the file-block order captured by
|
|
717
749
|
// renderHistoryContent. When a file block was persisted with
|
|
718
|
-
// `_attachmentId
|
|
719
|
-
// (the `attachment:N` entries in contentOrder index into
|
|
720
|
-
// DB rows without a matching ref go to the tail as orphan
|
|
721
|
-
// unmatched refs drop their contentOrder entry and trigger a remap.
|
|
750
|
+
// `_attachmentId` (user-message uploads), we join on that id to position
|
|
751
|
+
// the chip inline (the `attachment:N` entries in contentOrder index into
|
|
752
|
+
// msgAttachments). DB rows without a matching ref go to the tail as orphan
|
|
753
|
+
// chips; unmatched refs drop their contentOrder entry and trigger a remap.
|
|
754
|
+
// Assistant-authored file blocks carry no `_attachmentId`, so when no ids
|
|
755
|
+
// match we fall back to positional alignment if the ref and row counts
|
|
756
|
+
// agree; otherwise we strip the markers and let chips fall to the tail.
|
|
722
757
|
let alignedContentOrder = m.contentOrder;
|
|
723
758
|
if (
|
|
724
759
|
m.attachmentRefs.length > 0 &&
|
|
@@ -769,14 +804,18 @@ export function handleListMessages({
|
|
|
769
804
|
: undefined;
|
|
770
805
|
})
|
|
771
806
|
.filter((e): e is string => e !== undefined);
|
|
772
|
-
} else {
|
|
773
|
-
// No
|
|
774
|
-
//
|
|
775
|
-
//
|
|
807
|
+
} else if (m.attachmentRefs.length !== msgAttachments.length) {
|
|
808
|
+
// No ref carried an attachmentId we could match and the counts
|
|
809
|
+
// disagree, so positional mapping can't be trusted — strip any
|
|
810
|
+
// attachment:N entries so the client doesn't position attachments
|
|
811
|
+
// inline against a misaligned array (they fall to the tail instead).
|
|
776
812
|
alignedContentOrder = m.contentOrder.filter(
|
|
777
813
|
(entry) => !ATTACHMENT_ENTRY_RE.test(entry),
|
|
778
814
|
);
|
|
779
815
|
}
|
|
816
|
+
// Otherwise no ref matched an id but the counts agree (the
|
|
817
|
+
// assistant-authored case): the Nth marker maps to the Nth row
|
|
818
|
+
// positionally, so the original contentOrder is left untouched.
|
|
780
819
|
} else if (m.attachmentRefs.length > 0 && msgAttachments.length === 0) {
|
|
781
820
|
// Refs were captured but no DB rows came back — drop the
|
|
782
821
|
// contentOrder entries to avoid out-of-bounds renders.
|
|
@@ -792,14 +831,8 @@ export function handleListMessages({
|
|
|
792
831
|
// on createdAt. The mismatch is benign — it may return slightly extra
|
|
793
832
|
// data on a page boundary but never loses messages.
|
|
794
833
|
const displayTimestamp = m.sentAt ?? m.timestamp;
|
|
795
|
-
const mergedMessageIds = mergedIdMap.get(m.id) ?? [];
|
|
796
|
-
const daemonMessageId =
|
|
797
|
-
m.role === "assistant"
|
|
798
|
-
? (mergedMessageIds[mergedMessageIds.length - 1] ?? m.id)
|
|
799
|
-
: undefined;
|
|
800
834
|
return {
|
|
801
835
|
id: m.id ?? "",
|
|
802
|
-
...(daemonMessageId ? { daemonMessageId } : {}),
|
|
803
836
|
role: m.role,
|
|
804
837
|
content: m.text,
|
|
805
838
|
timestamp: new Date(displayTimestamp).toISOString(),
|
|
@@ -821,10 +854,16 @@ export function handleListMessages({
|
|
|
821
854
|
});
|
|
822
855
|
|
|
823
856
|
if (isPaginated) {
|
|
857
|
+
// Prefer the page's oldest visible row (the documented cursor semantic).
|
|
858
|
+
// When a scan-cap-truncated page comes back empty there's no visible row
|
|
859
|
+
// to anchor on, so fall back to the resume cursor so the client still gets
|
|
860
|
+
// a `(timestamp, id)` to continue paginating from instead of stalling.
|
|
824
861
|
const oldestTimestamp =
|
|
825
|
-
rawMessages.length > 0
|
|
862
|
+
rawMessages.length > 0
|
|
863
|
+
? rawMessages[0].createdAt
|
|
864
|
+
: scanResumeCursor?.createdAt;
|
|
826
865
|
const oldestMessageId =
|
|
827
|
-
rawMessages.length > 0 ? rawMessages[0].id :
|
|
866
|
+
rawMessages.length > 0 ? rawMessages[0].id : scanResumeCursor?.id;
|
|
828
867
|
// `page=latest` always emits both metadata fields so the web client has
|
|
829
868
|
// a stable contract; emit `null` when the conversation is empty.
|
|
830
869
|
// The existing `beforeTimestamp` branch keeps its conditional shape to
|
|
@@ -849,305 +888,6 @@ export function handleListMessages({
|
|
|
849
888
|
return { messages };
|
|
850
889
|
}
|
|
851
890
|
|
|
852
|
-
// ── Tool-result merging ─────────────────────────────────────────────
|
|
853
|
-
|
|
854
|
-
function isToolResultType(type: string): boolean {
|
|
855
|
-
return type === "tool_result" || type === "web_search_tool_result";
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function isSystemNoticeText(block: Record<string, unknown>): boolean {
|
|
859
|
-
if (block.type !== "text") return false;
|
|
860
|
-
const text = typeof block.text === "string" ? block.text : "";
|
|
861
|
-
return (
|
|
862
|
-
text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
/**
|
|
867
|
-
* Merge tool_result blocks from user messages into the preceding assistant
|
|
868
|
-
* message's content array. This lets renderHistoryContent's pendingToolUses
|
|
869
|
-
* map pair tool_use and tool_result blocks, preventing "unknown" tool names.
|
|
870
|
-
*
|
|
871
|
-
* User messages that consist entirely of tool_result blocks (and optional
|
|
872
|
-
* system_notice text) are removed from the output. Mixed messages (tool_result
|
|
873
|
-
* + real user text) keep only the non-tool-result blocks.
|
|
874
|
-
*/
|
|
875
|
-
function mergeToolResultsIntoAssistantMessages(
|
|
876
|
-
messages: MessageRow[],
|
|
877
|
-
): MessageRow[] {
|
|
878
|
-
// Index of the most recent assistant message in the output array.
|
|
879
|
-
let lastAssistantIdx = -1;
|
|
880
|
-
// Parsed content caches — lazily populated per assistant message.
|
|
881
|
-
const parsedAssistantContent = new Map<number, unknown[]>();
|
|
882
|
-
|
|
883
|
-
const result: MessageRow[] = [];
|
|
884
|
-
|
|
885
|
-
for (const msg of messages) {
|
|
886
|
-
if (msg.role === "assistant") {
|
|
887
|
-
lastAssistantIdx = result.length;
|
|
888
|
-
result.push(msg);
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// Only process user messages — other roles pass through.
|
|
893
|
-
if (msg.role !== "user") {
|
|
894
|
-
result.push(msg);
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
let blocks: unknown[];
|
|
899
|
-
try {
|
|
900
|
-
const parsed = JSON.parse(msg.content);
|
|
901
|
-
if (!Array.isArray(parsed)) {
|
|
902
|
-
result.push(msg);
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
blocks = parsed;
|
|
906
|
-
} catch {
|
|
907
|
-
result.push(msg);
|
|
908
|
-
continue;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Separate tool-result blocks from real user content.
|
|
912
|
-
const toolResultBlocks: unknown[] = [];
|
|
913
|
-
const otherBlocks: unknown[] = [];
|
|
914
|
-
for (const block of blocks) {
|
|
915
|
-
if (
|
|
916
|
-
typeof block === "object" &&
|
|
917
|
-
block !== null &&
|
|
918
|
-
typeof (block as Record<string, unknown>).type === "string"
|
|
919
|
-
) {
|
|
920
|
-
const rec = block as Record<string, unknown>;
|
|
921
|
-
if (isToolResultType(rec.type as string)) {
|
|
922
|
-
toolResultBlocks.push(block);
|
|
923
|
-
} else if (isSystemNoticeText(rec)) {
|
|
924
|
-
// System notices don't count as user content — drop them when
|
|
925
|
-
// the message is otherwise tool-result-only.
|
|
926
|
-
otherBlocks.push(block);
|
|
927
|
-
} else {
|
|
928
|
-
otherBlocks.push(block);
|
|
929
|
-
}
|
|
930
|
-
} else {
|
|
931
|
-
otherBlocks.push(block);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// No tool results → pass through unchanged. System notices are only
|
|
936
|
-
// injected alongside tool results in the agent loop, so a pure user
|
|
937
|
-
// message (no tool_result blocks) should never be filtered — even if
|
|
938
|
-
// the user's text happens to look like a system_notice tag.
|
|
939
|
-
if (toolResultBlocks.length === 0) {
|
|
940
|
-
result.push(msg);
|
|
941
|
-
continue;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// Append tool_result blocks to the preceding assistant message's content.
|
|
945
|
-
if (lastAssistantIdx >= 0) {
|
|
946
|
-
const assistant = result[lastAssistantIdx];
|
|
947
|
-
let assistantContent = parsedAssistantContent.get(lastAssistantIdx);
|
|
948
|
-
if (!assistantContent) {
|
|
949
|
-
try {
|
|
950
|
-
const parsed = JSON.parse(assistant.content);
|
|
951
|
-
assistantContent = Array.isArray(parsed) ? parsed : [parsed];
|
|
952
|
-
} catch {
|
|
953
|
-
assistantContent = [];
|
|
954
|
-
}
|
|
955
|
-
parsedAssistantContent.set(lastAssistantIdx, assistantContent);
|
|
956
|
-
}
|
|
957
|
-
assistantContent.push(...toolResultBlocks);
|
|
958
|
-
} else {
|
|
959
|
-
// No preceding assistant message (pagination boundary) — keep the
|
|
960
|
-
// original message as-is to avoid permanent data loss. The preceding
|
|
961
|
-
// assistant tool_use lives in the previous page; dropping the result
|
|
962
|
-
// here would be unrecoverable.
|
|
963
|
-
// Still strip system notices so internal prompt text isn't exposed.
|
|
964
|
-
const filteredBlocks = blocks.filter(
|
|
965
|
-
(b) =>
|
|
966
|
-
!(
|
|
967
|
-
typeof b === "object" &&
|
|
968
|
-
b !== null &&
|
|
969
|
-
isSystemNoticeText(b as Record<string, unknown>)
|
|
970
|
-
),
|
|
971
|
-
);
|
|
972
|
-
result.push({
|
|
973
|
-
...msg,
|
|
974
|
-
content:
|
|
975
|
-
filteredBlocks.length === blocks.length
|
|
976
|
-
? msg.content
|
|
977
|
-
: JSON.stringify(filteredBlocks),
|
|
978
|
-
});
|
|
979
|
-
continue;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// If the user message had only tool_result (+ system_notice) blocks,
|
|
983
|
-
// suppress it entirely. Otherwise keep the non-tool-result content.
|
|
984
|
-
const realUserContent = otherBlocks.filter(
|
|
985
|
-
(b) =>
|
|
986
|
-
!(
|
|
987
|
-
typeof b === "object" &&
|
|
988
|
-
b !== null &&
|
|
989
|
-
isSystemNoticeText(b as Record<string, unknown>)
|
|
990
|
-
),
|
|
991
|
-
);
|
|
992
|
-
if (realUserContent.length > 0) {
|
|
993
|
-
result.push({ ...msg, content: JSON.stringify(otherBlocks) });
|
|
994
|
-
}
|
|
995
|
-
// else: tool-result-only → suppressed (results already merged above)
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// Write back any modified assistant message content.
|
|
999
|
-
for (const [idx, content] of parsedAssistantContent) {
|
|
1000
|
-
result[idx] = { ...result[idx], content: JSON.stringify(content) };
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
return result;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// ── Consecutive assistant message merging ────────────────────────────
|
|
1007
|
-
|
|
1008
|
-
/** Parse a message's JSON content into an array of content blocks. */
|
|
1009
|
-
function parseContentBlocks(content: string): unknown[] {
|
|
1010
|
-
try {
|
|
1011
|
-
const parsed = JSON.parse(content);
|
|
1012
|
-
return Array.isArray(parsed) ? parsed : [parsed];
|
|
1013
|
-
} catch (err) {
|
|
1014
|
-
log.warn(
|
|
1015
|
-
{ err },
|
|
1016
|
-
"Failed to parse content blocks during assistant message merge",
|
|
1017
|
-
);
|
|
1018
|
-
return [];
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* Append content blocks from a donor message onto a target block array.
|
|
1024
|
-
* Parses the donor's JSON content and pushes each block into `target`.
|
|
1025
|
-
*/
|
|
1026
|
-
function appendContentBlocks(target: unknown[], donorContent: string): void {
|
|
1027
|
-
try {
|
|
1028
|
-
const parsed = JSON.parse(donorContent);
|
|
1029
|
-
if (Array.isArray(parsed)) {
|
|
1030
|
-
target.push(...parsed);
|
|
1031
|
-
} else {
|
|
1032
|
-
target.push(parsed);
|
|
1033
|
-
}
|
|
1034
|
-
} catch (err) {
|
|
1035
|
-
log.warn(
|
|
1036
|
-
{ err },
|
|
1037
|
-
"Failed to parse donor content blocks during assistant message merge",
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
/**
|
|
1043
|
-
* Promote metadata fields from a donor message to the surviving message
|
|
1044
|
-
* when the survivor lacks them. Currently promotes `subagentNotification`.
|
|
1045
|
-
* Returns a new MessageRow if promotion occurred, otherwise the original.
|
|
1046
|
-
*/
|
|
1047
|
-
function promoteMetadata(survivor: MessageRow, donor: MessageRow): MessageRow {
|
|
1048
|
-
if (donor.metadata && survivor.metadata) {
|
|
1049
|
-
try {
|
|
1050
|
-
const survivorMeta = JSON.parse(survivor.metadata);
|
|
1051
|
-
const donorMeta = JSON.parse(donor.metadata);
|
|
1052
|
-
if (
|
|
1053
|
-
!survivorMeta.subagentNotification &&
|
|
1054
|
-
donorMeta.subagentNotification
|
|
1055
|
-
) {
|
|
1056
|
-
survivorMeta.subagentNotification = donorMeta.subagentNotification;
|
|
1057
|
-
return { ...survivor, metadata: JSON.stringify(survivorMeta) };
|
|
1058
|
-
}
|
|
1059
|
-
} catch (err) {
|
|
1060
|
-
log.warn(
|
|
1061
|
-
{ err },
|
|
1062
|
-
"Failed to parse metadata during assistant message merge",
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
} else if (donor.metadata && !survivor.metadata) {
|
|
1066
|
-
return { ...survivor, metadata: donor.metadata };
|
|
1067
|
-
}
|
|
1068
|
-
return survivor;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
/**
|
|
1072
|
-
* Merge consecutive assistant messages into a single message at query time.
|
|
1073
|
-
*
|
|
1074
|
-
* During streaming, all assistant turns within one agent loop accumulate on
|
|
1075
|
-
* a single client-side ChatMessage. In the DB, each API turn is stored as a
|
|
1076
|
-
* separate assistant row (consolidation is deferred to compaction for
|
|
1077
|
-
* prefix-cache stability). This produces N separate assistant messages that
|
|
1078
|
-
* the client renders as N individual bubbles — each showing "Completed 1
|
|
1079
|
-
* step" instead of one grouped "Completed N steps" accordion.
|
|
1080
|
-
*
|
|
1081
|
-
* This function concatenates the content block arrays of consecutive
|
|
1082
|
-
* assistant messages (no intervening user messages after tool-result
|
|
1083
|
-
* merging) into the first message of each run. The merged messages are
|
|
1084
|
-
* removed from the output. This is query-time only — the DB is not
|
|
1085
|
-
* modified.
|
|
1086
|
-
*
|
|
1087
|
-
* The first message in each run keeps its id, createdAt, and metadata so
|
|
1088
|
-
* that attachment lookups, display timestamps, and subagent notifications
|
|
1089
|
-
* continue to work. Metadata from later messages in the run (e.g.
|
|
1090
|
-
* subagentNotification) is preserved by promoting it to the surviving
|
|
1091
|
-
* message when the surviving message has no metadata of its own for that
|
|
1092
|
-
* field.
|
|
1093
|
-
*/
|
|
1094
|
-
function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
|
|
1095
|
-
messages: MessageRow[];
|
|
1096
|
-
/** Maps each surviving message ID → all original message IDs merged into it. */
|
|
1097
|
-
mergedIdMap: Map<string, string[]>;
|
|
1098
|
-
} {
|
|
1099
|
-
const result: MessageRow[] = [];
|
|
1100
|
-
// Key = index in `result`, value = accumulated content blocks.
|
|
1101
|
-
const pendingMerges = new Map<number, unknown[]>();
|
|
1102
|
-
// Key = index in `result`, value = IDs of messages merged into the target.
|
|
1103
|
-
const mergedIds = new Map<number, string[]>();
|
|
1104
|
-
|
|
1105
|
-
for (const msg of messages) {
|
|
1106
|
-
const lastIdx = result.length - 1;
|
|
1107
|
-
const isConsecutiveAssistant =
|
|
1108
|
-
msg.role === "assistant" &&
|
|
1109
|
-
lastIdx >= 0 &&
|
|
1110
|
-
result[lastIdx].role === "assistant";
|
|
1111
|
-
|
|
1112
|
-
if (!isConsecutiveAssistant) {
|
|
1113
|
-
result.push(msg);
|
|
1114
|
-
continue;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Track the donor message ID.
|
|
1118
|
-
let ids = mergedIds.get(lastIdx);
|
|
1119
|
-
if (!ids) {
|
|
1120
|
-
ids = [];
|
|
1121
|
-
mergedIds.set(lastIdx, ids);
|
|
1122
|
-
}
|
|
1123
|
-
ids.push(msg.id);
|
|
1124
|
-
|
|
1125
|
-
// Lazily parse the target's content on first merge.
|
|
1126
|
-
let targetContent = pendingMerges.get(lastIdx);
|
|
1127
|
-
if (!targetContent) {
|
|
1128
|
-
targetContent = parseContentBlocks(result[lastIdx].content);
|
|
1129
|
-
pendingMerges.set(lastIdx, targetContent);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
appendContentBlocks(targetContent, msg.content);
|
|
1133
|
-
result[lastIdx] = promoteMetadata(result[lastIdx], msg);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
// Write back merged content for any messages that were targets.
|
|
1137
|
-
for (const [idx, content] of pendingMerges) {
|
|
1138
|
-
result[idx] = { ...result[idx], content: JSON.stringify(content) };
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// Build the merged ID map keyed by surviving message ID.
|
|
1142
|
-
const mergedIdMap = new Map<string, string[]>();
|
|
1143
|
-
for (const [idx, ids] of mergedIds) {
|
|
1144
|
-
mergedIdMap.set(result[idx].id, ids);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
return { messages: result, mergedIdMap };
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
/**
|
|
1151
891
|
/**
|
|
1152
892
|
* Persist the pre-chat onboarding payload to disk.
|
|
1153
893
|
*
|
|
@@ -1240,6 +980,7 @@ export async function handleSendMessage(
|
|
|
1240
980
|
): Promise<unknown> {
|
|
1241
981
|
const body = (rawBody ?? {}) as {
|
|
1242
982
|
conversationKey?: string;
|
|
983
|
+
conversationId?: string;
|
|
1243
984
|
content?: string;
|
|
1244
985
|
attachmentIds?: string[];
|
|
1245
986
|
sourceChannel?: string;
|
|
@@ -1266,13 +1007,21 @@ export async function handleSendMessage(
|
|
|
1266
1007
|
cohort?: string;
|
|
1267
1008
|
websiteUrl?: string;
|
|
1268
1009
|
contentSourceUrl?: string;
|
|
1010
|
+
bootstrapTemplate?: string;
|
|
1011
|
+
initialMessage?: string;
|
|
1012
|
+
skills?: string[];
|
|
1269
1013
|
};
|
|
1270
1014
|
};
|
|
1271
1015
|
|
|
1272
1016
|
const actorPrincipalId = headers?.["x-vellum-actor-principal-id"];
|
|
1273
1017
|
const principalType = headers?.["x-vellum-principal-type"];
|
|
1018
|
+
const originClientId = headers?.["x-vellum-client-id"]?.trim() || undefined;
|
|
1274
1019
|
|
|
1275
1020
|
const { conversationKey, content, attachmentIds } = body;
|
|
1021
|
+
const inboundConversationId =
|
|
1022
|
+
typeof body.conversationId === "string" && body.conversationId.length > 0
|
|
1023
|
+
? body.conversationId
|
|
1024
|
+
: undefined;
|
|
1276
1025
|
const clientMessageId =
|
|
1277
1026
|
typeof body.clientMessageId === "string" ? body.clientMessageId : undefined;
|
|
1278
1027
|
const requestedInferenceProfile =
|
|
@@ -1340,12 +1089,6 @@ export async function handleSendMessage(
|
|
|
1340
1089
|
? (canonicalizeTimeZone(body.clientTimezone) ?? undefined)
|
|
1341
1090
|
: undefined;
|
|
1342
1091
|
|
|
1343
|
-
// When conversationKey is omitted, derive a stable default from
|
|
1344
|
-
// sourceChannel + sourceInterface so that repeated calls from the same
|
|
1345
|
-
// channel/interface pair share a single conversation thread.
|
|
1346
|
-
const resolvedConversationKey =
|
|
1347
|
-
conversationKey ?? `default:${sourceChannel}:${sourceInterface}`;
|
|
1348
|
-
|
|
1349
1092
|
// Reject non-string content values (numbers, objects, etc.)
|
|
1350
1093
|
if (content != null && typeof content !== "string") {
|
|
1351
1094
|
throw new BadRequestError("content must be a string");
|
|
@@ -1409,9 +1152,45 @@ export async function handleSendMessage(
|
|
|
1409
1152
|
// timer so the next heartbeat is a full interval after this interaction.
|
|
1410
1153
|
HeartbeatService.getInstance()?.resetTimer();
|
|
1411
1154
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1155
|
+
// Resolve the target conversation. Fetch by `conversationId` (the
|
|
1156
|
+
// assistant-minted internal id) when the client supplies it — clients
|
|
1157
|
+
// must obtain this id from a prior daemon response, so a missing row
|
|
1158
|
+
// is a 404. Otherwise fall through to the external-key path: the
|
|
1159
|
+
// client-supplied `conversationKey` (external-key lookup; materializes
|
|
1160
|
+
// on first use) or, when neither is provided, a channel-dependent
|
|
1161
|
+
// default. The vellum channel mints a fresh conversation on every
|
|
1162
|
+
// empty-handed send so first-message-of-a-new-chat surfaces with a
|
|
1163
|
+
// server-minted id; other channels (phone, slack, …) share a stable
|
|
1164
|
+
// `default:<channel>:<interface>` thread so repeated calls from the
|
|
1165
|
+
// same channel/interface stay co-located.
|
|
1166
|
+
let mapping: {
|
|
1167
|
+
conversationId: string;
|
|
1168
|
+
conversationType: string;
|
|
1169
|
+
created: boolean;
|
|
1170
|
+
};
|
|
1171
|
+
if (inboundConversationId !== undefined) {
|
|
1172
|
+
const existing = getConversation(inboundConversationId);
|
|
1173
|
+
if (!existing) {
|
|
1174
|
+
throw new NotFoundError(
|
|
1175
|
+
`Conversation ${inboundConversationId} not found`,
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
mapping = {
|
|
1179
|
+
conversationId: existing.id,
|
|
1180
|
+
conversationType: existing.conversationType,
|
|
1181
|
+
created: false,
|
|
1182
|
+
};
|
|
1183
|
+
} else {
|
|
1184
|
+
const resolvedConversationKey =
|
|
1185
|
+
conversationKey && conversationKey.length > 0
|
|
1186
|
+
? conversationKey
|
|
1187
|
+
: sourceChannel === "vellum"
|
|
1188
|
+
? crypto.randomUUID()
|
|
1189
|
+
: `default:${sourceChannel}:${sourceInterface}`;
|
|
1190
|
+
mapping = getOrCreateConversation(resolvedConversationKey, {
|
|
1191
|
+
conversationType: "standard",
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1415
1194
|
|
|
1416
1195
|
if (requestedRiskThreshold !== undefined) {
|
|
1417
1196
|
const result = await ipcCall("set_conversation_threshold", {
|
|
@@ -1445,6 +1224,7 @@ export async function handleSendMessage(
|
|
|
1445
1224
|
publishConversationListAndMetadataChanged(
|
|
1446
1225
|
"created",
|
|
1447
1226
|
mapping.conversationId,
|
|
1227
|
+
originClientId,
|
|
1448
1228
|
);
|
|
1449
1229
|
}
|
|
1450
1230
|
}
|
|
@@ -1620,51 +1400,38 @@ export async function handleSendMessage(
|
|
|
1620
1400
|
: ("content-source" as const);
|
|
1621
1401
|
effectiveContent = buildScanFirstMessage(scanUrl, scanVariant);
|
|
1622
1402
|
// Fall through to normal inference path below
|
|
1623
|
-
} else if (isWakeUp && body.onboarding?.
|
|
1624
|
-
effectiveContent =
|
|
1625
|
-
// Fall through to normal inference path — the bootstrap template
|
|
1626
|
-
// and geo-writing skill handle this message.
|
|
1403
|
+
} else if (isWakeUp && body.onboarding?.initialMessage) {
|
|
1404
|
+
effectiveContent = body.onboarding.initialMessage;
|
|
1627
1405
|
} else if (isWakeUp) {
|
|
1628
1406
|
const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
|
|
1629
1407
|
|
|
1630
1408
|
conversation.processing = true;
|
|
1631
1409
|
let cleanupDeferred = false;
|
|
1632
1410
|
try {
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1411
|
+
const rawContent = content ?? "";
|
|
1412
|
+
const attachments = hasAttachments
|
|
1413
|
+
? smDeps.resolveAttachments(attachmentIds)
|
|
1414
|
+
: [];
|
|
1415
|
+
const greetingMeta = {
|
|
1636
1416
|
userMessageChannel: sourceChannel,
|
|
1637
1417
|
assistantMessageChannel: sourceChannel,
|
|
1638
1418
|
userMessageInterface: sourceInterface,
|
|
1639
1419
|
assistantMessageInterface: sourceInterface,
|
|
1640
1420
|
};
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
:
|
|
1646
|
-
|
|
1647
|
-
const persisted = await addMessage(
|
|
1648
|
-
mapping.conversationId,
|
|
1649
|
-
"user",
|
|
1650
|
-
JSON.stringify(userMsg.content),
|
|
1651
|
-
channelMeta,
|
|
1652
|
-
);
|
|
1653
|
-
conversation.getMessages().push(userMsg);
|
|
1654
|
-
|
|
1655
|
-
setConversationOriginChannelIfUnset(
|
|
1656
|
-
mapping.conversationId,
|
|
1657
|
-
sourceChannel,
|
|
1658
|
-
);
|
|
1659
|
-
setConversationOriginInterfaceIfUnset(
|
|
1660
|
-
mapping.conversationId,
|
|
1661
|
-
sourceInterface,
|
|
1662
|
-
);
|
|
1421
|
+
const persisted = await persistQueuedMessageBody(conversation, {
|
|
1422
|
+
content: rawContent,
|
|
1423
|
+
attachments,
|
|
1424
|
+
requestId: crypto.randomUUID(),
|
|
1425
|
+
metadata: greetingMeta,
|
|
1426
|
+
});
|
|
1663
1427
|
|
|
1664
1428
|
const conversationId = mapping.conversationId;
|
|
1429
|
+
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
1430
|
+
trustContext: conversation.trustContext,
|
|
1431
|
+
});
|
|
1665
1432
|
|
|
1666
1433
|
const assistantMsg = createAssistantMessage(cannedGreeting);
|
|
1667
|
-
await addMessage(
|
|
1434
|
+
const persistedAssistant = await addMessage(
|
|
1668
1435
|
mapping.conversationId,
|
|
1669
1436
|
"assistant",
|
|
1670
1437
|
JSON.stringify(assistantMsg.content),
|
|
@@ -1708,8 +1475,12 @@ export async function handleSendMessage(
|
|
|
1708
1475
|
text: cannedGreeting,
|
|
1709
1476
|
conversationId,
|
|
1710
1477
|
});
|
|
1711
|
-
|
|
1712
|
-
|
|
1478
|
+
emitCannedMessageComplete(
|
|
1479
|
+
broadcastMessage,
|
|
1480
|
+
conversationId,
|
|
1481
|
+
persistedAssistant.id,
|
|
1482
|
+
);
|
|
1483
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1713
1484
|
conversation.processing = false;
|
|
1714
1485
|
silentlyWithLog(
|
|
1715
1486
|
conversation.drainQueue(),
|
|
@@ -1787,6 +1558,7 @@ export async function handleSendMessage(
|
|
|
1787
1558
|
: deps.approvalConversationGenerator,
|
|
1788
1559
|
verifiedActorExternalUserId,
|
|
1789
1560
|
verifiedActorPrincipalId,
|
|
1561
|
+
originClientId,
|
|
1790
1562
|
});
|
|
1791
1563
|
if (inlineReplyResult.consumed) {
|
|
1792
1564
|
return {
|
|
@@ -1807,25 +1579,22 @@ export async function handleSendMessage(
|
|
|
1807
1579
|
if (conversation.isProcessing()) {
|
|
1808
1580
|
// Queue the message so it's processed when the current turn completes
|
|
1809
1581
|
const requestId = crypto.randomUUID();
|
|
1810
|
-
const enqueueResult = conversation.enqueueMessage(
|
|
1811
|
-
contentAfterScan,
|
|
1582
|
+
const enqueueResult = conversation.enqueueMessage({
|
|
1583
|
+
content: contentAfterScan,
|
|
1812
1584
|
attachments,
|
|
1813
|
-
broadcastMessage,
|
|
1585
|
+
onEvent: broadcastMessage,
|
|
1814
1586
|
requestId,
|
|
1815
|
-
|
|
1816
|
-
undefined, // currentPage
|
|
1817
|
-
{
|
|
1587
|
+
metadata: {
|
|
1818
1588
|
userMessageChannel: sourceChannel,
|
|
1819
1589
|
assistantMessageChannel: sourceChannel,
|
|
1820
1590
|
userMessageInterface: sourceInterface,
|
|
1821
1591
|
assistantMessageInterface: sourceInterface,
|
|
1822
1592
|
...(body.automated === true ? { automated: true } : {}),
|
|
1823
1593
|
},
|
|
1824
|
-
|
|
1825
|
-
undefined, // displayContent
|
|
1594
|
+
isInteractive,
|
|
1826
1595
|
transport,
|
|
1827
1596
|
clientMessageId,
|
|
1828
|
-
);
|
|
1597
|
+
});
|
|
1829
1598
|
if (enqueueResult.rejected) {
|
|
1830
1599
|
return new RouteResponse(
|
|
1831
1600
|
JSON.stringify({ accepted: false, error: "queue_full" }),
|
|
@@ -1945,37 +1714,33 @@ export async function handleSendMessage(
|
|
|
1945
1714
|
conversation.processing = true;
|
|
1946
1715
|
let cleanupDeferred = false;
|
|
1947
1716
|
try {
|
|
1948
|
-
const
|
|
1949
|
-
const imageSourcePaths: Record<string, string> = {};
|
|
1950
|
-
for (let i = 0; i < attachments.length; i++) {
|
|
1951
|
-
const a = attachments[i];
|
|
1952
|
-
if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
|
|
1953
|
-
imageSourcePaths[`${i}:${a.filename}`] = a.filePath;
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
const channelMeta = {
|
|
1957
|
-
...provenance,
|
|
1717
|
+
const slashMeta = {
|
|
1958
1718
|
userMessageChannel: sourceChannel,
|
|
1959
1719
|
assistantMessageChannel: sourceChannel,
|
|
1960
1720
|
userMessageInterface: sourceInterface,
|
|
1961
1721
|
assistantMessageInterface: sourceInterface,
|
|
1962
1722
|
...(body.automated === true ? { automated: true } : {}),
|
|
1963
|
-
...(Object.keys(imageSourcePaths).length > 0
|
|
1964
|
-
? { imageSourcePaths }
|
|
1965
|
-
: {}),
|
|
1966
1723
|
};
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
)
|
|
1975
|
-
|
|
1724
|
+
const persisted = await persistQueuedMessageBody(conversation, {
|
|
1725
|
+
content: rawContent,
|
|
1726
|
+
attachments,
|
|
1727
|
+
requestId: crypto.randomUUID(),
|
|
1728
|
+
metadata: slashMeta,
|
|
1729
|
+
clientMessageId,
|
|
1730
|
+
});
|
|
1731
|
+
if (persisted.deduplicated) {
|
|
1732
|
+
return {
|
|
1733
|
+
accepted: true,
|
|
1734
|
+
messageId: persisted.id,
|
|
1735
|
+
conversationId: mapping.conversationId,
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1976
1738
|
|
|
1739
|
+
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
1740
|
+
trustContext: conversation.trustContext,
|
|
1741
|
+
});
|
|
1977
1742
|
const assistantMsg = createAssistantMessage(slashResult.message);
|
|
1978
|
-
await addMessage(
|
|
1743
|
+
const persistedAssistant = await addMessage(
|
|
1979
1744
|
mapping.conversationId,
|
|
1980
1745
|
"assistant",
|
|
1981
1746
|
JSON.stringify(assistantMsg.content),
|
|
@@ -1983,15 +1748,6 @@ export async function handleSendMessage(
|
|
|
1983
1748
|
);
|
|
1984
1749
|
conversation.getMessages().push(assistantMsg);
|
|
1985
1750
|
|
|
1986
|
-
setConversationOriginChannelIfUnset(
|
|
1987
|
-
mapping.conversationId,
|
|
1988
|
-
sourceChannel,
|
|
1989
|
-
);
|
|
1990
|
-
setConversationOriginInterfaceIfUnset(
|
|
1991
|
-
mapping.conversationId,
|
|
1992
|
-
sourceInterface,
|
|
1993
|
-
);
|
|
1994
|
-
|
|
1995
1751
|
// Snapshot model info now so the deferred callback cannot observe
|
|
1996
1752
|
// a config change from a concurrent request.
|
|
1997
1753
|
const modelInfoEvent = isModelSlashCommand(rawContent)
|
|
@@ -2030,11 +1786,12 @@ export async function handleSendMessage(
|
|
|
2030
1786
|
text: message,
|
|
2031
1787
|
conversationId,
|
|
2032
1788
|
});
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
conversationId
|
|
2036
|
-
|
|
2037
|
-
|
|
1789
|
+
emitCannedMessageComplete(
|
|
1790
|
+
broadcastMessage,
|
|
1791
|
+
conversationId,
|
|
1792
|
+
persistedAssistant.id,
|
|
1793
|
+
);
|
|
1794
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
2038
1795
|
conversation.processing = false;
|
|
2039
1796
|
silentlyWithLog(conversation.drainQueue(), "slash-command queue drain");
|
|
2040
1797
|
}, 0);
|
|
@@ -2053,24 +1810,43 @@ export async function handleSendMessage(
|
|
|
2053
1810
|
|
|
2054
1811
|
if (slashResult.kind === "compact") {
|
|
2055
1812
|
conversation.processing = true;
|
|
2056
|
-
const
|
|
2057
|
-
const channelMeta = {
|
|
2058
|
-
...provenance,
|
|
1813
|
+
const slashMeta = {
|
|
2059
1814
|
userMessageChannel: sourceChannel,
|
|
2060
1815
|
assistantMessageChannel: sourceChannel,
|
|
2061
1816
|
userMessageInterface: sourceInterface,
|
|
2062
1817
|
assistantMessageInterface: sourceInterface,
|
|
2063
1818
|
};
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
1819
|
+
let persisted: Awaited<ReturnType<typeof persistQueuedMessageBody>>;
|
|
1820
|
+
try {
|
|
1821
|
+
persisted = await persistQueuedMessageBody(conversation, {
|
|
1822
|
+
content: rawContent,
|
|
1823
|
+
attachments,
|
|
1824
|
+
requestId: crypto.randomUUID(),
|
|
1825
|
+
metadata: slashMeta,
|
|
1826
|
+
clientMessageId,
|
|
1827
|
+
});
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
// The fire-and-forget compaction below owns clearing `processing`, but a
|
|
1830
|
+
// throw from this initial persist never reaches it — reset here so the
|
|
1831
|
+
// conversation isn't stranded in queued mode.
|
|
1832
|
+
conversation.processing = false;
|
|
1833
|
+
silentlyWithLog(conversation.drainQueue(), "compact-command queue drain");
|
|
1834
|
+
throw err;
|
|
1835
|
+
}
|
|
1836
|
+
if (persisted.deduplicated) {
|
|
1837
|
+
conversation.processing = false;
|
|
1838
|
+
silentlyWithLog(conversation.drainQueue(), "compact-dedup queue drain");
|
|
1839
|
+
return {
|
|
1840
|
+
accepted: true,
|
|
1841
|
+
messageId: persisted.id,
|
|
1842
|
+
conversationId: mapping.conversationId,
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
2072
1845
|
|
|
2073
1846
|
const conversationId = mapping.conversationId;
|
|
1847
|
+
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
1848
|
+
trustContext: conversation.trustContext,
|
|
1849
|
+
});
|
|
2074
1850
|
|
|
2075
1851
|
// Fire-and-forget: return 202 immediately, run compaction async.
|
|
2076
1852
|
// forceCompact() makes an LLM call that can exceed the client's
|
|
@@ -2085,7 +1861,7 @@ export async function handleSendMessage(
|
|
|
2085
1861
|
messageId: persisted.id,
|
|
2086
1862
|
clientMessageId,
|
|
2087
1863
|
});
|
|
2088
|
-
publishConversationMessagesChanged(conversationId);
|
|
1864
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
2089
1865
|
conversation.emitActivityState(
|
|
2090
1866
|
"thinking",
|
|
2091
1867
|
"context_compacting",
|
|
@@ -2097,7 +1873,7 @@ export async function handleSendMessage(
|
|
|
2097
1873
|
const responseText = formatCompactResult(result);
|
|
2098
1874
|
|
|
2099
1875
|
const assistantMsg = createAssistantMessage(responseText);
|
|
2100
|
-
await addMessage(
|
|
1876
|
+
const persistedAssistant = await addMessage(
|
|
2101
1877
|
conversationId,
|
|
2102
1878
|
"assistant",
|
|
2103
1879
|
JSON.stringify(assistantMsg.content),
|
|
@@ -2111,11 +1887,15 @@ export async function handleSendMessage(
|
|
|
2111
1887
|
text: responseText,
|
|
2112
1888
|
conversationId,
|
|
2113
1889
|
});
|
|
2114
|
-
|
|
2115
|
-
|
|
1890
|
+
emitCannedMessageComplete(
|
|
1891
|
+
broadcastMessage,
|
|
1892
|
+
conversationId,
|
|
1893
|
+
persistedAssistant.id,
|
|
1894
|
+
);
|
|
1895
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
2116
1896
|
} catch (err) {
|
|
2117
1897
|
if (assistantMessagePersisted) {
|
|
2118
|
-
publishConversationMessagesChanged(conversationId);
|
|
1898
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
2119
1899
|
}
|
|
2120
1900
|
log.error({ err, conversationId }, "Compact command failed");
|
|
2121
1901
|
broadcastMessage({
|
|
@@ -2143,93 +1923,115 @@ export async function handleSendMessage(
|
|
|
2143
1923
|
|
|
2144
1924
|
if (slashResult.kind === "clean") {
|
|
2145
1925
|
conversation.processing = true;
|
|
2146
|
-
const provenance = provenanceFromTrustContext(conversation.trustContext);
|
|
2147
|
-
const channelMeta = {
|
|
2148
|
-
...provenance,
|
|
2149
|
-
userMessageChannel: sourceChannel,
|
|
2150
|
-
assistantMessageChannel: sourceChannel,
|
|
2151
|
-
userMessageInterface: sourceInterface,
|
|
2152
|
-
assistantMessageInterface: sourceInterface,
|
|
2153
|
-
};
|
|
2154
|
-
const cleanMsg = createUserMessage(rawContent, attachments);
|
|
2155
|
-
const persisted = await addMessage(
|
|
2156
|
-
mapping.conversationId,
|
|
2157
|
-
"user",
|
|
2158
|
-
JSON.stringify(cleanMsg.content),
|
|
2159
|
-
channelMeta,
|
|
2160
|
-
);
|
|
2161
|
-
conversation.getMessages().push(cleanMsg);
|
|
2162
|
-
|
|
2163
1926
|
const conversationId = mapping.conversationId;
|
|
2164
|
-
|
|
2165
|
-
|
|
1927
|
+
// Outer try/finally guarantees the processing flag is cleared (and the
|
|
1928
|
+
// queue drained) on every failure path — including a throw from the
|
|
1929
|
+
// initial user-message persist below, which would otherwise leave the
|
|
1930
|
+
// conversation stuck in queued mode indefinitely.
|
|
2166
1931
|
try {
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
1932
|
+
const slashMeta = {
|
|
1933
|
+
userMessageChannel: sourceChannel,
|
|
1934
|
+
assistantMessageChannel: sourceChannel,
|
|
1935
|
+
userMessageInterface: sourceInterface,
|
|
1936
|
+
assistantMessageInterface: sourceInterface,
|
|
1937
|
+
};
|
|
1938
|
+
const persisted = await persistQueuedMessageBody(conversation, {
|
|
1939
|
+
content: rawContent,
|
|
1940
|
+
attachments,
|
|
1941
|
+
requestId: crypto.randomUUID(),
|
|
1942
|
+
metadata: slashMeta,
|
|
2172
1943
|
clientMessageId,
|
|
2173
1944
|
});
|
|
2174
|
-
|
|
1945
|
+
if (persisted.deduplicated) {
|
|
1946
|
+
return {
|
|
1947
|
+
accepted: true,
|
|
1948
|
+
messageId: persisted.id,
|
|
1949
|
+
conversationId,
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
2175
1952
|
|
|
2176
|
-
const
|
|
2177
|
-
|
|
1953
|
+
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
1954
|
+
trustContext: conversation.trustContext,
|
|
1955
|
+
});
|
|
1956
|
+
let assistantMessagePersisted = false;
|
|
1957
|
+
try {
|
|
1958
|
+
broadcastMessage({
|
|
1959
|
+
type: "user_message_echo",
|
|
1960
|
+
text: rawContent,
|
|
1961
|
+
conversationId,
|
|
1962
|
+
messageId: persisted.id,
|
|
1963
|
+
clientMessageId,
|
|
1964
|
+
});
|
|
1965
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
2178
1966
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
conversationId,
|
|
2182
|
-
"assistant",
|
|
2183
|
-
JSON.stringify(assistantMsg.content),
|
|
2184
|
-
channelMeta,
|
|
2185
|
-
);
|
|
2186
|
-
assistantMessagePersisted = true;
|
|
2187
|
-
conversation.getMessages().push(assistantMsg);
|
|
1967
|
+
const result = await conversation.forceClean();
|
|
1968
|
+
const responseText = formatCleanResult(result);
|
|
2188
1969
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
1970
|
+
const assistantMsg = createAssistantMessage(responseText);
|
|
1971
|
+
const persistedAssistant = await addMessage(
|
|
1972
|
+
conversationId,
|
|
1973
|
+
"assistant",
|
|
1974
|
+
JSON.stringify(assistantMsg.content),
|
|
1975
|
+
channelMeta,
|
|
1976
|
+
);
|
|
1977
|
+
assistantMessagePersisted = true;
|
|
1978
|
+
conversation.getMessages().push(assistantMsg);
|
|
1979
|
+
|
|
1980
|
+
broadcastMessage({
|
|
1981
|
+
type: "assistant_text_delta",
|
|
1982
|
+
text: responseText,
|
|
1983
|
+
conversationId,
|
|
1984
|
+
});
|
|
1985
|
+
emitCannedMessageComplete(
|
|
1986
|
+
broadcastMessage,
|
|
1987
|
+
conversationId,
|
|
1988
|
+
persistedAssistant.id,
|
|
1989
|
+
);
|
|
1990
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1991
|
+
} catch (err) {
|
|
1992
|
+
if (assistantMessagePersisted) {
|
|
1993
|
+
publishConversationMessagesChanged(conversationId, originClientId);
|
|
1994
|
+
}
|
|
1995
|
+
log.error({ err, conversationId }, "Clean command failed");
|
|
1996
|
+
broadcastMessage({
|
|
1997
|
+
type: "conversation_error",
|
|
1998
|
+
conversationId,
|
|
1999
|
+
code: "UNKNOWN",
|
|
2000
|
+
userMessage: `Clean failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2001
|
+
retryable: true,
|
|
2002
|
+
});
|
|
2199
2003
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2004
|
+
|
|
2005
|
+
return {
|
|
2006
|
+
accepted: true,
|
|
2007
|
+
messageId: persisted.id,
|
|
2203
2008
|
conversationId,
|
|
2204
|
-
|
|
2205
|
-
userMessage: `Clean failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2206
|
-
retryable: true,
|
|
2207
|
-
});
|
|
2009
|
+
};
|
|
2208
2010
|
} finally {
|
|
2209
2011
|
conversation.processing = false;
|
|
2210
2012
|
silentlyWithLog(conversation.drainQueue(), "clean-command queue drain");
|
|
2211
2013
|
}
|
|
2212
|
-
|
|
2213
|
-
return {
|
|
2214
|
-
accepted: true,
|
|
2215
|
-
messageId: persisted.id,
|
|
2216
|
-
conversationId,
|
|
2217
|
-
};
|
|
2218
2014
|
}
|
|
2219
2015
|
|
|
2220
2016
|
const resolvedContent = slashResult.content;
|
|
2221
2017
|
|
|
2222
2018
|
const requestId = crypto.randomUUID();
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2019
|
+
const persistResult = await conversation.persistUserMessage({
|
|
2020
|
+
content: resolvedContent,
|
|
2021
|
+
attachments,
|
|
2022
|
+
requestId,
|
|
2023
|
+
metadata: body.automated === true ? { automated: true } : undefined,
|
|
2024
|
+
clientMessageId,
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
const messageId = persistResult.id;
|
|
2028
|
+
|
|
2029
|
+
if (persistResult.deduplicated) {
|
|
2030
|
+
return {
|
|
2031
|
+
accepted: true,
|
|
2032
|
+
messageId,
|
|
2033
|
+
conversationId: mapping.conversationId,
|
|
2034
|
+
};
|
|
2233
2035
|
}
|
|
2234
2036
|
|
|
2235
2037
|
broadcastMessage({
|
|
@@ -2240,7 +2042,7 @@ export async function handleSendMessage(
|
|
|
2240
2042
|
requestId,
|
|
2241
2043
|
clientMessageId,
|
|
2242
2044
|
});
|
|
2243
|
-
publishConversationMessagesChanged(mapping.conversationId);
|
|
2045
|
+
publishConversationMessagesChanged(mapping.conversationId, originClientId);
|
|
2244
2046
|
|
|
2245
2047
|
// Fire-and-forget the agent loop; events flow to the hub via broadcastMessage.
|
|
2246
2048
|
conversation
|
|
@@ -2285,14 +2087,25 @@ async function generateLlmSuggestion(
|
|
|
2285
2087
|
? escapeXmlContent(priorUserText)
|
|
2286
2088
|
: priorUserText;
|
|
2287
2089
|
|
|
2288
|
-
const systemPrompt =
|
|
2289
|
-
"You generate short, casual reply suggestions a user might type next in a chat.
|
|
2090
|
+
const systemPrompt = [
|
|
2091
|
+
"You generate short, casual reply suggestions a user might type next in a chat.",
|
|
2092
|
+
"Match the tone and register of the preceding conversation.",
|
|
2093
|
+
"",
|
|
2094
|
+
"CRITICAL — write from the USER'S perspective only, NEVER from the assistant's:",
|
|
2095
|
+
"- The suggestion is what the USER will type into the chat input",
|
|
2096
|
+
'- Use first-person "I" only if the user has used it in their prior messages',
|
|
2097
|
+
'- NEVER start with phrases like "I can help", "Here\'s what", "Let me", "I\'d suggest" — those are assistant-voice',
|
|
2098
|
+
"- Think: if you were the user reading the assistant's reply, what question or follow-up would you ask next?",
|
|
2099
|
+
"",
|
|
2100
|
+
"Output only the reply text inside the requested tags — no preamble, no commentary.",
|
|
2101
|
+
].join("\n");
|
|
2290
2102
|
|
|
2291
2103
|
const userPrompt =
|
|
2292
2104
|
`Here is the end of a conversation:\n\n` +
|
|
2293
2105
|
`<user_message>${truncatedUser ?? "(no prior user message)"}</user_message>\n` +
|
|
2294
2106
|
`<assistant_message>${truncatedAssistant}</assistant_message>\n\n` +
|
|
2295
|
-
`Write the
|
|
2107
|
+
`Write the USER'S next reply — what the user would type. Focus on the LAST question or call-to-action in the assistant message. Keep it short (under 15 words), casual, and in the user's voice. ` +
|
|
2108
|
+
`The reply must read as something typed BY the user, not something the assistant would say. Respond in this exact format:\n\n` +
|
|
2296
2109
|
`<reply>YOUR_REPLY_HERE</reply>`;
|
|
2297
2110
|
|
|
2298
2111
|
// Single user message only — no assistant-role prefill. Anthropic
|
|
@@ -2368,14 +2181,27 @@ export async function handleGetSuggestion(
|
|
|
2368
2181
|
};
|
|
2369
2182
|
|
|
2370
2183
|
const conversationKey = queryParams?.conversationKey;
|
|
2371
|
-
|
|
2372
|
-
|
|
2184
|
+
const conversationId = queryParams?.conversationId;
|
|
2185
|
+
if (!conversationKey && !conversationId) {
|
|
2186
|
+
throw new BadRequestError(
|
|
2187
|
+
"conversationKey or conversationId query parameter is required",
|
|
2188
|
+
);
|
|
2373
2189
|
}
|
|
2374
2190
|
|
|
2375
|
-
|
|
2376
|
-
if (
|
|
2191
|
+
let resolvedConversationId: string | undefined;
|
|
2192
|
+
if (conversationId) {
|
|
2193
|
+
resolvedConversationId = conversationId;
|
|
2194
|
+
} else if (conversationKey) {
|
|
2195
|
+
const mapping = getConversationByKey(conversationKey);
|
|
2196
|
+
if (mapping) {
|
|
2197
|
+
resolvedConversationId = mapping.conversationId;
|
|
2198
|
+
} else if (getConversation(conversationKey)) {
|
|
2199
|
+
resolvedConversationId = conversationKey;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
if (!resolvedConversationId) return noSuggestion;
|
|
2377
2203
|
|
|
2378
|
-
const rawMessages = getMessages(
|
|
2204
|
+
const rawMessages = getMessages(resolvedConversationId);
|
|
2379
2205
|
if (rawMessages.length === 0) return noSuggestion;
|
|
2380
2206
|
|
|
2381
2207
|
// Staleness check: compare requested messageId against the latest
|
|
@@ -2521,6 +2347,47 @@ function handleSearchConversations({
|
|
|
2521
2347
|
return { query, results };
|
|
2522
2348
|
}
|
|
2523
2349
|
|
|
2350
|
+
// ---------------------------------------------------------------------------
|
|
2351
|
+
// Metadata helpers
|
|
2352
|
+
// ---------------------------------------------------------------------------
|
|
2353
|
+
|
|
2354
|
+
/**
|
|
2355
|
+
* Assemble the standard channel metadata object for message persistence.
|
|
2356
|
+
*
|
|
2357
|
+
* Combines provenance (trust context), channel/interface routing, and
|
|
2358
|
+
* optional per-message fields (automated flag, image source paths) into the
|
|
2359
|
+
* Record that `addMessage` stores in the `metadata` column.
|
|
2360
|
+
*/
|
|
2361
|
+
function buildChannelMetadata(
|
|
2362
|
+
sourceChannel: string,
|
|
2363
|
+
sourceInterface: string,
|
|
2364
|
+
opts?: {
|
|
2365
|
+
trustContext?: Parameters<typeof provenanceFromTrustContext>[0];
|
|
2366
|
+
provenanceOverride?: Record<string, unknown>;
|
|
2367
|
+
automated?: boolean;
|
|
2368
|
+
attachments?: ReadonlyArray<{
|
|
2369
|
+
filename: string;
|
|
2370
|
+
mimeType: string;
|
|
2371
|
+
filePath?: string;
|
|
2372
|
+
}>;
|
|
2373
|
+
},
|
|
2374
|
+
): Record<string, unknown> {
|
|
2375
|
+
const provenance =
|
|
2376
|
+
opts?.provenanceOverride ?? provenanceFromTrustContext(opts?.trustContext);
|
|
2377
|
+
const imageSourcePaths = opts?.attachments
|
|
2378
|
+
? extractImageSourcePaths(opts.attachments)
|
|
2379
|
+
: undefined;
|
|
2380
|
+
return {
|
|
2381
|
+
...provenance,
|
|
2382
|
+
userMessageChannel: sourceChannel,
|
|
2383
|
+
assistantMessageChannel: sourceChannel,
|
|
2384
|
+
userMessageInterface: sourceInterface,
|
|
2385
|
+
assistantMessageInterface: sourceInterface,
|
|
2386
|
+
...(opts?.automated ? { automated: true } : {}),
|
|
2387
|
+
...(imageSourcePaths ? { imageSourcePaths } : {}),
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2524
2391
|
// ---------------------------------------------------------------------------
|
|
2525
2392
|
// Module-level state
|
|
2526
2393
|
// ---------------------------------------------------------------------------
|
|
@@ -2629,10 +2496,31 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2629
2496
|
description:
|
|
2630
2497
|
"Return an LLM-generated follow-up suggestion for the most recent assistant message.",
|
|
2631
2498
|
tags: ["messages"],
|
|
2499
|
+
queryParams: [
|
|
2500
|
+
{
|
|
2501
|
+
name: "conversationId",
|
|
2502
|
+
type: "string",
|
|
2503
|
+
description:
|
|
2504
|
+
"Conversation ID to fetch a suggestion for. Either this or conversationKey is required.",
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
name: "conversationKey",
|
|
2508
|
+
type: "string",
|
|
2509
|
+
description:
|
|
2510
|
+
"Legacy conversation key. Either this or conversationId is required.",
|
|
2511
|
+
},
|
|
2512
|
+
{
|
|
2513
|
+
name: "messageId",
|
|
2514
|
+
type: "string",
|
|
2515
|
+
description:
|
|
2516
|
+
"Optional. Latest assistant message ID the client has seen — used to detect staleness.",
|
|
2517
|
+
},
|
|
2518
|
+
],
|
|
2632
2519
|
responseBody: z.object({
|
|
2633
|
-
suggestion: z.string(),
|
|
2634
|
-
messageId: z.string(),
|
|
2520
|
+
suggestion: z.string().nullable(),
|
|
2521
|
+
messageId: z.string().nullable(),
|
|
2635
2522
|
source: z.string(),
|
|
2523
|
+
stale: z.boolean().optional(),
|
|
2636
2524
|
}),
|
|
2637
2525
|
handler: async (args) =>
|
|
2638
2526
|
handleGetSuggestion(args, {
|