@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f
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/ARCHITECTURE.md +94 -91
- package/Dockerfile +5 -16
- package/README.md +7 -10
- package/bun.lock +0 -4
- package/docs/architecture/security.md +20 -20
- package/docs/stt-provider-onboarding.md +4 -5
- package/docs/trusted-contact-access.md +7 -7
- package/eslint-rules/cli-no-daemon-internals.js +4 -0
- package/eslint.config.mjs +7 -1
- package/knip.json +0 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
- package/openapi.yaml +1073 -225
- package/package.json +1 -4
- package/scripts/test.sh +35 -13
- package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
- package/src/__tests__/acp-session.test.ts +4 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
- package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
- package/src/__tests__/app-source-watcher.test.ts +11 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
- package/src/__tests__/assistant-attachments.test.ts +62 -4
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
- package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
- package/src/__tests__/attachments-store.test.ts +3 -0
- package/src/__tests__/background-shell-bash.test.ts +34 -0
- package/src/__tests__/background-shell-host-bash.test.ts +12 -0
- package/src/__tests__/background-tool-registry.test.ts +97 -0
- package/src/__tests__/background-tool-routes.test.ts +72 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/btw-routes.test.ts +57 -3
- package/src/__tests__/call-controller.test.ts +474 -84
- package/src/__tests__/call-domain.test.ts +48 -1
- package/src/__tests__/call-pointer-messages.test.ts +57 -19
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +15 -14
- package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
- package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
- package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
- package/src/__tests__/call-setup-flow.test.ts +311 -0
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -7
- package/src/__tests__/channel-readiness-service.test.ts +0 -6
- package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
- package/src/__tests__/channel-retry-sweep.test.ts +76 -64
- package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
- package/src/__tests__/clear-all-lexical.test.ts +90 -0
- package/src/__tests__/compactor-media-strip.test.ts +177 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +17 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
- package/src/__tests__/contacts-relay-reads.test.ts +188 -23
- package/src/__tests__/contacts-tools.test.ts +42 -1
- package/src/__tests__/contacts-write.test.ts +181 -0
- package/src/__tests__/context-search-conversations-source.test.ts +220 -81
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +39 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
- package/src/__tests__/conversation-clear-safety.test.ts +0 -1
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -0
- package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
- package/src/__tests__/conversation-error.test.ts +0 -18
- package/src/__tests__/conversation-fork-crud.test.ts +67 -0
- package/src/__tests__/conversation-history-web-search.test.ts +7 -137
- package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
- package/src/__tests__/conversation-process-callsite.test.ts +37 -0
- package/src/__tests__/conversation-queue.test.ts +149 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
- package/src/__tests__/conversation-skill-tools.test.ts +73 -0
- package/src/__tests__/conversation-wipe.test.ts +20 -5
- package/src/__tests__/credential-prompt-route.test.ts +69 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -3
- package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
- package/src/__tests__/edit-propagation.test.ts +56 -2
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
- package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
- package/src/__tests__/guardian-question-mode.test.ts +57 -0
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/guardian-wait-controller.test.ts +619 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
- package/src/__tests__/heic-fixture.ts +55 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
- package/src/__tests__/http-conversation-lineage.test.ts +6 -1
- package/src/__tests__/image-conversion.test.ts +170 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
- package/src/__tests__/introduction-card-resolver.test.ts +354 -0
- package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
- package/src/__tests__/invite-routes-http.test.ts +246 -420
- package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
- package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
- package/src/__tests__/list-messages-attachments.test.ts +159 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
- package/src/__tests__/list-messages-queued.test.ts +25 -0
- package/src/__tests__/managed-store.test.ts +129 -1
- package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
- package/src/__tests__/media-stream-output.test.ts +350 -19
- package/src/__tests__/media-stream-server-integration.test.ts +943 -169
- package/src/__tests__/media-stream-stt-session.test.ts +498 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +19 -0
- package/src/__tests__/non-member-access-request.test.ts +349 -3
- package/src/__tests__/notification-decision-fallback.test.ts +104 -8
- package/src/__tests__/notification-decision-strategy.test.ts +57 -8
- package/src/__tests__/onboarding-template-contract.test.ts +27 -43
- package/src/__tests__/openai-provider.test.ts +33 -0
- package/src/__tests__/openai-responses-provider.test.ts +40 -0
- package/src/__tests__/persistence-layering-guard.test.ts +29 -20
- package/src/__tests__/persona-resolver.test.ts +56 -2
- package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
- package/src/__tests__/phone.test.ts +1 -15
- package/src/__tests__/plugin-bootstrap.test.ts +25 -196
- package/src/__tests__/plugin-disabled-state.test.ts +33 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
- package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
- package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
- package/src/__tests__/plugin-pipeline.test.ts +63 -1
- package/src/__tests__/plugin-route-contribution.test.ts +62 -115
- package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
- package/src/__tests__/plugin-types.test.ts +0 -1
- package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
- package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
- package/src/__tests__/public-ingress-urls.test.ts +0 -48
- package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
- package/src/__tests__/resolve-trust-class.test.ts +28 -10
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
- package/src/__tests__/schedule-retry.test.ts +38 -14
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
- package/src/__tests__/schedule-routes.test.ts +14 -6
- package/src/__tests__/schedule-store.test.ts +3 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
- package/src/__tests__/scheduler-recurrence.test.ts +57 -107
- package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
- package/src/__tests__/scheduler-wake.test.ts +13 -23
- package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/shell-observability.test.ts +1 -0
- package/src/__tests__/skill-boundary-guard.test.ts +3 -4
- package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
- package/src/__tests__/skill-script-runner-host.test.ts +53 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
- package/src/__tests__/skill-tool-factory.test.ts +73 -1
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
- package/src/__tests__/slack-inbound-verification.test.ts +36 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
- package/src/__tests__/stt-hints.test.ts +2 -256
- package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
- package/src/__tests__/subagent-persistence.test.ts +122 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +99 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
- package/src/__tests__/system-prompt.test.ts +17 -118
- package/src/__tests__/task-scheduler.test.ts +68 -89
- package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
- package/src/__tests__/telephony-tts-guard.test.ts +538 -0
- package/src/__tests__/title-generate-hook.test.ts +16 -0
- package/src/__tests__/tool-approval-handler.test.ts +87 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
- package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
- package/src/__tests__/trust-context-guards.test.ts +21 -10
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
- package/src/__tests__/twilio-provider.test.ts +17 -17
- package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
- package/src/__tests__/twilio-routes.test.ts +257 -387
- package/src/__tests__/twilio-validation.test.ts +1 -1
- package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
- package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
- package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
- package/src/__tests__/voice-quality.test.ts +2 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
- package/src/__tests__/voice-session-bridge.test.ts +1 -10
- package/src/__tests__/workspace-git-service.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
- package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
- package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
- package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
- package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
- package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
- package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
- package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
- package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
- package/src/acp/__tests__/session-manager.test.ts +6 -0
- package/src/acp/agent-process.test.ts +116 -0
- package/src/acp/agent-process.ts +71 -0
- package/src/acp/auto-install.ts +12 -8
- package/src/acp/failure-error.test.ts +109 -0
- package/src/acp/failure-error.ts +111 -0
- package/src/acp/prepare-agent-env.ts +0 -12
- package/src/acp/resolve-agent.test.ts +6 -45
- package/src/acp/resolve-agent.ts +2 -4
- package/src/acp/session-manager.test.ts +312 -0
- package/src/acp/session-manager.ts +102 -46
- package/src/agent/ax-tree-compaction.test.ts +4 -1
- package/src/agent/image-optimize.ts +17 -127
- package/src/agent/loop.ts +45 -192
- package/src/api/events/background-tool-completed.test.ts +51 -0
- package/src/api/events/background-tool-completed.ts +29 -0
- package/src/api/events/background-tool-started.test.ts +36 -0
- package/src/api/events/background-tool-started.ts +28 -0
- package/src/api/events/conversation-error.ts +0 -1
- package/src/api/events/open-panel.ts +33 -0
- package/src/api/events/subagent-status-changed.ts +4 -1
- package/src/api/index.ts +19 -0
- package/src/api/responses/conversation-message.ts +34 -0
- package/src/approvals/guardian-decision-primitive.ts +74 -24
- package/src/approvals/guardian-request-resolvers.ts +450 -122
- package/src/apps/app-store.ts +1 -0
- package/src/apps/shared-app-links-store.ts +1 -0
- package/src/avatar/identity-avatar.ts +3 -7
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
- package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
- package/src/calls/call-controller.ts +134 -48
- package/src/calls/call-domain.ts +18 -42
- package/src/calls/call-pointer-messages.ts +51 -67
- package/src/calls/call-setup-flow-types.ts +76 -0
- package/src/calls/call-setup-flow.ts +1743 -0
- package/src/calls/call-speech-output.ts +87 -23
- package/src/calls/call-store.ts +4 -0
- package/src/calls/call-transport.ts +22 -36
- package/src/calls/fish-audio-client.ts +3 -0
- package/src/calls/gateway-invite-reader.ts +98 -0
- package/src/calls/guardian-wait-controller.ts +451 -0
- package/src/calls/media-stream-audio-transcode.ts +82 -2
- package/src/calls/media-stream-output.ts +142 -36
- package/src/calls/media-stream-server.ts +297 -178
- package/src/calls/media-stream-stt-session.ts +393 -50
- package/src/calls/media-turn-detector.ts +4 -6
- package/src/calls/relay-access-wait.ts +3 -4
- package/src/calls/relay-setup-router.ts +22 -43
- package/src/calls/relay-verification.ts +17 -24
- package/src/calls/resolve-call-tts-provider.ts +96 -29
- package/src/calls/stt-hints.ts +24 -87
- package/src/calls/telephony-credential-preflight.ts +159 -0
- package/src/calls/telephony-tts-capability.ts +183 -0
- package/src/calls/tts-call-strategy.ts +18 -20
- package/src/calls/twilio-provider.ts +3 -2
- package/src/calls/twilio-routes.ts +47 -351
- package/src/calls/types.ts +5 -3
- package/src/calls/voice-ingress-preflight.ts +9 -0
- package/src/calls/voice-quality.ts +13 -18
- package/src/calls/voice-session-bridge.ts +25 -35
- package/src/channels/__tests__/types.test.ts +28 -0
- package/src/channels/channel-verification-sessions.ts +7 -2
- package/src/channels/config.ts +0 -42
- package/src/channels/slack-thread-store.ts +12 -0
- package/src/channels/types.ts +23 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/schedules.test.ts +83 -34
- package/src/cli/commands/__tests__/status.test.ts +42 -2
- package/src/cli/commands/contacts.ts +2 -2
- package/src/cli/commands/credentials.ts +17 -0
- package/src/cli/commands/monitoring.ts +191 -0
- package/src/cli/commands/schedules-worker.ts +175 -0
- package/src/cli/commands/schedules.ts +169 -43
- package/src/cli/commands/status.ts +12 -1
- package/src/cli/commands/tools.ts +5 -1
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
- package/src/cli/lib/search-plugins.ts +6 -0
- package/src/cli/lib/toggle-plugin.ts +21 -2
- package/src/cli/program.ts +3 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +13 -45
- package/src/config/bundled-skills/acp/TOOLS.json +3 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
- package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/loader.ts +11 -0
- package/src/config/schema.ts +9 -1
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
- package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
- package/src/config/schemas/call-site-catalog.ts +0 -12
- package/src/config/schemas/calls.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +1 -1
- package/src/config/schemas/llm.ts +0 -2
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/config/schemas/memory-v3.ts +82 -0
- package/src/config/schemas/monitoring.ts +61 -0
- package/src/config/schemas/schedules.ts +35 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills-plugin-filter.test.ts +96 -0
- package/src/config/skills.ts +26 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
- package/src/contacts/canonical-guardian-store.ts +5 -0
- package/src/contacts/contact-store.ts +81 -35
- package/src/contacts/contacts-write.ts +50 -25
- package/src/contacts/member-write-relay.ts +115 -31
- package/src/contacts/types.ts +0 -8
- package/src/context/compactor.ts +29 -15
- package/src/context/outbound-sanitize.ts +202 -0
- package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
- package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
- package/src/daemon/abort-watchdog.ts +7 -0
- package/src/daemon/app-source-watcher.ts +9 -0
- package/src/daemon/assistant-attachments.ts +42 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
- package/src/daemon/conversation-agent-loop.ts +61 -68
- package/src/daemon/conversation-error.ts +1 -15
- package/src/daemon/conversation-history.ts +32 -236
- package/src/daemon/conversation-initial-prompt.ts +77 -0
- package/src/daemon/conversation-lifecycle.ts +25 -1
- package/src/daemon/conversation-messaging.ts +2 -2
- package/src/daemon/conversation-plugin-scope.ts +38 -0
- package/src/daemon/conversation-process.ts +50 -14
- package/src/daemon/conversation-runtime-assembly.ts +36 -6
- package/src/daemon/conversation-skill-tools.ts +19 -3
- package/src/daemon/conversation-store.ts +3 -3
- package/src/daemon/conversation-surfaces.ts +171 -0
- package/src/daemon/conversation-tool-setup.test.ts +95 -0
- package/src/daemon/conversation-tool-setup.ts +116 -1
- package/src/daemon/conversation-turn-finalize.ts +211 -0
- package/src/daemon/conversation.ts +92 -1
- package/src/daemon/disk-pressure-policy.ts +2 -1
- package/src/daemon/event-loop-watchdog.ts +22 -6
- package/src/daemon/external-plugins-bootstrap.ts +47 -134
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
- package/src/daemon/handlers/config-a2a.ts +7 -14
- package/src/daemon/handlers/config-channels.ts +2 -10
- package/src/daemon/handlers/conversation-history.ts +3 -1
- package/src/daemon/handlers/conversations.ts +0 -52
- package/src/daemon/handlers/shared.ts +4 -24
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +59 -472
- package/src/daemon/message-protocol.ts +3 -3
- package/src/daemon/message-provenance.ts +12 -14
- package/src/daemon/message-types/background-tools.ts +28 -0
- package/src/daemon/message-types/contacts.ts +2 -10
- package/src/daemon/message-types/conversations.ts +10 -0
- package/src/daemon/message-types/integrations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pointer-conversation-trust.ts +23 -9
- package/src/daemon/pointer-turn-runner.ts +174 -0
- package/src/daemon/providers-setup.ts +16 -0
- package/src/daemon/shutdown-handlers.ts +139 -47
- package/src/daemon/skill-memory-refresh.ts +2 -2
- package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
- package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
- package/src/daemon/tool-setup-types.ts +8 -0
- package/src/daemon/trust-context.ts +21 -6
- package/src/daemon/turn-latency-tracker.test.ts +93 -0
- package/src/daemon/turn-latency-tracker.ts +38 -0
- package/src/daemon/wake-conversation-ops.ts +10 -8
- package/src/documents/document-comments-store.test.ts +7 -1
- package/src/documents/document-comments-store.ts +8 -0
- package/src/documents/document-store.ts +15 -0
- package/src/export/__tests__/transcript-formatter.test.ts +11 -0
- package/src/export/transcript-formatter.ts +49 -67
- package/src/home/__tests__/feed-writer.test.ts +247 -1
- package/src/home/conversation-starter-checkpoints.ts +1 -0
- package/src/home/feed-writer.ts +61 -0
- package/src/home/job-handlers/conversation-starters.ts +1 -0
- package/src/hooks/hook-loader.ts +12 -0
- package/src/hooks/registry.ts +32 -4
- package/src/inbound/public-ingress-urls.ts +0 -17
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
- package/src/ipc/assistant-server.ts +28 -7
- package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
- package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
- package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
- package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
- package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
- package/src/ipc/routes/db-proxy.ts +5 -2
- package/src/ipc/routes/invite-ipc-routes.ts +53 -39
- package/src/jobs/worker.ts +9 -2
- package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
- package/src/live-voice/live-voice-archive.ts +5 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
- package/src/messaging/providers/channel-transport.ts +6 -0
- package/src/messaging/providers/index.ts +3 -0
- package/src/messaging/providers/slack/api.test.ts +49 -1
- package/src/messaging/providers/slack/api.ts +110 -0
- package/src/messaging/providers/slack/send.test.ts +85 -0
- package/src/messaging/providers/slack/send.ts +100 -28
- package/src/messaging/providers/slack/transport.ts +7 -13
- package/src/messaging/providers/whatsapp/send.ts +29 -9
- package/src/monitoring/__tests__/control.test.ts +235 -0
- package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
- package/src/monitoring/control.ts +105 -0
- package/src/monitoring/resource-sampler.ts +258 -0
- package/src/monitoring/sample-ring-buffer.ts +108 -0
- package/src/monitoring/worker.ts +94 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/access-request-copy.ts +94 -23
- package/src/notifications/adapters/slack.ts +89 -21
- package/src/notifications/approval-card-builder.ts +33 -18
- package/src/notifications/approval-card-data.ts +48 -15
- package/src/notifications/broadcaster.ts +25 -16
- package/src/notifications/decision-engine.ts +131 -32
- package/src/notifications/guardian-question-mode.ts +58 -10
- package/src/permissions/approval-policy.ts +8 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/gateway-threshold-reader.ts +7 -6
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/secret-prompter.ts +11 -3
- package/src/permissions/types.ts +2 -2
- package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
- package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
- package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
- package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
- package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
- package/src/persistence/a2a-invite-store.ts +205 -0
- package/src/persistence/attachments-store.ts +94 -10
- package/src/persistence/checkpoints.ts +45 -0
- package/src/persistence/conversation-crud.ts +453 -260
- package/src/persistence/conversation-display-order-migration.ts +8 -2
- package/src/persistence/conversation-group-migration.test.ts +8 -0
- package/src/persistence/conversation-group-migration.ts +9 -0
- package/src/persistence/conversation-queries.ts +244 -125
- package/src/persistence/conversation-row-batch-delete.ts +9 -5
- package/src/persistence/conversation-search-lexical.ts +49 -0
- package/src/persistence/db-async-query.ts +42 -0
- package/src/persistence/db-connection.ts +13 -0
- package/src/persistence/delivery-channels.ts +0 -82
- package/src/persistence/delivery-crud.ts +27 -6
- package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
- package/src/persistence/embeddings/embedding-identity.ts +17 -5
- package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
- package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
- package/src/persistence/embeddings/qdrant-client.ts +81 -5
- package/src/persistence/group-crud.ts +13 -1
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
- package/src/persistence/job-handlers/cleanup.ts +57 -9
- package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
- package/src/persistence/job-handlers/message-lexical.ts +251 -0
- package/src/persistence/jobs-store.ts +102 -8
- package/src/persistence/jobs-worker.ts +68 -28
- package/src/persistence/llm-request-log-store.ts +35 -19
- package/src/persistence/llm-usage-store.ts +11 -0
- package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
- package/src/persistence/memory-lifecycle-hooks.ts +55 -1
- package/src/persistence/migrations/028-call-session-mode.ts +1 -1
- package/src/persistence/migrations/116-messages-fts.ts +8 -1
- package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
- package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
- package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
- package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
- package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
- package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
- package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
- package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
- package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
- package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
- package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
- package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
- package/src/persistence/raw-query.ts +50 -11
- package/src/persistence/schema/a2a.ts +14 -0
- package/src/persistence/schema/contacts.ts +0 -62
- package/src/persistence/schema/conversation-groups.ts +12 -0
- package/src/persistence/schema/conversations.ts +2 -0
- package/src/persistence/schema/documents.ts +65 -0
- package/src/persistence/schema/guardian.ts +4 -0
- package/src/persistence/schema/index.ts +4 -0
- package/src/persistence/schema/memory-injection.ts +62 -0
- package/src/persistence/schema/workflows.ts +55 -0
- package/src/persistence/slow-query-log.ts +437 -0
- package/src/persistence/slow-sync-log.ts +204 -0
- package/src/persistence/steps.ts +16 -0
- package/src/persistence/subagent-store.ts +125 -0
- package/src/persistence/worker-control.ts +45 -238
- package/src/plugin-api/event-hub-facade.ts +172 -0
- package/src/plugin-api/index.ts +9 -1
- package/src/plugin-api/types.ts +40 -9
- package/src/plugins/defaults/default-plugin-names.ts +60 -0
- package/src/plugins/defaults/index.ts +19 -29
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
- package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
- package/src/plugins/defaults/memory/config.ts +11 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
- package/src/plugins/defaults/memory/embeddings.ts +77 -0
- package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
- package/src/plugins/defaults/memory/frontmatter.ts +50 -0
- package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
- package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
- package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
- package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
- package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
- package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
- package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
- package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
- package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
- package/src/plugins/defaults/memory/graph/tools.ts +1 -1
- package/src/plugins/defaults/memory/hooks/init.ts +31 -5
- package/src/plugins/defaults/memory/indexer.ts +2 -3
- package/src/plugins/defaults/memory/injectors.ts +2 -2
- package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
- package/src/plugins/defaults/memory/job-handlers.ts +13 -12
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
- package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
- package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
- package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
- package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
- package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
- package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
- package/src/plugins/defaults/memory/search/semantic.ts +2 -2
- package/src/plugins/defaults/memory/startup.ts +244 -0
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
- package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
- package/src/plugins/defaults/memory/v2/activation.ts +2 -4
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
- package/src/plugins/defaults/memory/v2/migration.ts +2 -5
- package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
- package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
- package/src/plugins/defaults/memory/v2/router.ts +5 -5
- package/src/plugins/defaults/memory/v2/sim.ts +2 -4
- package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
- package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
- package/src/plugins/defaults/memory/v3/card.ts +1 -4
- package/src/plugins/defaults/memory/v3/dense.ts +60 -25
- package/src/plugins/defaults/memory/v3/edge.ts +1 -1
- package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
- package/src/plugins/defaults/memory/v3/gate.ts +158 -0
- package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
- package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
- package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
- package/src/plugins/defaults/memory/v3/prune.ts +2 -2
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
- package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
- package/src/plugins/injector-registry.ts +13 -2
- package/src/plugins/mtime-cache.ts +36 -47
- package/src/plugins/pipeline.ts +15 -1
- package/src/plugins/types.ts +14 -23
- package/src/prompts/__tests__/system-prompt.test.ts +170 -32
- package/src/prompts/persona-resolver.ts +13 -9
- package/src/prompts/system-prompt.ts +30 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -119
- package/src/prompts/templates/SOUL.md +43 -13
- package/src/prompts/templates/system-sections.ts +32 -27
- package/src/prompts/templates/users/default.md +19 -0
- package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +14 -1
- package/src/providers/openai/responses-provider.ts +18 -0
- package/src/providers/placeholder-sentinels.ts +18 -8
- package/src/providers/retry.ts +1 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
- package/src/providers/speech-to-text/provider-catalog.ts +0 -89
- package/src/providers/speech-to-text/resolve.ts +55 -0
- package/src/providers/tool-progress-events.test.ts +50 -0
- package/src/providers/tool-progress-events.ts +99 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/vellum-model-routing.test.ts +51 -0
- package/src/providers/vellum-model-routing.ts +84 -0
- package/src/runtime/AGENTS.md +5 -4
- package/src/runtime/__tests__/agent-wake.test.ts +158 -1
- package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
- package/src/runtime/access-request-helper.ts +154 -3
- package/src/runtime/actor-trust-resolver.ts +7 -21
- package/src/runtime/agent-wake.ts +41 -1
- package/src/runtime/assistant-event.ts +86 -12
- package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
- package/src/runtime/channel-approval-types.ts +52 -2
- package/src/runtime/channel-approvals.ts +23 -8
- package/src/runtime/channel-invite-transport.ts +2 -2
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-readiness-service.ts +3 -5
- package/src/runtime/channel-retry-sweep.ts +18 -24
- package/src/runtime/channel-verification-service.ts +9 -11
- package/src/runtime/finalize-event-delivery.ts +30 -16
- package/src/runtime/guardian-action-service.ts +10 -11
- package/src/runtime/guardian-reply-router.ts +122 -21
- package/src/runtime/http-server.ts +44 -101
- package/src/runtime/introduction-policy.test.ts +214 -0
- package/src/runtime/introduction-policy.ts +284 -0
- package/src/runtime/invite-service.ts +104 -361
- package/src/runtime/local-actor-identity.test.ts +38 -0
- package/src/runtime/local-actor-identity.ts +14 -0
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
- package/src/runtime/middleware/rate-limiter.ts +30 -0
- package/src/runtime/middleware/twilio-validation.ts +5 -7
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
- package/src/runtime/migrations/origin-mode.ts +6 -6
- package/src/runtime/pre-first-message-gate.ts +1 -0
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
- package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes.ts +15 -3
- package/src/runtime/routes/app-management-routes.ts +4 -7
- package/src/runtime/routes/approval-routes.ts +4 -0
- package/src/runtime/routes/attachment-routes.ts +54 -3
- package/src/runtime/routes/avatar-routes.ts +0 -4
- package/src/runtime/routes/background-tool-routes.ts +34 -5
- package/src/runtime/routes/btw-routes.ts +32 -4
- package/src/runtime/routes/channel-route-shared.ts +18 -14
- package/src/runtime/routes/channel-verification-routes.ts +0 -3
- package/src/runtime/routes/contact-routes.ts +298 -117
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +74 -37
- package/src/runtime/routes/conversation-query-routes.ts +5 -2
- package/src/runtime/routes/conversation-routes.ts +222 -82
- package/src/runtime/routes/conversations-import-routes.ts +8 -1
- package/src/runtime/routes/credential-prompt-routes.ts +20 -5
- package/src/runtime/routes/debug-routes.ts +1 -0
- package/src/runtime/routes/documents-routes.ts +5 -2
- package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
- package/src/runtime/routes/global-search-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +51 -0
- package/src/runtime/routes/home-feed-routes.ts +60 -1
- package/src/runtime/routes/identity-routes.ts +4 -87
- package/src/runtime/routes/inbound-message-handler.ts +82 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
- package/src/runtime/routes/internal-twilio-routes.ts +0 -15
- package/src/runtime/routes/messages-lexical-routes.ts +75 -0
- package/src/runtime/routes/migration-routes.ts +69 -6
- package/src/runtime/routes/monitoring-routes.ts +237 -0
- package/src/runtime/routes/plugins-routes.ts +329 -8
- package/src/runtime/routes/schedule-routes.ts +49 -8
- package/src/runtime/routes/schedule-worker-routes.ts +210 -0
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
- package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
- package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
- package/src/runtime/services/analyze-conversation.ts +14 -4
- package/src/runtime/services/conversation-serializer.ts +5 -0
- package/src/runtime/slack-no-response.ts +47 -0
- package/src/runtime/slack-reply-session.test.ts +633 -0
- package/src/runtime/slack-reply-session.ts +382 -0
- package/src/runtime/slack-task-progress.ts +95 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/tool-grant-request-helper.ts +17 -1
- package/src/runtime/trust-class.test.ts +141 -0
- package/src/runtime/trust-class.ts +121 -0
- package/src/runtime/trust-verdict-consumer.ts +7 -6
- package/src/schedule/__tests__/run-script.test.ts +21 -0
- package/src/schedule/__tests__/worker-control.test.ts +241 -0
- package/src/schedule/run-script.ts +13 -6
- package/src/schedule/schedule-usage-store.ts +1 -0
- package/src/schedule/scheduler.ts +178 -127
- package/src/schedule/worker-control.ts +118 -0
- package/src/schedule/worker.ts +131 -0
- package/src/skills/__tests__/categories-cache.test.ts +38 -0
- package/src/skills/categories-cache.ts +40 -5
- package/src/skills/managed-store.ts +41 -6
- package/src/skills/validate-input.ts +47 -0
- package/src/stt/types.ts +5 -9
- package/src/subagent/manager.ts +267 -39
- package/src/subagent/types.ts +6 -2
- package/src/subagent/validate-allowlists.ts +65 -0
- package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
- package/src/telemetry/watchdog-direct-emit.ts +89 -0
- package/src/tools/acp/list-agents.test.ts +0 -6
- package/src/tools/background-tool-registry.ts +61 -0
- package/src/tools/document/document-comment-tool.test.ts +4 -0
- package/src/tools/host-terminal/host-shell.test.ts +397 -0
- package/src/tools/host-terminal/host-shell.ts +203 -14
- package/src/tools/skills/find-similar-skills.test.ts +152 -1
- package/src/tools/skills/find-similar-skills.ts +30 -4
- package/src/tools/skills/load.ts +36 -0
- package/src/tools/skills/scaffold-managed.ts +66 -34
- package/src/tools/skills/skill-script-runner.ts +17 -0
- package/src/tools/skills/skill-tool-factory.ts +5 -1
- package/src/tools/terminal/safe-env.ts +3 -0
- package/src/tools/terminal/shell.test.ts +177 -0
- package/src/tools/terminal/shell.ts +98 -4
- package/src/tools/tool-approval-handler.ts +106 -29
- package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
- package/src/tools/types.ts +29 -17
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tts/__tests__/provider-adapters.test.ts +37 -0
- package/src/tts/__tests__/provider-catalog.test.ts +21 -0
- package/src/tts/provider-catalog.ts +40 -9
- package/src/tts/providers/fish-audio-provider.ts +19 -10
- package/src/tts/types.ts +3 -4
- package/src/util/__tests__/process-tree.test.ts +10 -2
- package/src/util/__tests__/text-spacing.test.ts +53 -0
- package/src/util/cgroup-memory.ts +142 -0
- package/src/util/image-conversion.ts +281 -0
- package/src/util/phone.ts +1 -8
- package/src/util/platform.ts +33 -0
- package/src/util/process-tree.ts +10 -2
- package/src/util/text-spacing.ts +53 -0
- package/src/util/worker-process.ts +271 -0
- package/src/workflows/journal-store.ts +16 -1
- package/src/workflows/library.test.ts +35 -20
- package/src/workflows/library.ts +105 -66
- package/src/workspace/git-service.ts +1 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
- package/src/workspace/migrations/118-seed-now-md.ts +64 -0
- package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
- package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
- package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
- package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
- package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
- package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/drizzle/0000_dizzy_maggott.sql +0 -301
- package/drizzle/meta/0000_snapshot.json +0 -1933
- package/drizzle/meta/_journal.json +0 -13
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
- package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
- package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
- package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
- package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
- package/src/__tests__/invite-redemption-service.test.ts +0 -1180
- package/src/__tests__/invite-service-ipc.test.ts +0 -350
- package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
- package/src/__tests__/relay-server.test.ts +0 -6153
- package/src/__tests__/telephony-stt-routing.test.ts +0 -329
- package/src/__tests__/voice-invite-redemption.test.ts +0 -702
- package/src/calls/relay-server.ts +0 -2463
- package/src/calls/telephony-stt-routing.ts +0 -145
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
- package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
- package/src/daemon/daemon-skill-host.ts +0 -262
- package/src/daemon/message-types/meet.ts +0 -143
- package/src/daemon/shutdown-registry.ts +0 -40
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
- package/src/ipc/__tests__/skill-server.test.ts +0 -182
- package/src/ipc/skill-ipc-types.ts +0 -54
- package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
- package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
- package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
- package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
- package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
- package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
- package/src/ipc/skill-routes/config.ts +0 -47
- package/src/ipc/skill-routes/events.ts +0 -147
- package/src/ipc/skill-routes/identity.ts +0 -21
- package/src/ipc/skill-routes/index.ts +0 -37
- package/src/ipc/skill-routes/log.ts +0 -40
- package/src/ipc/skill-routes/memory.ts +0 -92
- package/src/ipc/skill-routes/platform.ts +0 -39
- package/src/ipc/skill-routes/providers.ts +0 -162
- package/src/ipc/skill-routes/registries.ts +0 -338
- package/src/ipc/skill-server.ts +0 -831
- package/src/ipc/skill-socket-path.ts +0 -20
- package/src/jobs/register-job-handlers.ts +0 -74
- package/src/persistence/invite-store.ts +0 -481
- package/src/plugins/job-handler-registry.ts +0 -94
- package/src/providers/cache-control.ts +0 -26
- package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
- package/src/runtime/invite-redemption-service.ts +0 -828
- package/src/runtime/invite-redemption-templates.ts +0 -46
- package/src/runtime/runtime-mode.ts +0 -40
- package/src/runtime/slack-dm-text-delivery.ts +0 -177
- package/src/util/voice-code.ts +0 -31
|
@@ -28,15 +28,15 @@ import { findConversation } from "../daemon/conversation-registry.js";
|
|
|
28
28
|
import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
|
|
29
29
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
30
30
|
import { clearAllConversationIds } from "../home/feed-writer.js";
|
|
31
|
-
import { MEMORY_RETROSPECTIVE_SOURCES } from "../plugins/defaults/memory/memory-retrospective-constants.js";
|
|
32
31
|
import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
|
|
33
32
|
import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
|
|
33
|
+
import { trustClassSchema } from "../runtime/trust-class.js";
|
|
34
34
|
import { UserError } from "../util/errors.js";
|
|
35
35
|
import { safeParseRecord } from "../util/json.js";
|
|
36
36
|
import { getLogger } from "../util/logger.js";
|
|
37
37
|
import { getLogsDbPath } from "../util/logs-db-path.js";
|
|
38
38
|
import { getConversationsDir } from "../util/platform.js";
|
|
39
|
-
import { createRowMapper } from "../util/row-mapper.js";
|
|
39
|
+
import { createRowMapper, parseJsonNullable } from "../util/row-mapper.js";
|
|
40
40
|
import { withSqliteRetry } from "../util/sqlite-retry.js";
|
|
41
41
|
import {
|
|
42
42
|
deleteOrphanAttachments,
|
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
copyForkMessagesViaSubprocess,
|
|
73
73
|
type ForkIdPair,
|
|
74
74
|
} from "./fork-message-copy.js";
|
|
75
|
+
import { enqueueLexicalIndexForMessage } from "./job-handlers/message-lexical.js";
|
|
75
76
|
import { getMemoryPersistenceHooks } from "./memory-lifecycle-hooks.js";
|
|
76
77
|
import {
|
|
77
78
|
rawExec,
|
|
@@ -92,6 +93,7 @@ import {
|
|
|
92
93
|
skillLoadedEvents,
|
|
93
94
|
toolInvocations,
|
|
94
95
|
} from "./schema/index.js";
|
|
96
|
+
import { timeSyncSection } from "./slow-sync-log.js";
|
|
95
97
|
|
|
96
98
|
const log = getLogger("conversation-store");
|
|
97
99
|
|
|
@@ -134,6 +136,18 @@ const acpNotificationSchema = z.object({
|
|
|
134
136
|
agent: z.string().optional(),
|
|
135
137
|
});
|
|
136
138
|
|
|
139
|
+
const backgroundToolCompletionMetadataSchema = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
toolName: z.string(),
|
|
142
|
+
conversationId: z.string(),
|
|
143
|
+
command: z.string(),
|
|
144
|
+
startedAt: z.number(),
|
|
145
|
+
status: z.enum(["completed", "failed", "cancelled"]),
|
|
146
|
+
exitCode: z.number().nullable(),
|
|
147
|
+
output: z.string(),
|
|
148
|
+
completedAt: z.number(),
|
|
149
|
+
});
|
|
150
|
+
|
|
137
151
|
export const messageMetadataSchema = z
|
|
138
152
|
.object({
|
|
139
153
|
userMessageChannel: channelIdSchema.optional(),
|
|
@@ -158,13 +172,27 @@ export const messageMetadataSchema = z
|
|
|
158
172
|
* trust status changes later. Used by the memory write gate (indexer)
|
|
159
173
|
* and read gate (conversation history loading) to enforce trust-aware access.
|
|
160
174
|
*/
|
|
161
|
-
provenanceTrustClass:
|
|
162
|
-
.enum(["guardian", "trusted_contact", "unverified_contact", "unknown"])
|
|
163
|
-
.optional(),
|
|
175
|
+
provenanceTrustClass: trustClassSchema.optional(),
|
|
164
176
|
provenanceSourceChannel: channelIdSchema.optional(),
|
|
165
177
|
provenanceGuardianExternalUserId: z.string().optional(),
|
|
166
178
|
provenanceRequesterIdentifier: z.string().optional(),
|
|
167
179
|
automated: z.boolean().optional(),
|
|
180
|
+
/**
|
|
181
|
+
* Transcript-suppression flag: the row is a machine signal (e.g. the
|
|
182
|
+
* channel-setup wizard-close marker, the onboarding greeting kickoff),
|
|
183
|
+
* persisted and LLM-visible but never rendered as a user message. Test
|
|
184
|
+
* with {@link isHiddenMessageMetadata} — hidden rows are filtered from
|
|
185
|
+
* list-messages and queued snapshots, skip the user_message_echo, and
|
|
186
|
+
* are excluded from search/memory indexing and other consumers that
|
|
187
|
+
* treat message text as organic user input.
|
|
188
|
+
*/
|
|
189
|
+
hidden: z.boolean().optional(),
|
|
190
|
+
/**
|
|
191
|
+
* Structured terminal record stamped onto a `<background_event
|
|
192
|
+
* source="background-tool">` wake so the web can rebuild the inline
|
|
193
|
+
* bash/host_bash card from history after a daemon restart.
|
|
194
|
+
*/
|
|
195
|
+
backgroundToolCompletion: backgroundToolCompletionMetadataSchema.optional(),
|
|
168
196
|
forkSourceMessageId: z.string().optional(),
|
|
169
197
|
/** Image source paths from desktop attachments, keyed by filename. */
|
|
170
198
|
imageSourcePaths: z.record(z.string(), z.string()).optional(),
|
|
@@ -191,6 +219,44 @@ export const messageMetadataSchema = z
|
|
|
191
219
|
})
|
|
192
220
|
.passthrough();
|
|
193
221
|
|
|
222
|
+
/** Validated shape of a persisted message's `metadata` column. */
|
|
223
|
+
export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Shared predicate for the transcript-suppression flag on user-message
|
|
227
|
+
* metadata (see the `hidden` field on {@link messageMetadataSchema}). One
|
|
228
|
+
* definition so the sites that must agree — echo suppression, list-messages
|
|
229
|
+
* filtering, queued-snapshot filtering, indexing exclusion, and downstream
|
|
230
|
+
* consumers of message text — cannot drift.
|
|
231
|
+
*/
|
|
232
|
+
export function isHiddenMessageMetadata(
|
|
233
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
234
|
+
): boolean {
|
|
235
|
+
return metadata?.hidden === true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parse a persisted message's metadata JSON against {@link messageMetadataSchema}
|
|
240
|
+
* — the single source of truth for its shape — returning the validated fields,
|
|
241
|
+
* or `undefined` when the column is absent, not valid JSON, or fails validation.
|
|
242
|
+
* The single place the raw JSON.parse + safeParse dance lives, so callers read
|
|
243
|
+
* typed fields (e.g. `provenanceTrustClass`, `automated`, `subagentNotification`)
|
|
244
|
+
* instead of re-implementing it.
|
|
245
|
+
*/
|
|
246
|
+
export function parseMessageMetadata(
|
|
247
|
+
metadataJson: string | null,
|
|
248
|
+
): MessageMetadata | undefined {
|
|
249
|
+
if (!metadataJson) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const parsed = messageMetadataSchema.safeParse(JSON.parse(metadataJson));
|
|
254
|
+
return parsed.success ? parsed.data : undefined;
|
|
255
|
+
} catch {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
194
260
|
function cloneForkMessageMetadata(
|
|
195
261
|
metadata: string | null,
|
|
196
262
|
sourceMessageId: string,
|
|
@@ -228,7 +294,9 @@ function cloneForkMessageMetadata(
|
|
|
228
294
|
export function provenanceFromTrustContext(
|
|
229
295
|
ctx: TrustContext | null | undefined,
|
|
230
296
|
): Record<string, unknown> {
|
|
231
|
-
if (!ctx)
|
|
297
|
+
if (!ctx) {
|
|
298
|
+
return { provenanceTrustClass: "unknown" };
|
|
299
|
+
}
|
|
232
300
|
return {
|
|
233
301
|
provenanceTrustClass: ctx.trustClass,
|
|
234
302
|
provenanceSourceChannel: ctx.sourceChannel,
|
|
@@ -282,6 +350,8 @@ export interface ConversationRow {
|
|
|
282
350
|
archivedAt: number | null;
|
|
283
351
|
surfacedAt: number | null;
|
|
284
352
|
inferenceProfile: string | null;
|
|
353
|
+
/** Parsed plugin-id list scoping this chat; null = default (all globally-enabled). */
|
|
354
|
+
enabledPlugins: string[] | null;
|
|
285
355
|
inferenceProfileSessionId: string | null;
|
|
286
356
|
inferenceProfileExpiresAt: number | null;
|
|
287
357
|
lastNotifiedInferenceProfile: string | null;
|
|
@@ -318,6 +388,10 @@ export const parseConversation = createRowMapper<
|
|
|
318
388
|
archivedAt: "archivedAt",
|
|
319
389
|
surfacedAt: "surfacedAt",
|
|
320
390
|
inferenceProfile: "inferenceProfile",
|
|
391
|
+
enabledPlugins: {
|
|
392
|
+
from: "enabledPlugins",
|
|
393
|
+
transform: parseJsonNullable<string[]>(),
|
|
394
|
+
},
|
|
321
395
|
inferenceProfileSessionId: "inferenceProfileSessionId",
|
|
322
396
|
inferenceProfileExpiresAt: "inferenceProfileExpiresAt",
|
|
323
397
|
lastNotifiedInferenceProfile: "lastNotifiedInferenceProfile",
|
|
@@ -383,6 +457,11 @@ interface InsertMessageCoreParams {
|
|
|
383
457
|
content: string;
|
|
384
458
|
metadata?: Record<string, unknown>;
|
|
385
459
|
clientMessageId?: string;
|
|
460
|
+
/** Pre-assigned message ID. When omitted, one is generated via
|
|
461
|
+
* `uuid()`. Callers that already have a correlation ID (e.g.
|
|
462
|
+
* `requestId` for user turns) can pass it here so the persisted
|
|
463
|
+
* row ID matches the runtime request ID. */
|
|
464
|
+
id?: string;
|
|
386
465
|
}
|
|
387
466
|
|
|
388
467
|
/**
|
|
@@ -409,9 +488,10 @@ interface InsertMessageCoreParams {
|
|
|
409
488
|
async function insertMessageCore(
|
|
410
489
|
params: InsertMessageCoreParams,
|
|
411
490
|
): Promise<InsertedMessage> {
|
|
412
|
-
const { conversationId, role, content, metadata, clientMessageId } =
|
|
491
|
+
const { conversationId, role, content, metadata, clientMessageId, id } =
|
|
492
|
+
params;
|
|
413
493
|
const db = getDb();
|
|
414
|
-
const messageId = uuid();
|
|
494
|
+
const messageId = id ?? uuid();
|
|
415
495
|
|
|
416
496
|
if (metadata) {
|
|
417
497
|
const result = messageMetadataSchema.safeParse(metadata);
|
|
@@ -432,105 +512,117 @@ async function insertMessageCore(
|
|
|
432
512
|
// The timestamp is recomputed each attempt so a late retry doesn't persist a
|
|
433
513
|
// stale `updatedAt`.
|
|
434
514
|
return withSqliteRetry(
|
|
435
|
-
(): InsertedMessage =>
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
515
|
+
(): InsertedMessage =>
|
|
516
|
+
timeSyncSection(
|
|
517
|
+
"messages:insert",
|
|
518
|
+
(): InsertedMessage => {
|
|
519
|
+
const now = monotonicNow();
|
|
520
|
+
const values = {
|
|
521
|
+
id: messageId,
|
|
522
|
+
conversationId,
|
|
523
|
+
role,
|
|
524
|
+
content,
|
|
525
|
+
createdAt: now,
|
|
526
|
+
...(metadataStr ? { metadata: metadataStr } : {}),
|
|
527
|
+
...(clientMessageId ? { clientMessageId } : {}),
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
if (clientMessageId) {
|
|
531
|
+
// Idempotent insert: skip silently if this clientMessageId was
|
|
532
|
+
// already persisted for the conversation.
|
|
533
|
+
const raw = getSqliteFrom(db);
|
|
534
|
+
raw.exec("SAVEPOINT insert_msg");
|
|
535
|
+
try {
|
|
536
|
+
db.insert(messages).values(values).run();
|
|
537
|
+
if (originChannelCandidate) {
|
|
538
|
+
db.update(conversations)
|
|
539
|
+
.set({ originChannel: originChannelCandidate })
|
|
540
|
+
.where(
|
|
541
|
+
and(
|
|
542
|
+
eq(conversations.id, conversationId),
|
|
543
|
+
isNull(conversations.originChannel),
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
.run();
|
|
547
|
+
}
|
|
548
|
+
db.update(conversations)
|
|
549
|
+
.set({ updatedAt: now, lastMessageAt: now })
|
|
550
|
+
.where(eq(conversations.id, conversationId))
|
|
551
|
+
.run();
|
|
552
|
+
raw.exec("RELEASE insert_msg");
|
|
553
|
+
} catch (insertErr) {
|
|
554
|
+
raw.exec("ROLLBACK TO insert_msg");
|
|
555
|
+
raw.exec("RELEASE insert_msg");
|
|
556
|
+
const code = (insertErr as { code?: string }).code ?? "";
|
|
557
|
+
if (code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
558
|
+
// Duplicate clientMessageId — return the existing row.
|
|
559
|
+
const existing = db
|
|
560
|
+
.select()
|
|
561
|
+
.from(messages)
|
|
562
|
+
.where(
|
|
563
|
+
and(
|
|
564
|
+
eq(messages.conversationId, conversationId),
|
|
565
|
+
eq(messages.clientMessageId, clientMessageId),
|
|
566
|
+
),
|
|
567
|
+
)
|
|
568
|
+
.get();
|
|
569
|
+
if (existing) {
|
|
570
|
+
return {
|
|
571
|
+
id: existing.id,
|
|
572
|
+
conversationId: existing.conversationId,
|
|
573
|
+
role: existing.role as MessageRole,
|
|
574
|
+
content: existing.content,
|
|
575
|
+
createdAt: existing.createdAt,
|
|
576
|
+
...(existing.metadata
|
|
577
|
+
? { metadata: existing.metadata }
|
|
578
|
+
: {}),
|
|
579
|
+
clientMessageId: existing.clientMessageId ?? undefined,
|
|
580
|
+
deduplicated: true,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
throw insertErr;
|
|
497
585
|
}
|
|
586
|
+
} else {
|
|
587
|
+
// No clientMessageId — standard insert inside a transaction.
|
|
588
|
+
db.transaction((tx) => {
|
|
589
|
+
tx.insert(messages).values(values).run();
|
|
590
|
+
if (originChannelCandidate) {
|
|
591
|
+
tx.update(conversations)
|
|
592
|
+
.set({ originChannel: originChannelCandidate })
|
|
593
|
+
.where(
|
|
594
|
+
and(
|
|
595
|
+
eq(conversations.id, conversationId),
|
|
596
|
+
isNull(conversations.originChannel),
|
|
597
|
+
),
|
|
598
|
+
)
|
|
599
|
+
.run();
|
|
600
|
+
}
|
|
601
|
+
tx.update(conversations)
|
|
602
|
+
.set({ updatedAt: now, lastMessageAt: now })
|
|
603
|
+
.where(eq(conversations.id, conversationId))
|
|
604
|
+
.run();
|
|
605
|
+
});
|
|
498
606
|
}
|
|
499
|
-
throw insertErr;
|
|
500
|
-
}
|
|
501
|
-
} else {
|
|
502
|
-
// No clientMessageId — standard insert inside a transaction.
|
|
503
|
-
db.transaction((tx) => {
|
|
504
|
-
tx.insert(messages).values(values).run();
|
|
505
|
-
if (originChannelCandidate) {
|
|
506
|
-
tx.update(conversations)
|
|
507
|
-
.set({ originChannel: originChannelCandidate })
|
|
508
|
-
.where(
|
|
509
|
-
and(
|
|
510
|
-
eq(conversations.id, conversationId),
|
|
511
|
-
isNull(conversations.originChannel),
|
|
512
|
-
),
|
|
513
|
-
)
|
|
514
|
-
.run();
|
|
515
|
-
}
|
|
516
|
-
tx.update(conversations)
|
|
517
|
-
.set({ updatedAt: now, lastMessageAt: now })
|
|
518
|
-
.where(eq(conversations.id, conversationId))
|
|
519
|
-
.run();
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
607
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
608
|
+
return {
|
|
609
|
+
id: messageId,
|
|
610
|
+
conversationId,
|
|
611
|
+
role,
|
|
612
|
+
content,
|
|
613
|
+
createdAt: now,
|
|
614
|
+
...(metadataStr ? { metadata: metadataStr } : {}),
|
|
615
|
+
...(clientMessageId ? { clientMessageId } : {}),
|
|
616
|
+
deduplicated: false,
|
|
617
|
+
};
|
|
618
|
+
},
|
|
619
|
+
() => ({
|
|
620
|
+
conversationId,
|
|
621
|
+
role,
|
|
622
|
+
contentBytes:
|
|
623
|
+
typeof content === "string" ? content.length : undefined,
|
|
624
|
+
}),
|
|
625
|
+
),
|
|
534
626
|
{ op: "insertMessageCore", context: { conversationId } },
|
|
535
627
|
);
|
|
536
628
|
}
|
|
@@ -616,6 +708,7 @@ export function createConversation(
|
|
|
616
708
|
try {
|
|
617
709
|
db.insert(conversations).values(conversation).run();
|
|
618
710
|
rawRun(
|
|
711
|
+
"conversation:create:setGroup",
|
|
619
712
|
"UPDATE conversations SET group_id = ?, is_pinned = ? WHERE id = ?",
|
|
620
713
|
effectiveGroupId,
|
|
621
714
|
effectiveGroupId === "system:pinned" ? 1 : 0,
|
|
@@ -653,6 +746,7 @@ export function countConversationsByScheduleJobId(
|
|
|
653
746
|
): number {
|
|
654
747
|
return (
|
|
655
748
|
rawGet<{ c: number }>(
|
|
749
|
+
"conversation:countByScheduleJobId",
|
|
656
750
|
"SELECT COUNT(*) AS c FROM conversations WHERE schedule_job_id = ?",
|
|
657
751
|
scheduleJobId,
|
|
658
752
|
)?.c ?? 0
|
|
@@ -690,67 +784,6 @@ export function findAnalysisConversationFor(
|
|
|
690
784
|
return row ? { id: row.id } : null;
|
|
691
785
|
}
|
|
692
786
|
|
|
693
|
-
/**
|
|
694
|
-
* Find the most recent memory-retrospective background conversation rooted
|
|
695
|
-
* at `parentConversationId`. Used by the memory-retrospective job handler
|
|
696
|
-
* to load the prior retrospective's `remember` calls into the new run's
|
|
697
|
-
* `<already_remembered>` block — bounded source-of-truth for "what the
|
|
698
|
-
* prior pass already saved" that scales as the source conversation grows.
|
|
699
|
-
*
|
|
700
|
-
* Walks up `forkParentConversationId` when no retrospective exists at the
|
|
701
|
-
* current level. This lets a forked conversation inherit dedup context from
|
|
702
|
-
* its source's most recent retro on the fork's *first* retrospective —
|
|
703
|
-
* otherwise the fork would re-save every fact the source already retro'd.
|
|
704
|
-
* Once the fork accumulates its own retros, those are found at the first
|
|
705
|
-
* iteration and we never walk up.
|
|
706
|
-
*
|
|
707
|
-
* The returned `forkParentConversationId` is the conversation the
|
|
708
|
-
* retrospective row is rooted at — `parentConversationId` itself when it has
|
|
709
|
-
* its own retros, or an ancestor when the chain walk found the row higher
|
|
710
|
-
* up. Callers that mutate the prior (e.g. GC of superseded runs) must check
|
|
711
|
-
* it against the source conversation: an ancestor's row is that ancestor's
|
|
712
|
-
* dedup baseline, not the caller's to delete.
|
|
713
|
-
*
|
|
714
|
-
* Returns `null` when no prior retrospective exists anywhere in the fork
|
|
715
|
-
* chain (true first-run case).
|
|
716
|
-
*
|
|
717
|
-
* Hits `idx_conversations_fork_parent_conversation_id` for the
|
|
718
|
-
* `forkParentConversationId` lookup.
|
|
719
|
-
*/
|
|
720
|
-
const MAX_FORK_CHAIN_DEPTH = 16;
|
|
721
|
-
|
|
722
|
-
export function findMostRecentRetrospectiveFor(
|
|
723
|
-
parentConversationId: string,
|
|
724
|
-
): { id: string; forkParentConversationId: string } | null {
|
|
725
|
-
const db = getDb();
|
|
726
|
-
let currentId: string | null = parentConversationId;
|
|
727
|
-
for (let depth = 0; depth < MAX_FORK_CHAIN_DEPTH && currentId; depth++) {
|
|
728
|
-
const row = db
|
|
729
|
-
.select({ id: conversations.id })
|
|
730
|
-
.from(conversations)
|
|
731
|
-
.where(
|
|
732
|
-
and(
|
|
733
|
-
inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
|
|
734
|
-
eq(conversations.forkParentConversationId, currentId),
|
|
735
|
-
),
|
|
736
|
-
)
|
|
737
|
-
.orderBy(desc(conversations.createdAt))
|
|
738
|
-
.limit(1)
|
|
739
|
-
.get();
|
|
740
|
-
if (row) return { id: row.id, forkParentConversationId: currentId };
|
|
741
|
-
|
|
742
|
-
const parent = db
|
|
743
|
-
.select({
|
|
744
|
-
forkParentConversationId: conversations.forkParentConversationId,
|
|
745
|
-
})
|
|
746
|
-
.from(conversations)
|
|
747
|
-
.where(eq(conversations.id, currentId))
|
|
748
|
-
.get();
|
|
749
|
-
currentId = parent?.forkParentConversationId ?? null;
|
|
750
|
-
}
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
787
|
/**
|
|
755
788
|
* Returns the `source` column for the given conversation, or null if
|
|
756
789
|
* not found. Tiny convenience used by the recursion guard in the
|
|
@@ -774,6 +807,7 @@ export function getConversationSource(conversationId: string): string | null {
|
|
|
774
807
|
function getConversationGroupId(conversationId: string): string | null {
|
|
775
808
|
ensureGroupMigration();
|
|
776
809
|
const row = rawGet<{ group_id: string | null }>(
|
|
810
|
+
"conversation:getGroupId",
|
|
777
811
|
"SELECT group_id FROM conversations WHERE id = ?",
|
|
778
812
|
conversationId,
|
|
779
813
|
);
|
|
@@ -940,6 +974,7 @@ export function forkConversation(params: {
|
|
|
940
974
|
? sourceHistoryStrippedAt
|
|
941
975
|
: null,
|
|
942
976
|
inferenceProfile: sourceConversation.inferenceProfile,
|
|
977
|
+
enabledPlugins: encodeEnabledPlugins(sourceConversation.enabledPlugins),
|
|
943
978
|
})
|
|
944
979
|
.where(eq(conversations.id, fc.id))
|
|
945
980
|
.run();
|
|
@@ -950,18 +985,8 @@ export function forkConversation(params: {
|
|
|
950
985
|
messageAt: number;
|
|
951
986
|
} | null = null;
|
|
952
987
|
|
|
953
|
-
|
|
988
|
+
const forkedMessageValues = messagesToCopy.map((message) => {
|
|
954
989
|
const forkedMessageId = uuid();
|
|
955
|
-
db.insert(messages)
|
|
956
|
-
.values({
|
|
957
|
-
id: forkedMessageId,
|
|
958
|
-
conversationId: fc.id,
|
|
959
|
-
role: message.role,
|
|
960
|
-
content: message.content,
|
|
961
|
-
createdAt: message.createdAt,
|
|
962
|
-
metadata: cloneForkMessageMetadata(message.metadata, message.id),
|
|
963
|
-
})
|
|
964
|
-
.run();
|
|
965
990
|
forkedMessageIds.set(message.id, forkedMessageId);
|
|
966
991
|
|
|
967
992
|
if (message.role === "assistant") {
|
|
@@ -970,6 +995,27 @@ export function forkConversation(params: {
|
|
|
970
995
|
messageAt: message.createdAt,
|
|
971
996
|
};
|
|
972
997
|
}
|
|
998
|
+
|
|
999
|
+
return {
|
|
1000
|
+
id: forkedMessageId,
|
|
1001
|
+
conversationId: fc.id,
|
|
1002
|
+
role: message.role,
|
|
1003
|
+
content: message.content,
|
|
1004
|
+
createdAt: message.createdAt,
|
|
1005
|
+
metadata: cloneForkMessageMetadata(message.metadata, message.id),
|
|
1006
|
+
};
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// Insert in chunks of one multi-row statement each so a large fork takes
|
|
1010
|
+
// the SQLite write lock O(messages / chunk) times instead of once per row.
|
|
1011
|
+
const FORK_INSERT_CHUNK_SIZE = 200;
|
|
1012
|
+
for (
|
|
1013
|
+
let i = 0;
|
|
1014
|
+
i < forkedMessageValues.length;
|
|
1015
|
+
i += FORK_INSERT_CHUNK_SIZE
|
|
1016
|
+
) {
|
|
1017
|
+
const chunk = forkedMessageValues.slice(i, i + FORK_INSERT_CHUNK_SIZE);
|
|
1018
|
+
db.insert(messages).values(chunk).run();
|
|
973
1019
|
}
|
|
974
1020
|
|
|
975
1021
|
populateForkContentsInProcess({
|
|
@@ -1059,7 +1105,9 @@ function populateForkContentsInProcess(args: PopulateForkContentsArgs): void {
|
|
|
1059
1105
|
const attachmentIdMap = new Map<string, string>();
|
|
1060
1106
|
for (const message of messagesToCopy) {
|
|
1061
1107
|
const forkedMessageId = forkedMessageIds.get(message.id);
|
|
1062
|
-
if (!forkedMessageId)
|
|
1108
|
+
if (!forkedMessageId) {
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1063
1111
|
|
|
1064
1112
|
const attachmentLinks = db
|
|
1065
1113
|
.select({
|
|
@@ -1173,7 +1221,9 @@ function latestForkedAssistantFrom(
|
|
|
1173
1221
|
): { messageId: string; messageAt: number } | null {
|
|
1174
1222
|
for (let i = messagesToCopy.length - 1; i >= 0; i--) {
|
|
1175
1223
|
const message = messagesToCopy[i]!;
|
|
1176
|
-
if (message.role !== "assistant")
|
|
1224
|
+
if (message.role !== "assistant") {
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1177
1227
|
const forkedMessageId = forkedMessageIds.get(message.id);
|
|
1178
1228
|
if (forkedMessageId) {
|
|
1179
1229
|
return { messageId: forkedMessageId, messageAt: message.createdAt };
|
|
@@ -1317,6 +1367,9 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1317
1367
|
? sourceHistoryStrippedAt
|
|
1318
1368
|
: null,
|
|
1319
1369
|
inferenceProfile: sourceConversation.inferenceProfile,
|
|
1370
|
+
enabledPlugins: encodeEnabledPlugins(
|
|
1371
|
+
sourceConversation.enabledPlugins,
|
|
1372
|
+
),
|
|
1320
1373
|
})
|
|
1321
1374
|
.where(eq(conversations.id, fc.id))
|
|
1322
1375
|
.run();
|
|
@@ -1455,6 +1508,13 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
1455
1508
|
removeConversationDir(id, createdAtForDiskCleanup);
|
|
1456
1509
|
}
|
|
1457
1510
|
|
|
1511
|
+
// Let the memory feature purge the conversation's per-message index (e.g. its
|
|
1512
|
+
// lexical points). Fired from the shared primitive so every delete caller —
|
|
1513
|
+
// route, wipe, retrospective cleanup/GC — cleans up. Routed through the
|
|
1514
|
+
// persistence-hook seam so this layer stays decoupled from the memory plugin;
|
|
1515
|
+
// a no-op when memory is not present.
|
|
1516
|
+
getMemoryPersistenceHooks().onConversationDeleted(id);
|
|
1517
|
+
|
|
1458
1518
|
return result;
|
|
1459
1519
|
}
|
|
1460
1520
|
|
|
@@ -1561,6 +1621,12 @@ export async function deleteConversationGently(
|
|
|
1561
1621
|
removeConversationDir(id, createdAtForDiskCleanup);
|
|
1562
1622
|
}
|
|
1563
1623
|
|
|
1624
|
+
// Let the memory feature purge the conversation's per-message index — the
|
|
1625
|
+
// gentle path is the retrospective-GC caller, which would otherwise leak the
|
|
1626
|
+
// conversation's lexical points. Routed through the persistence-hook seam;
|
|
1627
|
+
// a no-op when memory is not present.
|
|
1628
|
+
getMemoryPersistenceHooks().onConversationDeleted(id);
|
|
1629
|
+
|
|
1564
1630
|
return result;
|
|
1565
1631
|
}
|
|
1566
1632
|
|
|
@@ -1632,6 +1698,10 @@ export interface AddMessageOptions {
|
|
|
1632
1698
|
* duplicate inserts for the same `(conversationId, clientMessageId)`
|
|
1633
1699
|
* pair are silently skipped. */
|
|
1634
1700
|
clientMessageId?: string;
|
|
1701
|
+
/** Pre-assigned message ID. When omitted, one is generated
|
|
1702
|
+
* internally. Pass the same value as `requestId` for user turns so
|
|
1703
|
+
* the persisted row ID matches the runtime correlation ID. */
|
|
1704
|
+
id?: string;
|
|
1635
1705
|
}
|
|
1636
1706
|
|
|
1637
1707
|
/**
|
|
@@ -1645,13 +1715,14 @@ export async function addMessage(
|
|
|
1645
1715
|
content: string,
|
|
1646
1716
|
options?: AddMessageOptions,
|
|
1647
1717
|
) {
|
|
1648
|
-
const { metadata, skipIndexing, clientMessageId } = options ?? {};
|
|
1718
|
+
const { metadata, skipIndexing, clientMessageId, id } = options ?? {};
|
|
1649
1719
|
const inserted = await insertMessageCore({
|
|
1650
1720
|
conversationId,
|
|
1651
1721
|
role,
|
|
1652
1722
|
content,
|
|
1653
1723
|
metadata,
|
|
1654
1724
|
clientMessageId,
|
|
1725
|
+
id,
|
|
1655
1726
|
});
|
|
1656
1727
|
|
|
1657
1728
|
if (inserted.deduplicated) {
|
|
@@ -1660,7 +1731,15 @@ export async function addMessage(
|
|
|
1660
1731
|
|
|
1661
1732
|
const message = inserted;
|
|
1662
1733
|
|
|
1663
|
-
|
|
1734
|
+
// Hidden rows are machine signals suppressed from the transcript — they
|
|
1735
|
+
// must not surface as search excerpts or be embedded into memory either.
|
|
1736
|
+
if (!skipIndexing && !isHiddenMessageMetadata(metadata)) {
|
|
1737
|
+
// Message-content lexical indexing is host infrastructure, invoked
|
|
1738
|
+
// directly (not through the memory hook seam, whose active side effects go
|
|
1739
|
+
// inert while the memory plugin is disabled — search indexing must not).
|
|
1740
|
+
// The direct write seams (streaming finalize, import, edit) enqueue for
|
|
1741
|
+
// themselves; this covers the plain addMessage path.
|
|
1742
|
+
enqueueLexicalIndexForMessage(message.id);
|
|
1664
1743
|
try {
|
|
1665
1744
|
const parsed = metadata
|
|
1666
1745
|
? messageMetadataSchema.safeParse(metadata)
|
|
@@ -1711,13 +1790,21 @@ export async function addMessage(
|
|
|
1711
1790
|
|
|
1712
1791
|
export function getMessages(conversationId: string): MessageRow[] {
|
|
1713
1792
|
const db = getDb();
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1793
|
+
// Synchronous read of every row for the conversation — the dominant
|
|
1794
|
+
// per-turn main-thread cost on large conversations. Timed so a freeze the
|
|
1795
|
+
// event-loop watchdog detects can be attributed here (see slow-sync-log).
|
|
1796
|
+
return timeSyncSection(
|
|
1797
|
+
"conversation-crud:get-messages",
|
|
1798
|
+
() =>
|
|
1799
|
+
db
|
|
1800
|
+
.select()
|
|
1801
|
+
.from(messages)
|
|
1802
|
+
.where(eq(messages.conversationId, conversationId))
|
|
1803
|
+
.orderBy(asc(messages.createdAt))
|
|
1804
|
+
.all()
|
|
1805
|
+
.map(parseMessage),
|
|
1806
|
+
(rows) => ({ conversationId, rowCount: rows.length }),
|
|
1807
|
+
);
|
|
1721
1808
|
}
|
|
1722
1809
|
|
|
1723
1810
|
/**
|
|
@@ -1796,7 +1883,9 @@ export function countMessagesAfter(
|
|
|
1796
1883
|
.from(messages)
|
|
1797
1884
|
.where(eq(messages.id, afterMessageId))
|
|
1798
1885
|
.get();
|
|
1799
|
-
if (!ref)
|
|
1886
|
+
if (!ref) {
|
|
1887
|
+
return 0;
|
|
1888
|
+
}
|
|
1800
1889
|
// Tie-breaker on `messages.id` so rows that share a millisecond timestamp
|
|
1801
1890
|
// with the reference are not permanently skipped. Mirrors the
|
|
1802
1891
|
// `(createdAt, id)` cursor pattern used by the backfill job-handler and
|
|
@@ -1849,7 +1938,9 @@ export function getMessagesAfter(
|
|
|
1849
1938
|
.from(messages)
|
|
1850
1939
|
.where(eq(messages.id, afterMessageId))
|
|
1851
1940
|
.get();
|
|
1852
|
-
if (!ref)
|
|
1941
|
+
if (!ref) {
|
|
1942
|
+
return [];
|
|
1943
|
+
}
|
|
1853
1944
|
// Same `(createdAt, id)` cursor as `countMessagesAfter` — rows sharing
|
|
1854
1945
|
// the reference's millisecond timestamp would otherwise be skipped.
|
|
1855
1946
|
return db
|
|
@@ -1990,15 +2081,23 @@ export function getMessagesPaginated(
|
|
|
1990
2081
|
.all()
|
|
1991
2082
|
.map(parseMessage);
|
|
1992
2083
|
|
|
1993
|
-
if (chunk.length === 0)
|
|
2084
|
+
if (chunk.length === 0) {
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
1994
2087
|
rowsScanned += chunk.length;
|
|
1995
2088
|
|
|
1996
2089
|
for (const row of chunk) {
|
|
1997
|
-
if (!filter || filter(row))
|
|
1998
|
-
|
|
2090
|
+
if (!filter || filter(row)) {
|
|
2091
|
+
visible.push(row);
|
|
2092
|
+
}
|
|
2093
|
+
if (visible.length >= limit + 1) {
|
|
2094
|
+
break;
|
|
2095
|
+
}
|
|
1999
2096
|
}
|
|
2000
2097
|
|
|
2001
|
-
if (chunk.length < chunkSize)
|
|
2098
|
+
if (chunk.length < chunkSize) {
|
|
2099
|
+
break;
|
|
2100
|
+
}
|
|
2002
2101
|
const lastRow = chunk[chunk.length - 1];
|
|
2003
2102
|
lastScanned = { createdAt: lastRow.createdAt, id: lastRow.id };
|
|
2004
2103
|
cursorCreatedAt = lastRow.createdAt;
|
|
@@ -2010,7 +2109,9 @@ export function getMessagesPaginated(
|
|
|
2010
2109
|
// the scanned window, so report `hasMore: true` to keep the client draining
|
|
2011
2110
|
// — returning `false` here is the stall this loop exists to prevent.
|
|
2012
2111
|
const hasMore = filledPage || scanCapTruncated;
|
|
2013
|
-
if (filledPage)
|
|
2112
|
+
if (filledPage) {
|
|
2113
|
+
visible.splice(limit);
|
|
2114
|
+
}
|
|
2014
2115
|
visible.reverse();
|
|
2015
2116
|
|
|
2016
2117
|
// Only hand back a resume cursor when the cap (not DB exhaustion) cut the
|
|
@@ -2092,7 +2193,9 @@ export function updateConversationTitle(
|
|
|
2092
2193
|
): void {
|
|
2093
2194
|
const db = getDb();
|
|
2094
2195
|
const set: Record<string, unknown> = { title, updatedAt: Date.now() };
|
|
2095
|
-
if (isAutoTitle !== undefined)
|
|
2196
|
+
if (isAutoTitle !== undefined) {
|
|
2197
|
+
set.isAutoTitle = isAutoTitle;
|
|
2198
|
+
}
|
|
2096
2199
|
db.update(conversations).set(set).where(eq(conversations.id, id)).run();
|
|
2097
2200
|
|
|
2098
2201
|
// Update disk view meta.json with the new title
|
|
@@ -2181,9 +2284,12 @@ export function updateConversationSlackContextWatermark(
|
|
|
2181
2284
|
|
|
2182
2285
|
export function archiveConversation(id: string): boolean {
|
|
2183
2286
|
const conv = getConversation(id);
|
|
2184
|
-
if (!conv)
|
|
2287
|
+
if (!conv) {
|
|
2288
|
+
return false;
|
|
2289
|
+
}
|
|
2185
2290
|
const now = Date.now();
|
|
2186
2291
|
rawRun(
|
|
2292
|
+
"conversation:archive",
|
|
2187
2293
|
"UPDATE conversations SET archived_at = ?, updated_at = ? WHERE id = ?",
|
|
2188
2294
|
now,
|
|
2189
2295
|
now,
|
|
@@ -2194,9 +2300,12 @@ export function archiveConversation(id: string): boolean {
|
|
|
2194
2300
|
|
|
2195
2301
|
export function unarchiveConversation(id: string): boolean {
|
|
2196
2302
|
const conv = getConversation(id);
|
|
2197
|
-
if (!conv)
|
|
2303
|
+
if (!conv) {
|
|
2304
|
+
return false;
|
|
2305
|
+
}
|
|
2198
2306
|
const now = Date.now();
|
|
2199
2307
|
rawRun(
|
|
2308
|
+
"conversation:unarchive",
|
|
2200
2309
|
"UPDATE conversations SET archived_at = NULL, updated_at = ? WHERE id = ?",
|
|
2201
2310
|
now,
|
|
2202
2311
|
id,
|
|
@@ -2215,6 +2324,7 @@ export function setConversationProcessingStartedAt(
|
|
|
2215
2324
|
startedAt: number | null,
|
|
2216
2325
|
): void {
|
|
2217
2326
|
rawRun(
|
|
2327
|
+
"conversation:setProcessingStartedAt",
|
|
2218
2328
|
"UPDATE conversations SET processing_started_at = ? WHERE id = ?",
|
|
2219
2329
|
startedAt,
|
|
2220
2330
|
id,
|
|
@@ -2230,6 +2340,7 @@ export function setConversationProcessingStartedAt(
|
|
|
2230
2340
|
*/
|
|
2231
2341
|
export function clearStaleProcessingFlags(): number {
|
|
2232
2342
|
return rawRun(
|
|
2343
|
+
"conversation:clearStaleProcessingFlags",
|
|
2233
2344
|
"UPDATE conversations SET processing_started_at = NULL WHERE processing_started_at IS NOT NULL",
|
|
2234
2345
|
);
|
|
2235
2346
|
}
|
|
@@ -2244,8 +2355,11 @@ export function clearStaleProcessingFlags(): number {
|
|
|
2244
2355
|
*/
|
|
2245
2356
|
export function isConversationProcessing(id: string): boolean {
|
|
2246
2357
|
const inMemory = findConversation(id)?.isProcessing();
|
|
2247
|
-
if (inMemory != null)
|
|
2358
|
+
if (inMemory != null) {
|
|
2359
|
+
return inMemory;
|
|
2360
|
+
}
|
|
2248
2361
|
const row = rawGet<{ processing_started_at: number | null }>(
|
|
2362
|
+
"conversation:isProcessing",
|
|
2249
2363
|
"SELECT processing_started_at FROM conversations WHERE id = ?",
|
|
2250
2364
|
id,
|
|
2251
2365
|
);
|
|
@@ -2265,6 +2379,7 @@ export function isConversationProcessing(id: string): boolean {
|
|
|
2265
2379
|
*/
|
|
2266
2380
|
export function getConversationPersistedSeq(id: string): number | null {
|
|
2267
2381
|
const row = rawGet<{ seq: number | null }>(
|
|
2382
|
+
"conversation:getPersistedSeq",
|
|
2268
2383
|
"SELECT seq FROM conversations WHERE id = ?",
|
|
2269
2384
|
id,
|
|
2270
2385
|
);
|
|
@@ -2282,8 +2397,11 @@ export function getConversationPersistedSeq(id: string): number | null {
|
|
|
2282
2397
|
* Non-positive or non-finite `seq` values are ignored.
|
|
2283
2398
|
*/
|
|
2284
2399
|
export function recordConversationPersistedSeq(id: string, seq: number): void {
|
|
2285
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
2400
|
+
if (!Number.isFinite(seq) || seq <= 0) {
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2286
2403
|
rawRun(
|
|
2404
|
+
"conversation:recordPersistedSeq",
|
|
2287
2405
|
"UPDATE conversations SET seq = ? WHERE id = ? AND (seq IS NULL OR seq < ?)",
|
|
2288
2406
|
seq,
|
|
2289
2407
|
id,
|
|
@@ -2309,10 +2427,13 @@ export function setConversationSurfaced(
|
|
|
2309
2427
|
surfaced: boolean,
|
|
2310
2428
|
): { surfacedAt: number | null } | null {
|
|
2311
2429
|
const conv = getConversation(id);
|
|
2312
|
-
if (!conv)
|
|
2430
|
+
if (!conv) {
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2313
2433
|
const now = Date.now();
|
|
2314
2434
|
const surfacedAt = surfaced ? now : null;
|
|
2315
2435
|
rawRun(
|
|
2436
|
+
"conversation:setSurfaced",
|
|
2316
2437
|
"UPDATE conversations SET surfaced_at = ?, updated_at = ? WHERE id = ?",
|
|
2317
2438
|
surfacedAt,
|
|
2318
2439
|
now,
|
|
@@ -2346,6 +2467,51 @@ export function setConversationInferenceProfile(
|
|
|
2346
2467
|
.run();
|
|
2347
2468
|
}
|
|
2348
2469
|
|
|
2470
|
+
/**
|
|
2471
|
+
* Encode a plugin-id list for the `enabled_plugins` text column. Keeps a true
|
|
2472
|
+
* SQL NULL for `null` (rather than the JSON literal `"null"`) so it reads back
|
|
2473
|
+
* as "no per-chat restriction".
|
|
2474
|
+
*/
|
|
2475
|
+
function encodeEnabledPlugins(plugins: string[] | null): string | null {
|
|
2476
|
+
return plugins === null ? null : JSON.stringify(plugins);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Read the per-conversation plugin scope. Returns the parsed `string[]` of
|
|
2481
|
+
* plugin ids, or `null` when the column is unset/empty (= no per-chat
|
|
2482
|
+
* restriction). Defensively returns `null` on a JSON parse failure.
|
|
2483
|
+
*/
|
|
2484
|
+
export function getConversationEnabledPlugins(
|
|
2485
|
+
conversationId: string,
|
|
2486
|
+
): string[] | null {
|
|
2487
|
+
const db = getDb();
|
|
2488
|
+
const row = db
|
|
2489
|
+
.select({ enabledPlugins: conversations.enabledPlugins })
|
|
2490
|
+
.from(conversations)
|
|
2491
|
+
.where(eq(conversations.id, conversationId))
|
|
2492
|
+
.get();
|
|
2493
|
+
return parseJsonNullable<string[]>()(row?.enabledPlugins);
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
/**
|
|
2497
|
+
* Set or clear the per-conversation plugin scope. Pass a `string[]` to scope
|
|
2498
|
+
* the chat to those plugin ids, or `null` to clear the restriction and fall
|
|
2499
|
+
* back to all globally-enabled plugins.
|
|
2500
|
+
*/
|
|
2501
|
+
export function setConversationEnabledPlugins(
|
|
2502
|
+
conversationId: string,
|
|
2503
|
+
plugins: string[] | null,
|
|
2504
|
+
): void {
|
|
2505
|
+
const db = getDb();
|
|
2506
|
+
db.update(conversations)
|
|
2507
|
+
.set({
|
|
2508
|
+
enabledPlugins: encodeEnabledPlugins(plugins),
|
|
2509
|
+
updatedAt: Date.now(),
|
|
2510
|
+
})
|
|
2511
|
+
.where(eq(conversations.id, conversationId))
|
|
2512
|
+
.run();
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2349
2515
|
/**
|
|
2350
2516
|
* Atomically set the inference profile, session id, and expiry timestamp for
|
|
2351
2517
|
* a conversation. Pass `null` for all three to clear the session-backed
|
|
@@ -2391,7 +2557,9 @@ export function clearExpiredInferenceProfiles(
|
|
|
2391
2557
|
)
|
|
2392
2558
|
.all(now) as Array<{ conversationId: string; sessionId: string | null }>;
|
|
2393
2559
|
|
|
2394
|
-
if (expired.length === 0)
|
|
2560
|
+
if (expired.length === 0) {
|
|
2561
|
+
return [];
|
|
2562
|
+
}
|
|
2395
2563
|
|
|
2396
2564
|
const ids = expired.map((r) => r.conversationId);
|
|
2397
2565
|
const placeholders = ids.map(() => "?").join(", ");
|
|
@@ -2516,6 +2684,7 @@ export function setLastNotifiedInferenceProfile(
|
|
|
2516
2684
|
profileKey: string | null,
|
|
2517
2685
|
): void {
|
|
2518
2686
|
rawRun(
|
|
2687
|
+
"conversation:setLastNotifiedProfile",
|
|
2519
2688
|
"UPDATE conversations SET last_notified_inference_profile = ? WHERE id = ?",
|
|
2520
2689
|
profileKey,
|
|
2521
2690
|
conversationId,
|
|
@@ -2539,9 +2708,15 @@ export async function clearAll(): Promise<{
|
|
|
2539
2708
|
messages: number;
|
|
2540
2709
|
}> {
|
|
2541
2710
|
const msgCount =
|
|
2542
|
-
rawGet<{ c: number }>(
|
|
2711
|
+
rawGet<{ c: number }>(
|
|
2712
|
+
"conversation:clearAll:countMessages",
|
|
2713
|
+
"SELECT COUNT(*) AS c FROM messages",
|
|
2714
|
+
)?.c ?? 0;
|
|
2543
2715
|
const convCount =
|
|
2544
|
-
rawGet<{ c: number }>(
|
|
2716
|
+
rawGet<{ c: number }>(
|
|
2717
|
+
"conversation:clearAll:countConvs",
|
|
2718
|
+
"SELECT COUNT(*) AS c FROM conversations",
|
|
2719
|
+
)?.c ?? 0;
|
|
2545
2720
|
|
|
2546
2721
|
// Each DELETE goes through `runAsyncSqlite`. The original code threw
|
|
2547
2722
|
// on rawExec failure; mirror that here by throwing when the async
|
|
@@ -2561,71 +2736,48 @@ export async function clearAll(): Promise<{
|
|
|
2561
2736
|
// Delete in dependency order. Cascades handle memory_segments and
|
|
2562
2737
|
// tool_invocations, but we explicitly clear non-cascading memory
|
|
2563
2738
|
// tables too.
|
|
2564
|
-
//
|
|
2565
|
-
// FTS virtual tables are cleared before their base tables. If an FTS
|
|
2566
|
-
// table is corrupted, the DELETE will fail — we drop the associated
|
|
2567
|
-
// triggers so that the subsequent base-table DELETEs don't also fail
|
|
2568
|
-
// (SQLite triggers are atomic with the triggering statement, so a
|
|
2569
|
-
// corrupted FTS table would roll back every base-table DELETE).
|
|
2570
2739
|
await runOrThrow("DELETE FROM memory_segments");
|
|
2571
2740
|
await runOrThrow("DELETE FROM memory_summaries");
|
|
2572
2741
|
await runOrThrow("DELETE FROM memory_embeddings");
|
|
2573
2742
|
// memory_jobs and llm_request_logs each live in their own dedicated
|
|
2574
2743
|
// connection; clear them directly on those connections rather than through a
|
|
2575
2744
|
// sqlite3 subprocess.
|
|
2576
|
-
rawMemoryRun("DELETE FROM memory_jobs");
|
|
2745
|
+
rawMemoryRun("conversation:clearAll:memoryJobs", "DELETE FROM memory_jobs");
|
|
2577
2746
|
await runOrThrow("DELETE FROM memory_checkpoints");
|
|
2578
|
-
rawLogsRun(
|
|
2747
|
+
rawLogsRun(
|
|
2748
|
+
"conversation:clearAll:requestLogs",
|
|
2749
|
+
"DELETE FROM llm_request_logs",
|
|
2750
|
+
);
|
|
2579
2751
|
await runOrThrow("DELETE FROM llm_usage_events");
|
|
2580
2752
|
await runOrThrow("DELETE FROM message_attachments");
|
|
2581
2753
|
await runOrThrow("DELETE FROM attachments");
|
|
2582
2754
|
await runOrThrow("DELETE FROM tool_invocations");
|
|
2583
2755
|
await runOrThrow("DELETE FROM skill_loaded_events");
|
|
2584
|
-
let messagesFtsCorrupted = false;
|
|
2585
|
-
const ftsResult = await runAsyncSqlite(
|
|
2586
|
-
"DELETE FROM messages_fts",
|
|
2587
|
-
"clearAll:messages-fts",
|
|
2588
|
-
);
|
|
2589
|
-
if (!ftsResult.ok) {
|
|
2590
|
-
log.warn(
|
|
2591
|
-
{ error: ftsResult.error, backend: ftsResult.backend },
|
|
2592
|
-
"clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed",
|
|
2593
|
-
);
|
|
2594
|
-
rawExec("DROP TRIGGER IF EXISTS messages_fts_ai");
|
|
2595
|
-
rawExec("DROP TRIGGER IF EXISTS messages_fts_ad");
|
|
2596
|
-
rawExec("DROP TRIGGER IF EXISTS messages_fts_au");
|
|
2597
|
-
messagesFtsCorrupted = true;
|
|
2598
|
-
}
|
|
2599
2756
|
await runOrThrow("DELETE FROM messages");
|
|
2600
2757
|
await runOrThrow("DELETE FROM conversations");
|
|
2601
2758
|
|
|
2602
2759
|
// Record audit event — lifecycle_events is NOT deleted by clearAll(),
|
|
2603
2760
|
// so this survives the wipe and provides a permanent trail.
|
|
2604
2761
|
rawRun(
|
|
2762
|
+
"conversation:clearAll:auditEvent",
|
|
2605
2763
|
`INSERT INTO lifecycle_events (id, event_name, created_at) VALUES (?, ?, ?)`,
|
|
2606
2764
|
uuid(),
|
|
2607
2765
|
"conversations_clear_all",
|
|
2608
2766
|
Date.now(),
|
|
2609
2767
|
);
|
|
2610
2768
|
|
|
2611
|
-
//
|
|
2612
|
-
//
|
|
2613
|
-
//
|
|
2614
|
-
//
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
);
|
|
2623
|
-
rawExec(
|
|
2624
|
-
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; END`,
|
|
2625
|
-
);
|
|
2626
|
-
rawExec(
|
|
2627
|
-
`CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END`,
|
|
2628
|
-
);
|
|
2769
|
+
// Let the memory feature drop its bulk per-message index (e.g. the whole
|
|
2770
|
+
// lexical Qdrant collection) — a "delete all" leaves no ids to key
|
|
2771
|
+
// per-message cleanup on. Routed through the persistence-hook seam so this
|
|
2772
|
+
// layer stays decoupled from the memory plugin; a no-op when memory is not
|
|
2773
|
+
// present. AWAITED so the drop completes before clear-all returns and writes
|
|
2774
|
+
// resume — a message created right after must not upsert into a collection
|
|
2775
|
+
// that is about to be dropped. Best-effort — a failure must not fail the
|
|
2776
|
+
// whole clear-all.
|
|
2777
|
+
try {
|
|
2778
|
+
await getMemoryPersistenceHooks().onAllConversationsCleared();
|
|
2779
|
+
} catch (err) {
|
|
2780
|
+
log.warn({ err }, "clearAll: failed to clear memory per-message index");
|
|
2629
2781
|
}
|
|
2630
2782
|
|
|
2631
2783
|
// Clear the disk-view conversations directory and recreate it empty
|
|
@@ -2658,7 +2810,9 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
2658
2810
|
.limit(1)
|
|
2659
2811
|
.get();
|
|
2660
2812
|
|
|
2661
|
-
if (!lastUserMsg)
|
|
2813
|
+
if (!lastUserMsg) {
|
|
2814
|
+
return 0;
|
|
2815
|
+
}
|
|
2662
2816
|
|
|
2663
2817
|
// Use rowid to identify the last user message and everything after it.
|
|
2664
2818
|
// rowid is monotonically increasing for inserts, so this is safe even if
|
|
@@ -2674,7 +2828,9 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
2674
2828
|
.from(messages)
|
|
2675
2829
|
.where(condition)
|
|
2676
2830
|
.all();
|
|
2677
|
-
if (deleted === 0)
|
|
2831
|
+
if (deleted === 0) {
|
|
2832
|
+
return 0;
|
|
2833
|
+
}
|
|
2678
2834
|
|
|
2679
2835
|
// Collect attachment IDs linked to the messages being deleted so we can
|
|
2680
2836
|
// scope orphan cleanup to only those candidates (not freshly uploaded ones).
|
|
@@ -2713,6 +2869,12 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
2713
2869
|
|
|
2714
2870
|
deleteOrphanAttachments(candidateAttachmentIds);
|
|
2715
2871
|
|
|
2872
|
+
// Let the memory feature clean up the undone messages' per-message index
|
|
2873
|
+
// entries. This bulk delete bypasses `deleteMessageById`, so notify the seam
|
|
2874
|
+
// with the collected ids — after the transaction and off the write path. A
|
|
2875
|
+
// no-op when memory is not present.
|
|
2876
|
+
getMemoryPersistenceHooks().onMessagesDeleted(messageIds);
|
|
2877
|
+
|
|
2716
2878
|
return deleted;
|
|
2717
2879
|
}
|
|
2718
2880
|
|
|
@@ -2756,6 +2918,13 @@ export async function reserveMessage(
|
|
|
2756
2918
|
/**
|
|
2757
2919
|
* Update the content of an existing message. Used when consolidating
|
|
2758
2920
|
* multiple assistant messages into one.
|
|
2921
|
+
*
|
|
2922
|
+
* This is a pure CRUD primitive: it does NOT enqueue a lexical reindex, because
|
|
2923
|
+
* it is also driven by mid-stream partial flushes and high-frequency tool-timing
|
|
2924
|
+
* stamps (`_startedAt`/`_previewStartedAt`) that either don't change searchable
|
|
2925
|
+
* text or would spam reindex jobs. Callers on genuine content-change seams
|
|
2926
|
+
* (streaming finalize, channel edits, consolidation) enqueue the reindex
|
|
2927
|
+
* themselves via `enqueueLexicalIndexForMessage`.
|
|
2759
2928
|
*/
|
|
2760
2929
|
export function updateMessageContent(
|
|
2761
2930
|
messageId: string,
|
|
@@ -2831,7 +3000,9 @@ export function relinkAttachments(
|
|
|
2831
3000
|
fromMessageIds: string[],
|
|
2832
3001
|
toMessageId: string,
|
|
2833
3002
|
): number {
|
|
2834
|
-
if (fromMessageIds.length === 0)
|
|
3003
|
+
if (fromMessageIds.length === 0) {
|
|
3004
|
+
return 0;
|
|
3005
|
+
}
|
|
2835
3006
|
const db = getDb();
|
|
2836
3007
|
|
|
2837
3008
|
// Count how many links will be moved before updating.
|
|
@@ -2841,7 +3012,9 @@ export function relinkAttachments(
|
|
|
2841
3012
|
.where(inArray(messageAttachments.messageId, fromMessageIds))
|
|
2842
3013
|
.all();
|
|
2843
3014
|
|
|
2844
|
-
if (total === 0)
|
|
3015
|
+
if (total === 0) {
|
|
3016
|
+
return 0;
|
|
3017
|
+
}
|
|
2845
3018
|
|
|
2846
3019
|
db.update(messageAttachments)
|
|
2847
3020
|
.set({ messageId: toMessageId })
|
|
@@ -2933,6 +3106,15 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
2933
3106
|
|
|
2934
3107
|
deleteOrphanAttachments(candidateAttachmentIds);
|
|
2935
3108
|
|
|
3109
|
+
// Let the memory feature clean up the deleted message's per-message index
|
|
3110
|
+
// entry. Notified only when the row actually existed (`msgRow` set), after the
|
|
3111
|
+
// transaction and off the write path, via the persistence-hook seam (a no-op
|
|
3112
|
+
// when memory is not present). Covers single-message deletes (consolidation)
|
|
3113
|
+
// that do not go through the conversation-level purge.
|
|
3114
|
+
if (msgRow) {
|
|
3115
|
+
getMemoryPersistenceHooks().onMessagesDeleted([messageId]);
|
|
3116
|
+
}
|
|
3117
|
+
|
|
2936
3118
|
return result;
|
|
2937
3119
|
}
|
|
2938
3120
|
|
|
@@ -3009,18 +3191,13 @@ export function getConversationRecentProvenanceTrustClass(
|
|
|
3009
3191
|
| "unknown"
|
|
3010
3192
|
| undefined {
|
|
3011
3193
|
const row = rawGet<{ metadata: string | null }>(
|
|
3194
|
+
"conversation:getProvenanceTrustClass",
|
|
3012
3195
|
`SELECT metadata FROM messages
|
|
3013
3196
|
WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL
|
|
3014
3197
|
ORDER BY created_at DESC LIMIT 1`,
|
|
3015
3198
|
conversationId,
|
|
3016
3199
|
);
|
|
3017
|
-
|
|
3018
|
-
try {
|
|
3019
|
-
const parsed = messageMetadataSchema.safeParse(JSON.parse(row.metadata));
|
|
3020
|
-
return parsed.success ? parsed.data.provenanceTrustClass : undefined;
|
|
3021
|
-
} catch {
|
|
3022
|
-
return undefined;
|
|
3023
|
-
}
|
|
3200
|
+
return parseMessageMetadata(row?.metadata ?? null)?.provenanceTrustClass;
|
|
3024
3201
|
}
|
|
3025
3202
|
|
|
3026
3203
|
// ---------------------------------------------------------------------------
|
|
@@ -3051,6 +3228,7 @@ export function batchSetDisplayOrders(
|
|
|
3051
3228
|
safeGroupId = "system:all";
|
|
3052
3229
|
} else if (
|
|
3053
3230
|
!rawGet<{ id: string }>(
|
|
3231
|
+
"conversation:batchSetDisplayOrders:groupCheck",
|
|
3054
3232
|
"SELECT id FROM conversation_groups WHERE id = ?",
|
|
3055
3233
|
safeGroupId,
|
|
3056
3234
|
)
|
|
@@ -3066,6 +3244,7 @@ export function batchSetDisplayOrders(
|
|
|
3066
3244
|
safeGroupId === "system:background" ||
|
|
3067
3245
|
safeGroupId === "system:scheduled";
|
|
3068
3246
|
rawRun(
|
|
3247
|
+
"conversation:batchSetDisplayOrders:group",
|
|
3069
3248
|
`UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ?${
|
|
3070
3249
|
clearsSurfaced ? ", surfaced_at = NULL" : ""
|
|
3071
3250
|
} WHERE id = ?`,
|
|
@@ -3077,12 +3256,14 @@ export function batchSetDisplayOrders(
|
|
|
3077
3256
|
} else if (update.isPinned === undefined) {
|
|
3078
3257
|
// Only displayOrder provided — preserve existing pin state and group.
|
|
3079
3258
|
rawRun(
|
|
3259
|
+
"conversation:batchSetDisplayOrders:orderOnly",
|
|
3080
3260
|
"UPDATE conversations SET display_order = ? WHERE id = ?",
|
|
3081
3261
|
update.displayOrder,
|
|
3082
3262
|
update.id,
|
|
3083
3263
|
);
|
|
3084
3264
|
} else if (update.isPinned) {
|
|
3085
3265
|
rawRun(
|
|
3266
|
+
"conversation:batchSetDisplayOrders:pin",
|
|
3086
3267
|
"UPDATE conversations SET display_order = ?, is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
3087
3268
|
update.displayOrder,
|
|
3088
3269
|
update.id,
|
|
@@ -3091,6 +3272,7 @@ export function batchSetDisplayOrders(
|
|
|
3091
3272
|
// Restore system group from source/conversationType when unpinning,
|
|
3092
3273
|
// instead of clearing to NULL (which would lose provenance).
|
|
3093
3274
|
rawRun(
|
|
3275
|
+
"conversation:batchSetDisplayOrders:unpin",
|
|
3094
3276
|
`UPDATE conversations SET display_order = ?, is_pinned = 0,
|
|
3095
3277
|
group_id = CASE WHEN group_id = 'system:pinned' THEN
|
|
3096
3278
|
CASE
|
|
@@ -3125,13 +3307,16 @@ export function getDisplayMetaForConversations(
|
|
|
3125
3307
|
string,
|
|
3126
3308
|
{ displayOrder: number | null; isPinned: boolean; groupId: string | null }
|
|
3127
3309
|
>();
|
|
3128
|
-
if (conversationIds.length === 0)
|
|
3310
|
+
if (conversationIds.length === 0) {
|
|
3311
|
+
return result;
|
|
3312
|
+
}
|
|
3129
3313
|
for (const id of conversationIds) {
|
|
3130
3314
|
const row = rawGet<{
|
|
3131
3315
|
display_order: number | null;
|
|
3132
3316
|
is_pinned: number | null;
|
|
3133
3317
|
group_id: string | null;
|
|
3134
3318
|
}>(
|
|
3319
|
+
"conversation:getDisplayMeta",
|
|
3135
3320
|
"SELECT display_order, is_pinned, group_id FROM conversations WHERE id = ?",
|
|
3136
3321
|
id,
|
|
3137
3322
|
);
|
|
@@ -3154,10 +3339,14 @@ export function getDisplayMetaForConversations(
|
|
|
3154
3339
|
* be treated as turn boundaries.
|
|
3155
3340
|
*/
|
|
3156
3341
|
function isToolResultMessage(role: string, content: string): boolean {
|
|
3157
|
-
if (role !== "user")
|
|
3342
|
+
if (role !== "user") {
|
|
3343
|
+
return false;
|
|
3344
|
+
}
|
|
3158
3345
|
try {
|
|
3159
3346
|
const parsed = JSON.parse(content);
|
|
3160
|
-
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
3347
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
3348
|
+
return false;
|
|
3349
|
+
}
|
|
3161
3350
|
return parsed.every(
|
|
3162
3351
|
(block: unknown) =>
|
|
3163
3352
|
block != null &&
|
|
@@ -3313,7 +3502,9 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
3313
3502
|
|
|
3314
3503
|
// Look up the target message to get its conversationId and createdAt.
|
|
3315
3504
|
const target = getMessageById(messageId);
|
|
3316
|
-
if (!target)
|
|
3505
|
+
if (!target) {
|
|
3506
|
+
return [messageId];
|
|
3507
|
+
}
|
|
3317
3508
|
|
|
3318
3509
|
// Walk backward from the target message to find the turn boundary.
|
|
3319
3510
|
// Limit to 50 rows — sufficient for even aggressive tool-use loops.
|
|
@@ -3418,7 +3609,9 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
3418
3609
|
|
|
3419
3610
|
// Sort by createdAt to ensure stable ordering.
|
|
3420
3611
|
// Re-fetch createdAt for all collected IDs so the sort is accurate.
|
|
3421
|
-
if (assistantIds.length <= 1)
|
|
3612
|
+
if (assistantIds.length <= 1) {
|
|
3613
|
+
return assistantIds;
|
|
3614
|
+
}
|
|
3422
3615
|
|
|
3423
3616
|
const idSet = new Set(assistantIds);
|
|
3424
3617
|
const sorted = db
|