@vellumai/assistant 0.11.2 → 0.11.3-staging.2
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 +58 -3
- package/docs/trusted-contact-access.md +9 -5
- package/docs/vellum-doctor.md +195 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
- package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
- package/node_modules/@vellumai/environments/src/index.ts +17 -6
- package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
- package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
- package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
- package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
- package/openapi.yaml +589 -30
- package/package.json +1 -1
- package/src/__tests__/agent-image-optimize.test.ts +4 -4
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
- package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/answered-question-persistence.test.ts +240 -0
- package/src/__tests__/anthropic-provider.test.ts +165 -30
- package/src/__tests__/app-control-flow.test.ts +9 -8
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
- package/src/__tests__/attachments-store.test.ts +130 -68
- package/src/__tests__/attachments.test.ts +36 -36
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
- package/src/__tests__/canned-reply-release.test.ts +86 -0
- package/src/__tests__/channel-guardian.test.ts +59 -29
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
- package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
- package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/consult-deadline.test.ts +65 -0
- package/src/__tests__/conversation-agent-loop.test.ts +438 -2
- package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
- package/src/__tests__/conversation-disk-view.test.ts +4 -4
- package/src/__tests__/conversation-error.test.ts +22 -3
- package/src/__tests__/conversation-fork-crud.test.ts +5 -1
- package/src/__tests__/conversation-fork-referential.test.ts +352 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
- package/src/__tests__/conversation-group-tools.test.ts +32 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
- package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
- package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
- package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
- package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
- package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
- package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
- package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
- package/src/__tests__/conversation-queue.test.ts +433 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
- package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
- package/src/__tests__/conversation-store.test.ts +26 -10
- package/src/__tests__/conversation-summarize-route.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
- package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
- package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
- package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
- package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
- package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
- package/src/__tests__/credential-prompt-route.test.ts +57 -7
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +89 -28
- package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
- package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
- package/src/__tests__/disk-pressure-guard.test.ts +55 -55
- package/src/__tests__/disk-pressure-routes.test.ts +10 -10
- package/src/__tests__/disk-pressure-tools.test.ts +2 -2
- package/src/__tests__/disk-usage.test.ts +35 -20
- package/src/__tests__/document-sync-tags.test.ts +379 -0
- package/src/__tests__/document-update-default-surface.test.ts +28 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
- package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
- package/src/__tests__/file-list-tool.test.ts +12 -10
- package/src/__tests__/file-ops-service.test.ts +151 -44
- package/src/__tests__/file-read-container-boundary.test.ts +137 -0
- package/src/__tests__/file-read-tool.test.ts +21 -9
- package/src/__tests__/file-url-path-guard.test.ts +83 -0
- package/src/__tests__/filesystem-tools.test.ts +82 -66
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
- package/src/__tests__/helpers/hub-clients.ts +42 -0
- package/src/__tests__/helpers/mock-conversation.ts +46 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
- package/src/__tests__/history-repair.test.ts +112 -12
- package/src/__tests__/host-bash-proxy.test.ts +10 -5
- package/src/__tests__/host-bash-routes.test.ts +67 -0
- package/src/__tests__/host-browser-proxy.test.ts +2 -2
- package/src/__tests__/host-cu-proxy.test.ts +3 -7
- package/src/__tests__/host-file-proxy.test.ts +3 -7
- package/src/__tests__/host-file-read-tool.test.ts +5 -1
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
- package/src/__tests__/http-conversation-lineage.test.ts +2 -5
- package/src/__tests__/http-user-message-parity.test.ts +44 -0
- package/src/__tests__/identity-routes.test.ts +13 -13
- package/src/__tests__/image-conversion.test.ts +29 -26
- package/src/__tests__/image-source-path-reinject.test.ts +2 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
- package/src/__tests__/introduction-card-resolver.test.ts +6 -1
- package/src/__tests__/list-messages-attachments.test.ts +56 -32
- package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
- package/src/__tests__/list-messages-page-latest.test.ts +61 -61
- package/src/__tests__/list-messages-provider-error.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +49 -6
- package/src/__tests__/list-messages-system-card.test.ts +4 -4
- package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
- package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
- package/src/__tests__/llm-usage-store.test.ts +55 -0
- package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
- package/src/__tests__/media-stream-server-integration.test.ts +36 -0
- package/src/__tests__/migration-export-http.test.ts +1 -1
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
- package/src/__tests__/migration-validate-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-platform-adapter.test.ts +104 -0
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
- package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
- package/src/__tests__/path-policy.test.ts +164 -1
- package/src/__tests__/persist-media-references.test.ts +2 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
- package/src/__tests__/persist-unsendable-image.test.ts +23 -23
- package/src/__tests__/persona-resolver.test.ts +154 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
- package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
- package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
- package/src/__tests__/profiler-routes.test.ts +13 -13
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
- package/src/__tests__/proxy-approval-callback.test.ts +3 -3
- package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
- package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
- package/src/__tests__/resolve-trust-class.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
- package/src/__tests__/schedule-routes.test.ts +730 -8
- package/src/__tests__/schedule-store.test.ts +540 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/__tests__/scheduler-wake.test.ts +5 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
- package/src/__tests__/secure-keys.test.ts +91 -0
- package/src/__tests__/server-history-render.test.ts +97 -7
- package/src/__tests__/size-guard.test.ts +8 -8
- package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
- package/src/__tests__/skill-tool-factory.test.ts +247 -0
- package/src/__tests__/skills-install-staging.test.ts +15 -6
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
- package/src/__tests__/slack-inbound-verification.test.ts +4 -1
- package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
- package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
- package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
- package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
- package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
- package/src/__tests__/subagent-disposal.test.ts +10 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
- package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
- package/src/__tests__/subagent-manager-notify.test.ts +148 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/subagent-persistence.test.ts +141 -0
- package/src/__tests__/subagent-role-registry.test.ts +172 -63
- package/src/__tests__/subagent-role-resolution.test.ts +88 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
- package/src/__tests__/subagent-terminal-message.test.ts +88 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
- package/src/__tests__/subagent-tools.test.ts +1901 -108
- package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
- package/src/__tests__/system-prompt.test.ts +82 -7
- package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
- package/src/__tests__/tool-result-spool.test.ts +74 -18
- package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
- package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
- package/src/__tests__/tools-audio-read.test.ts +10 -10
- package/src/__tests__/tools-get-route.test.ts +85 -12
- package/src/__tests__/tools-list-cli.test.ts +171 -0
- package/src/__tests__/top-level-renderer.test.ts +54 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
- package/src/__tests__/turn-tail-chain.test.ts +135 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
- package/src/__tests__/ui-visual-surface.test.ts +5 -4
- package/src/__tests__/ui-work-result-surface.test.ts +5 -4
- package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
- package/src/__tests__/usage-routes.test.ts +62 -0
- package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
- package/src/__tests__/visible-app-context.test.ts +20 -4
- package/src/__tests__/web-search-history.test.ts +96 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
- package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/agent/attachments.ts +26 -24
- package/src/agent/history-repair/history-repair.ts +27 -26
- package/src/agent/image-optimize.ts +6 -6
- package/src/agent/loop.ts +40 -22
- package/src/agent/message-types.ts +3 -3
- package/src/api/constants/document-tools.ts +55 -0
- package/src/api/events/question-answered.ts +53 -0
- package/src/api/events/tool-result.ts +7 -0
- package/src/api/index.ts +13 -0
- package/src/api/responses/conversation-message.ts +11 -0
- package/src/api/surfaces-oauth-connect.test.ts +96 -0
- package/src/api/surfaces.ts +36 -8
- package/src/approvals/guardian-channel-delivery.ts +83 -0
- package/src/approvals/guardian-expiry-notifier.ts +13 -11
- package/src/approvals/guardian-request-resolvers.ts +222 -135
- package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
- package/src/calls/media-stream-server.ts +9 -1
- package/src/calls/voice-session-bridge.ts +38 -0
- package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
- package/src/channels/config.ts +7 -2
- package/src/channels/gateway-verification-sessions.ts +7 -2
- package/src/channels/types.ts +7 -1
- package/src/cli/commands/__tests__/cache.test.ts +7 -4
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
- package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
- package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/conversations.ts +7 -3
- package/src/cli/commands/credentials.help.ts +7 -0
- package/src/cli/commands/inference-profiles.ts +9 -3
- package/src/cli/commands/inference.help.ts +16 -2
- package/src/cli/commands/mcp.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +18 -0
- package/src/cli/commands/notifications.ts +3 -6
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
- package/src/cli/commands/oauth/connect.test.ts +26 -1
- package/src/cli/commands/oauth/connect.ts +4 -1
- package/src/cli/commands/oauth/index.help.ts +3 -1
- package/src/cli/commands/oauth/providers.ts +3 -0
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.help.ts +14 -12
- package/src/cli/commands/schedules.ts +19 -5
- package/src/cli/commands/tools.help.ts +4 -2
- package/src/cli/commands/tools.ts +38 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/cli.ts +1 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
- package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
- package/src/config/__tests__/profile-tool-support.test.ts +131 -0
- package/src/config/bundled-skills/AGENTS.md +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +53 -22
- package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
- package/src/config/bundled-skills/visualize/SKILL.md +1 -1
- package/src/config/default-profile-catalog.ts +6 -2
- package/src/config/feature-flag-registry.json +25 -17
- package/src/config/llm-resolver.ts +45 -15
- package/src/config/loader.ts +70 -1
- package/src/config/profile-tool-support.ts +110 -0
- package/src/config/sanitize-for-transfer.ts +11 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/stt.test.ts +20 -5
- package/src/config/schemas/channels.ts +13 -0
- package/src/config/schemas/memory-retrospective.ts +25 -0
- package/src/config/schemas/plugin-updates.ts +55 -0
- package/src/config/schemas/services.ts +8 -4
- package/src/config/schemas/stt.ts +17 -5
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
- package/src/contacts/guardian-delivery-reader.ts +29 -2
- package/src/context/compactor.ts +4 -4
- package/src/context/post-turn-tool-result-truncation.ts +32 -16
- package/src/context/strip-injections.ts +42 -0
- package/src/context/tool-result-spool.ts +32 -18
- package/src/credential-execution/ces-runtime.ts +3 -0
- package/src/credential-execution/client.ts +22 -1
- package/src/credential-execution/process-manager.test.ts +99 -0
- package/src/credential-execution/process-manager.ts +62 -16
- package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
- package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
- package/src/daemon/channel-ui-capability.ts +4 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
- package/src/daemon/conversation-agent-loop.ts +318 -148
- package/src/daemon/conversation-attachments.ts +1 -1
- package/src/daemon/conversation-error.ts +8 -1
- package/src/daemon/conversation-initial-prompt.ts +16 -38
- package/src/daemon/conversation-lifecycle.ts +154 -32
- package/src/daemon/conversation-messaging.ts +106 -43
- package/src/daemon/conversation-process.ts +131 -33
- package/src/daemon/conversation-queue-manager.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +9 -4
- package/src/daemon/conversation-store.ts +52 -3
- package/src/daemon/conversation-surface-state.ts +9 -0
- package/src/daemon/conversation-surfaces.ts +403 -210
- package/src/daemon/conversation-tool-setup.ts +56 -7
- package/src/daemon/conversation-turn-finalize.ts +134 -89
- package/src/daemon/conversation.ts +111 -27
- package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
- package/src/daemon/disk-pressure-guard.ts +3 -3
- package/src/daemon/doordash-steps.ts +5 -5
- package/src/daemon/external-plugins-bootstrap.ts +7 -1
- package/src/daemon/handlers/config-channels.ts +48 -5
- package/src/daemon/handlers/conversation-history.ts +1 -1
- package/src/daemon/handlers/conversations.ts +62 -17
- package/src/daemon/handlers/shared.ts +12 -0
- package/src/daemon/handlers/skills.ts +11 -1
- package/src/daemon/identity-helpers.ts +38 -10
- package/src/daemon/install-assistant-command.ts +322 -0
- package/src/daemon/lifecycle.ts +14 -3
- package/src/daemon/message-types/conversations.ts +6 -7
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/persist-media-references.ts +23 -14
- package/src/daemon/process-message.ts +6 -6
- package/src/daemon/tool-setup-types.ts +19 -122
- package/src/daemon/tool-side-effects.ts +17 -94
- package/src/daemon/trust-context-types.ts +39 -0
- package/src/daemon/trust-context.ts +14 -6
- package/src/daemon/turn-tail-chain.ts +73 -0
- package/src/daemon/web-search-history.ts +47 -33
- package/src/heartbeat/heartbeat-service.ts +6 -6
- package/src/ipc/__tests__/socket-path.test.ts +21 -0
- package/src/ipc/assistant-server.ts +17 -10
- package/src/ipc/cli-client.ts +10 -1
- package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
- package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
- package/src/ipc/socket-cleanup.ts +9 -7
- package/src/ipc/socket-path.ts +4 -96
- package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
- package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
- package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
- package/src/live-voice/__tests__/protocol.test.ts +52 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
- package/src/live-voice/live-voice-archive.ts +13 -13
- package/src/live-voice/live-voice-connection.ts +7 -1
- package/src/live-voice/live-voice-photo.ts +169 -0
- package/src/live-voice/live-voice-session.ts +108 -4
- package/src/live-voice/protocol.ts +89 -1
- package/src/mcp/client.ts +10 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
- package/src/messaging/providers/callback-routing.ts +1 -0
- package/src/messaging/providers/discord/api.ts +312 -0
- package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
- package/src/messaging/providers/discord/render.test.ts +204 -0
- package/src/messaging/providers/discord/render.ts +195 -0
- package/src/messaging/providers/discord/send.test.ts +179 -0
- package/src/messaging/providers/discord/send.ts +212 -0
- package/src/messaging/providers/discord/transport.ts +81 -0
- package/src/messaging/providers/index.ts +3 -1
- package/src/messaging/providers/retry-policy.test.ts +247 -0
- package/src/messaging/providers/retry-policy.ts +206 -0
- package/src/messaging/providers/slack/mention-source.test.ts +285 -0
- package/src/messaging/providers/slack/mention-source.ts +287 -0
- package/src/messaging/providers/telegram-bot/api.ts +47 -130
- package/src/messaging/providers/whatsapp/api.ts +47 -136
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
- package/src/monitoring/db-integrity-sample.ts +6 -1
- package/src/monitoring/plugin-auto-update.ts +359 -0
- package/src/monitoring/resource-sampler.ts +21 -5
- package/src/monitoring/worker.ts +13 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/__tests__/notification-utils.test.ts +249 -0
- package/src/notifications/adapters/platform.ts +31 -2
- package/src/notifications/assistant-reply-producer.ts +79 -32
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +82 -15
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/home-feed-side-effect.ts +6 -3
- package/src/notifications/notification-utils.ts +173 -5
- package/src/notifications/signal.ts +12 -0
- package/src/oauth/AGENTS.md +4 -2
- package/src/oauth/byo-connection.ts +6 -1
- package/src/oauth/credential-token-resolver.ts +3 -10
- package/src/oauth/manual-token-connection.ts +29 -64
- package/src/oauth/manual-token-providers.ts +62 -0
- package/src/oauth/provider-serializer.ts +13 -0
- package/src/oauth/seed-providers.ts +62 -11
- package/src/onboarding/onboarding-events-store.ts +91 -1
- package/src/permissions/question-prompter.test.ts +4 -0
- package/src/permissions/question-prompter.ts +21 -4
- package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
- package/src/persistence/attachments-store.ts +15 -15
- package/src/persistence/conversation-attention-store.ts +30 -10
- package/src/persistence/conversation-bootstrap.ts +17 -0
- package/src/persistence/conversation-crud.ts +363 -97
- package/src/persistence/conversation-group-migration.ts +1 -1
- package/src/persistence/conversation-key-store.ts +38 -10
- package/src/persistence/conversation-lineage.ts +226 -0
- package/src/persistence/conversation-plugin-facade.ts +28 -3
- package/src/persistence/conversation-queries.ts +207 -33
- package/src/persistence/conversation-types.ts +107 -1
- package/src/persistence/jobs-store.ts +32 -8
- package/src/persistence/llm-usage-store.ts +22 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
- package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
- package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
- package/src/persistence/schema/conversations.ts +32 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +30 -0
- package/src/persistence/subagent-store.ts +148 -6
- package/src/platform/managed-speech.test.ts +20 -0
- package/src/platform/managed-speech.ts +9 -0
- package/src/plugin-api/index.ts +8 -0
- package/src/plugin-api/webhook-url.ts +151 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
- package/src/plugins/defaults/exploration-drift/package.json +1 -1
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
- package/src/plugins/defaults/image-recovery/recover.ts +67 -57
- package/src/plugins/defaults/index.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
- package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
- package/src/plugins/defaults/memory/job-handlers.ts +87 -5
- package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
- package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/router.ts +8 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/plugins/types.ts +69 -8
- package/src/prompts/persona-resolver.ts +6 -2
- package/src/prompts/system-prompt.ts +72 -17
- package/src/providers/__tests__/inference.test.ts +40 -0
- package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
- package/src/providers/__tests__/retry-callsite.test.ts +67 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
- package/src/providers/anthropic/client.ts +112 -95
- package/src/providers/connection-resolution.ts +35 -1
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/adapter-factory.ts +16 -10
- package/src/providers/inference/auth.ts +16 -0
- package/src/providers/inference/connection-availability.ts +164 -12
- package/src/providers/inference/connections.ts +25 -1
- package/src/providers/media-resolve.ts +21 -11
- package/src/providers/model-catalog.ts +15 -0
- package/src/providers/openai/chat-completions-provider.ts +4 -4
- package/src/providers/openai/responses-provider.ts +12 -25
- package/src/providers/registry.ts +9 -4
- package/src/providers/retry.ts +25 -0
- package/src/providers/server-tool-pairing.ts +134 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
- package/src/providers/speech-to-text/deepgram.ts +1 -0
- package/src/providers/speech-to-text/resolve.ts +80 -10
- package/src/providers/speech-to-text/vellum-managed.ts +7 -1
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
- package/src/providers/types.ts +26 -6
- package/src/providers/vellum-model-routing.ts +6 -7
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/agent-wake.test.ts +16 -16
- package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
- package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
- package/src/runtime/agent-wake.ts +63 -24
- package/src/runtime/assistant-event-hub.ts +87 -12
- package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
- package/src/runtime/auth/same-actor.ts +56 -8
- package/src/runtime/auth/scopes.ts +12 -0
- package/src/runtime/auth/types.ts +2 -0
- package/src/runtime/background-job-runner.ts +5 -27
- package/src/runtime/channel-readiness-service.ts +59 -3
- package/src/runtime/channel-readiness-types.ts +18 -0
- package/src/runtime/desktop-presence.ts +69 -0
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/local-actor-identity.ts +11 -9
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
- package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
- package/src/runtime/routes/archive-utils.ts +16 -12
- package/src/runtime/routes/attachment-routes.ts +13 -6
- package/src/runtime/routes/canned-reply-release.ts +54 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -27
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
- package/src/runtime/routes/client-routes.ts +114 -2
- package/src/runtime/routes/conversation-cli-routes.ts +10 -12
- package/src/runtime/routes/conversation-list-routes.ts +59 -10
- package/src/runtime/routes/conversation-management-routes.ts +47 -18
- package/src/runtime/routes/conversation-query-routes.ts +32 -53
- package/src/runtime/routes/conversation-routes.ts +360 -293
- package/src/runtime/routes/credential-prompt-routes.ts +23 -2
- package/src/runtime/routes/debug-routes.ts +4 -1
- package/src/runtime/routes/default-provider-routes.ts +7 -11
- package/src/runtime/routes/documents-routes.ts +44 -1
- package/src/runtime/routes/events-routes.ts +28 -1
- package/src/runtime/routes/host-app-control-routes.ts +2 -0
- package/src/runtime/routes/host-bash-routes.ts +2 -0
- package/src/runtime/routes/host-browser-routes.ts +2 -0
- package/src/runtime/routes/host-cu-routes.ts +2 -0
- package/src/runtime/routes/host-file-routes.ts +2 -0
- package/src/runtime/routes/host-transfer-routes.ts +4 -0
- package/src/runtime/routes/identity-routes.ts +6 -6
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
- package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
- package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
- package/src/runtime/routes/inference-profiles-routes.ts +152 -73
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
- package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
- package/src/runtime/routes/log-export-routes.ts +1 -1
- package/src/runtime/routes/notification-routes.ts +0 -14
- package/src/runtime/routes/oauth-providers.ts +1 -0
- package/src/runtime/routes/plugins-routes.ts +106 -1
- package/src/runtime/routes/profiler-routes.ts +4 -2
- package/src/runtime/routes/question-routes.ts +28 -10
- package/src/runtime/routes/retrospective-routes.ts +9 -3
- package/src/runtime/routes/schedule-routes.ts +210 -12
- package/src/runtime/routes/secret-routes.ts +40 -23
- package/src/runtime/routes/settings-routes.ts +41 -13
- package/src/runtime/routes/skills-routes.ts +79 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
- package/src/runtime/routes/wake-conversation-routes.ts +23 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +24 -9
- package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
- package/src/runtime/sync/resource-sync-events.ts +79 -1
- package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
- package/src/runtime/sync/worker-daemon-notify.ts +44 -6
- package/src/runtime/verification-outbound-actions.ts +250 -222
- package/src/runtime/verification-templates.ts +41 -30
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/inference-profile.ts +64 -3
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +520 -26
- package/src/schedule/scheduler.ts +38 -0
- package/src/security/credential-backend.ts +51 -4
- package/src/security/encrypted-store.ts +64 -42
- package/src/security/secure-keys.ts +55 -7
- package/src/skills/catalog-install.ts +20 -7
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/skills/skill-history.test.ts +269 -0
- package/src/skills/skill-history.ts +250 -0
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +8 -6
- package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
- package/src/subagent/consult-prompt.ts +3 -1
- package/src/subagent/consult-transcript.ts +1 -1
- package/src/subagent/manager.ts +304 -44
- package/src/subagent/progress-events.ts +35 -0
- package/src/subagent/role-resolution.ts +88 -0
- package/src/subagent/types.ts +302 -67
- package/src/subagent/validate-allowlists.ts +3 -4
- package/src/telegram/__tests__/webhook-health.test.ts +73 -1
- package/src/telegram/webhook-health.ts +101 -3
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
- package/src/telemetry/activation-funnel.ts +6 -1
- package/src/telemetry/live-voice-funnel.ts +75 -0
- package/src/telemetry/telemetry-event-sources.ts +6 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +2 -0
- package/src/telemetry/types.ts +23 -4
- package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
- package/src/tools/ask-question/ask-question-tool.ts +56 -2
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/calls/call-start.ts +8 -5
- package/src/tools/conversation-groups/move_to_group.ts +10 -12
- package/src/tools/document/document-tool.ts +11 -4
- package/src/tools/execution-timeout.ts +9 -2
- package/src/tools/executor.ts +7 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +7 -13
- package/src/tools/filesystem/search.ts +23 -21
- package/src/tools/filesystem/write.ts +4 -1
- package/src/tools/host-filesystem/edit.ts +7 -2
- package/src/tools/host-filesystem/read.ts +21 -8
- package/src/tools/host-filesystem/write.ts +10 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/mcp/mcp-tool-factory.ts +25 -1
- package/src/tools/permission-checker.ts +8 -3
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +14 -3
- package/src/tools/schedule/update.ts +56 -33
- package/src/tools/shared/filesystem/audio-read.ts +6 -4
- package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
- package/src/tools/shared/filesystem/image-read.ts +12 -12
- package/src/tools/shared/filesystem/path-policy.ts +70 -5
- package/src/tools/shared/filesystem/size-guard.ts +6 -6
- package/src/tools/shared/input-misuse.ts +72 -0
- package/src/tools/skills/scaffold-managed.ts +82 -0
- package/src/tools/skills/skill-tool-factory.ts +14 -1
- package/src/tools/subagent/consult-deadline.ts +12 -7
- package/src/tools/subagent/message.ts +3 -1
- package/src/tools/subagent/read.ts +66 -4
- package/src/tools/subagent/spawn.ts +500 -78
- package/src/tools/tool-approval-handler.ts +45 -4
- package/src/tools/types.ts +22 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
- package/src/tools/ui-surface/visual-validation.ts +2 -0
- package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
- package/src/usage/subagent-attribution.ts +108 -0
- package/src/util/__tests__/sqlite-retry.test.ts +112 -0
- package/src/util/abort-reasons.ts +16 -0
- package/src/util/ansi.ts +44 -0
- package/src/util/disk-usage.ts +17 -13
- package/src/util/image-conversion.ts +20 -13
- package/src/util/schedule-source-key.ts +23 -0
- package/src/util/worker-process.ts +7 -1
- package/src/workspace/git-service.ts +33 -9
- package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +15 -2
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
- package/src/daemon/install-symlink.ts +0 -207
- package/src/notifications/deferred-emit.ts +0 -147
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
lte,
|
|
17
17
|
notInArray,
|
|
18
18
|
or,
|
|
19
|
+
type SQL,
|
|
19
20
|
sql,
|
|
20
21
|
} from "drizzle-orm";
|
|
21
22
|
import { v4 as uuid, v7 as uuidv7 } from "uuid";
|
|
@@ -70,11 +71,24 @@ import {
|
|
|
70
71
|
} from "./conversation-disk-view.js";
|
|
71
72
|
import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
|
|
72
73
|
import { ensureGroupMigration } from "./conversation-group-migration.js";
|
|
74
|
+
import {
|
|
75
|
+
isReferentialFork,
|
|
76
|
+
type LineageBound,
|
|
77
|
+
type LineageConversationRow,
|
|
78
|
+
lineageMessageFilter,
|
|
79
|
+
lineageMessagesAfterFilter,
|
|
80
|
+
type LineageSegment,
|
|
81
|
+
REFERENTIAL_FORK_STRATEGY,
|
|
82
|
+
resolveConversationLineage,
|
|
83
|
+
} from "./conversation-lineage.js";
|
|
73
84
|
import { deleteConversationRowsInBatches } from "./conversation-row-batch-delete.js";
|
|
74
85
|
import {
|
|
75
86
|
BACKGROUND_CONVERSATION_TYPES,
|
|
76
87
|
type ConversationCreateType,
|
|
88
|
+
type ConversationOrigin,
|
|
77
89
|
isHiddenMessageMetadata,
|
|
90
|
+
PINNED_GROUP_ID,
|
|
91
|
+
UNGROUPED_GROUP_ID,
|
|
78
92
|
} from "./conversation-types.js";
|
|
79
93
|
import { runAsyncSqlite } from "./db-async-query.js";
|
|
80
94
|
import {
|
|
@@ -252,9 +266,10 @@ export const messageMetadataSchema = z
|
|
|
252
266
|
/**
|
|
253
267
|
* Optional client-side metadata bag attached to user messages at persist
|
|
254
268
|
* time. `os` carries the client-reported OS surface ("web" | "ios" |
|
|
255
|
-
* "macos" | "android") from the request body's `clientOs`
|
|
256
|
-
* by `persistQueuedMessageBody
|
|
257
|
-
*
|
|
269
|
+
* "macos" | "windows" | "android") from the request body's `clientOs`
|
|
270
|
+
* field, stamped by `persistQueuedMessageBody`. The transport
|
|
271
|
+
* `userMessageInterface` is
|
|
272
|
+
* "web" for the web, mobile, and desktop apps alike, so this is the only
|
|
258
273
|
* per-platform attribution. `browser_family` / `browser_version` /
|
|
259
274
|
* `interface_version` (and an `os` override) come from the sanitized
|
|
260
275
|
* `x-vellum-*` client-metadata headers read by `handleSendMessage`
|
|
@@ -264,6 +279,20 @@ export const messageMetadataSchema = z
|
|
|
264
279
|
* require a migration -- dbt can unpack later via JSON_VALUE.
|
|
265
280
|
*/
|
|
266
281
|
client: z.record(z.string(), z.unknown()).optional(),
|
|
282
|
+
/**
|
|
283
|
+
* True when the `client.os` above was reported by this row's own request
|
|
284
|
+
* or transport rather than inherited from the conversation's live client
|
|
285
|
+
* state. `Conversation.clientOs` is refreshed only by a message that
|
|
286
|
+
* carries transport metadata, so a transport-less turn (surface action,
|
|
287
|
+
* signal ingress) stamps the OS of an earlier send. Consumers that must
|
|
288
|
+
* not misattribute a turn to a surface require this marker (the
|
|
289
|
+
* `chat.assistant_reply` presence gate, which drops the push when the Mac
|
|
290
|
+
* that opened the turn is attended). Absent reads as "origin unknown".
|
|
291
|
+
* Deliberately a sibling of `client` rather than an entry inside it:
|
|
292
|
+
* `turn-events-store` forwards `$.client` verbatim onto
|
|
293
|
+
* `TurnTelemetryEvent.client`.
|
|
294
|
+
*/
|
|
295
|
+
clientOsFromRequest: z.boolean().optional(),
|
|
267
296
|
subagentNotification: subagentNotificationSchema.optional(),
|
|
268
297
|
acpNotification: acpNotificationSchema.optional(),
|
|
269
298
|
/**
|
|
@@ -379,6 +408,7 @@ export {
|
|
|
379
408
|
isBackgroundEventMetadata,
|
|
380
409
|
isEchoSuppressedUserMessage,
|
|
381
410
|
isHiddenMessageMetadata,
|
|
411
|
+
isSuppressedQueuedMessage,
|
|
382
412
|
isVoiceSessionUserMessage,
|
|
383
413
|
} from "./conversation-types.js";
|
|
384
414
|
|
|
@@ -571,6 +601,8 @@ export interface ConversationRow {
|
|
|
571
601
|
originInterface: string | null;
|
|
572
602
|
forkParentConversationId: string | null;
|
|
573
603
|
forkParentMessageId: string | null;
|
|
604
|
+
/** `"reference"` on referential forks; `"cloning"` or null on copied ones. */
|
|
605
|
+
forkStrategy: string | null;
|
|
574
606
|
isAutoTitle: number;
|
|
575
607
|
scheduleJobId: string | null;
|
|
576
608
|
lastMessageAt: number | null;
|
|
@@ -611,6 +643,7 @@ export const parseConversation = createRowMapper<
|
|
|
611
643
|
originInterface: "originInterface",
|
|
612
644
|
forkParentConversationId: "forkParentConversationId",
|
|
613
645
|
forkParentMessageId: "forkParentMessageId",
|
|
646
|
+
forkStrategy: "forkStrategy",
|
|
614
647
|
isAutoTitle: "isAutoTitle",
|
|
615
648
|
scheduleJobId: "scheduleJobId",
|
|
616
649
|
lastMessageAt: "lastMessageAt",
|
|
@@ -951,6 +984,17 @@ export function createConversation(
|
|
|
951
984
|
source?: string;
|
|
952
985
|
scheduleJobId?: string;
|
|
953
986
|
groupId?: string;
|
|
987
|
+
/**
|
|
988
|
+
* Where this conversation came from. Stamped into
|
|
989
|
+
* `origin_channel` at insert, so the row is attributed from the
|
|
990
|
+
* moment it exists.
|
|
991
|
+
*
|
|
992
|
+
* Pass it whenever the origin is known. Omitting it leaves the
|
|
993
|
+
* column unset, and the conversation is instead attributed by
|
|
994
|
+
* `setConversationOriginChannelIfUnset` when its first inbound
|
|
995
|
+
* message arrives.
|
|
996
|
+
*/
|
|
997
|
+
origin?: ConversationOrigin;
|
|
954
998
|
forkParentConversationId?: string;
|
|
955
999
|
/**
|
|
956
1000
|
* Id of the conversation that spawned this one (subagent spawns).
|
|
@@ -958,6 +1002,14 @@ export function createConversation(
|
|
|
958
1002
|
* `forkParentConversationId` it implies no history inheritance.
|
|
959
1003
|
*/
|
|
960
1004
|
parentConversationId?: string;
|
|
1005
|
+
/**
|
|
1006
|
+
* Role of the subagent that owns this conversation, and how it was
|
|
1007
|
+
* spawned. Persisted here (not only on the ephemeral `subagents` row)
|
|
1008
|
+
* so usage telemetry can decompose delegated spend by variety long
|
|
1009
|
+
* after the subagent record is disposed. See migration 362.
|
|
1010
|
+
*/
|
|
1011
|
+
subagentRole?: string;
|
|
1012
|
+
subagentSpawnMode?: string;
|
|
961
1013
|
},
|
|
962
1014
|
) {
|
|
963
1015
|
const db = getDb();
|
|
@@ -972,6 +1024,7 @@ export function createConversation(
|
|
|
972
1024
|
requestedConversationType ?? "standard";
|
|
973
1025
|
const source = opts.source ?? "user";
|
|
974
1026
|
const groupId = opts.groupId;
|
|
1027
|
+
const originChannel = resolveConversationOrigin(opts.origin);
|
|
975
1028
|
// Time-ordered UUIDv7 for server-minted conversation ids (see message id).
|
|
976
1029
|
const id = opts.id ?? uuidv7();
|
|
977
1030
|
|
|
@@ -998,9 +1051,12 @@ export function createConversation(
|
|
|
998
1051
|
slackContextCompactionWatermarkAt: null as number | null,
|
|
999
1052
|
conversationType,
|
|
1000
1053
|
source,
|
|
1054
|
+
originChannel,
|
|
1001
1055
|
scheduleJobId: opts.scheduleJobId ?? null,
|
|
1002
1056
|
forkParentConversationId: opts.forkParentConversationId ?? null,
|
|
1003
1057
|
parentConversationId: opts.parentConversationId ?? null,
|
|
1058
|
+
subagentRole: opts.subagentRole ?? null,
|
|
1059
|
+
subagentSpawnMode: opts.subagentSpawnMode ?? null,
|
|
1004
1060
|
// Snapshot↔stream alignment baseline, captured at the creation instant.
|
|
1005
1061
|
// 0 (nothing stamped yet this process) is stored as NULL so `/messages`
|
|
1006
1062
|
// reports null and the client cold-starts rather than aligning to seq 0.
|
|
@@ -1016,7 +1072,7 @@ export function createConversation(
|
|
|
1016
1072
|
// write-contended paths wrap the call in `withSqliteRetry`, and because the
|
|
1017
1073
|
// insert+update are atomic here, such a retry re-runs the whole thing cleanly
|
|
1018
1074
|
// (a failed attempt rolls back, so no half-written row is ever left behind).
|
|
1019
|
-
const effectiveGroupId = groupId ??
|
|
1075
|
+
const effectiveGroupId = groupId ?? UNGROUPED_GROUP_ID;
|
|
1020
1076
|
const raw = getSqliteFrom(db);
|
|
1021
1077
|
raw.exec("SAVEPOINT create_conv");
|
|
1022
1078
|
try {
|
|
@@ -1025,7 +1081,7 @@ export function createConversation(
|
|
|
1025
1081
|
"conversation:create:setGroup",
|
|
1026
1082
|
"UPDATE conversations SET group_id = ?, is_pinned = ? WHERE id = ?",
|
|
1027
1083
|
effectiveGroupId,
|
|
1028
|
-
effectiveGroupId ===
|
|
1084
|
+
effectiveGroupId === PINNED_GROUP_ID ? 1 : 0,
|
|
1029
1085
|
id,
|
|
1030
1086
|
);
|
|
1031
1087
|
raw.exec("RELEASE create_conv");
|
|
@@ -1035,11 +1091,55 @@ export function createConversation(
|
|
|
1035
1091
|
throw err;
|
|
1036
1092
|
}
|
|
1037
1093
|
|
|
1038
|
-
|
|
1094
|
+
// The disk view records the same origin the row holds.
|
|
1095
|
+
initConversationDir(conversation);
|
|
1039
1096
|
|
|
1040
1097
|
return conversation;
|
|
1041
1098
|
}
|
|
1042
1099
|
|
|
1100
|
+
/**
|
|
1101
|
+
* Resolve a caller's {@link ConversationOrigin} to the string stored in
|
|
1102
|
+
* `origin_channel`.
|
|
1103
|
+
*
|
|
1104
|
+
* `inheritFrom` reads the parent's value, which is how a fork, a subagent
|
|
1105
|
+
* spawn, or a scheduled wake lands on the surface its parent belongs to.
|
|
1106
|
+
*
|
|
1107
|
+
* A parent whose own origin is unset yields an unset origin: the child
|
|
1108
|
+
* inherits the parent's state faithfully, including "not yet attributed".
|
|
1109
|
+
* Most rows are in exactly that state until they are claimed by a message or
|
|
1110
|
+
* swept at startup, so treating it as an error would fail the majority of
|
|
1111
|
+
* forks and subagent spawns.
|
|
1112
|
+
*
|
|
1113
|
+
* A parent that does not exist throws. That is not the same condition: it
|
|
1114
|
+
* means a caller named a conversation that is not there, and guessing an
|
|
1115
|
+
* origin for it would be a trust grant rather than a cosmetic default.
|
|
1116
|
+
* `recoverRestingTrustContext` reads this column, and the native channel
|
|
1117
|
+
* recovers `INTERNAL_GUARDIAN_TRUST_CONTEXT` on every later wake and
|
|
1118
|
+
* boot-resume, so a fork of a remote conversation that silently defaulted
|
|
1119
|
+
* would come back as the guardian's own. Parent ids here come from daemon
|
|
1120
|
+
* internals rather than user input, so a missing one is a programming error.
|
|
1121
|
+
*
|
|
1122
|
+
* `undefined` maps to NULL, leaving the row for first-message attribution.
|
|
1123
|
+
*/
|
|
1124
|
+
function resolveConversationOrigin(
|
|
1125
|
+
origin: ConversationOrigin | undefined,
|
|
1126
|
+
): string | null {
|
|
1127
|
+
if (origin === undefined) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
if (typeof origin === "string") {
|
|
1131
|
+
return origin;
|
|
1132
|
+
}
|
|
1133
|
+
const parent = getConversation(origin.inheritFrom);
|
|
1134
|
+
if (!parent) {
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`Cannot inherit conversation origin from ${origin.inheritFrom}: ` +
|
|
1137
|
+
"no such conversation",
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return parent.originChannel;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1043
1143
|
/**
|
|
1044
1144
|
* A conversation id adopted verbatim from an untrusted source must be safe to
|
|
1045
1145
|
* embed as a single path component of the on-disk conversation dir
|
|
@@ -1069,7 +1169,10 @@ export const ADOPTABLE_CONVERSATION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
|
1069
1169
|
* is validated first — a value like `../../tmp/x` would otherwise write
|
|
1070
1170
|
* `meta.json` outside the conversations directory.
|
|
1071
1171
|
*/
|
|
1072
|
-
export function ensureConversationExists(
|
|
1172
|
+
export function ensureConversationExists(
|
|
1173
|
+
id: string,
|
|
1174
|
+
origin: ConversationOrigin | undefined,
|
|
1175
|
+
): boolean {
|
|
1073
1176
|
if (getConversation(id)) {
|
|
1074
1177
|
return false;
|
|
1075
1178
|
}
|
|
@@ -1079,7 +1182,7 @@ export function ensureConversationExists(id: string): boolean {
|
|
|
1079
1182
|
);
|
|
1080
1183
|
}
|
|
1081
1184
|
try {
|
|
1082
|
-
createConversation({ id });
|
|
1185
|
+
createConversation({ id, origin });
|
|
1083
1186
|
return true;
|
|
1084
1187
|
} catch (err) {
|
|
1085
1188
|
// A concurrent caller may have created the row between the check and the
|
|
@@ -1701,9 +1804,26 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1701
1804
|
const hiddenRowIds = new Set(
|
|
1702
1805
|
loadOrderIds.slice(0, inheritedCompaction?.compactedMessageCount ?? 0),
|
|
1703
1806
|
);
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1807
|
+
// Read straight from config rather than taking a caller-supplied strategy:
|
|
1808
|
+
// how a fork is materialized is a workspace-wide storage decision, and a
|
|
1809
|
+
// per-call override would let two callers disagree about it on the same
|
|
1810
|
+
// workspace.
|
|
1811
|
+
const isReferential =
|
|
1812
|
+
getConfig().memory.retrospective.forkStrategy === "reference";
|
|
1813
|
+
// A referential fork copies nothing: its inherited window is read back
|
|
1814
|
+
// through `forkParentMessageId` by the lineage resolver.
|
|
1815
|
+
const rowsToCopy = isReferential
|
|
1816
|
+
? []
|
|
1817
|
+
: messagesToCopy.filter((message) => !hiddenRowIds.has(message.id));
|
|
1818
|
+
// The compacted prefix is dropped from a copied fork, so its own row count
|
|
1819
|
+
// behind the boundary is 0. A referential fork reads the source's rows
|
|
1820
|
+
// through the fork point, prefix included, so it must carry the source's
|
|
1821
|
+
// own count for the render to hide the same rows the source hides. Getting
|
|
1822
|
+
// this wrong does not lose data, it re-shows compacted history the summary
|
|
1823
|
+
// already covers.
|
|
1824
|
+
const forkCompactedMessageCount = isReferential
|
|
1825
|
+
? (inheritedCompaction?.compactedMessageCount ?? 0)
|
|
1826
|
+
: 0;
|
|
1707
1827
|
const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
|
|
1708
1828
|
const forkTitle =
|
|
1709
1829
|
params.title ?? `${sourceConversation.title ?? "Untitled"} (Fork)`;
|
|
@@ -1744,11 +1864,12 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1744
1864
|
// value from the copied rows when the tail is non-empty.
|
|
1745
1865
|
lastMessageAt: boundaryMessageCreatedAt,
|
|
1746
1866
|
contextSummary: inheritedCompaction?.summary ?? null,
|
|
1747
|
-
//
|
|
1748
|
-
// compacted prefix is not copied). The summary still
|
|
1749
|
-
// the history render keys on `contextSummary` presence,
|
|
1750
|
-
// this count.
|
|
1751
|
-
contextCompactedMessageCount:
|
|
1867
|
+
// On a copied fork zero of its own rows sit behind the boundary
|
|
1868
|
+
// (the compacted prefix is not copied). The summary still
|
|
1869
|
+
// renders: the history render keys on `contextSummary` presence,
|
|
1870
|
+
// not on this count.
|
|
1871
|
+
contextCompactedMessageCount: forkCompactedMessageCount,
|
|
1872
|
+
forkStrategy: isReferential ? REFERENTIAL_FORK_STRATEGY : null,
|
|
1752
1873
|
contextCompactedAt: inheritedCompaction?.compactedAt ?? null,
|
|
1753
1874
|
slackContextCompactionWatermarkTs: inheritsLatestCompaction
|
|
1754
1875
|
? sourceConversation.slackContextCompactionWatermarkTs
|
|
@@ -1773,15 +1894,20 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1773
1894
|
|
|
1774
1895
|
try {
|
|
1775
1896
|
// Phase 2 (off the event loop): copy the message rows in a sqlite3
|
|
1776
|
-
// subprocess so the daemon stays responsive during the heavy copy.
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
);
|
|
1897
|
+
// subprocess so the daemon stays responsive during the heavy copy. The
|
|
1898
|
+
// referential fork has no rows to copy, which is the whole point of it:
|
|
1899
|
+
// the write burst this phase exists to keep off the event loop does not
|
|
1900
|
+
// happen at all.
|
|
1901
|
+
if (!isReferential) {
|
|
1902
|
+
const copy = await copyForkMessagesViaSubprocess({
|
|
1903
|
+
forkConversationId: fork.id,
|
|
1904
|
+
idPairs,
|
|
1905
|
+
});
|
|
1906
|
+
if (!copy.ok) {
|
|
1907
|
+
throw new Error(
|
|
1908
|
+
`fork message copy failed (${copy.backend}): ${copy.error ?? "unknown"}`,
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1785
1911
|
}
|
|
1786
1912
|
|
|
1787
1913
|
// Phase 3 (in-process): attachments + memory-state seeding, reusing the
|
|
@@ -1813,7 +1939,7 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1813
1939
|
isFullHistoryFork:
|
|
1814
1940
|
copyBoundaryIndex === sourceMessages.length - 1,
|
|
1815
1941
|
// The copied range already starts at the visible window.
|
|
1816
|
-
inheritedCompactedMessageCount:
|
|
1942
|
+
inheritedCompactedMessageCount: forkCompactedMessageCount,
|
|
1817
1943
|
skipCompactionLedgerCopy: inheritedCompaction != null,
|
|
1818
1944
|
});
|
|
1819
1945
|
// The fork owns none of the source's ledger events — their
|
|
@@ -1831,7 +1957,7 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1831
1957
|
appendCompactionEvent(fork.id, {
|
|
1832
1958
|
compactedAt: inheritedCompaction.compactedAt,
|
|
1833
1959
|
summary: inheritedCompaction.summary,
|
|
1834
|
-
compactedMessageCount:
|
|
1960
|
+
compactedMessageCount: forkCompactedMessageCount,
|
|
1835
1961
|
});
|
|
1836
1962
|
}
|
|
1837
1963
|
},
|
|
@@ -1862,6 +1988,33 @@ export async function forkConversationForRetrospective(params: {
|
|
|
1862
1988
|
* artifacts (embeddings). Returns segment IDs so callers can clean up
|
|
1863
1989
|
* the corresponding Qdrant vector entries.
|
|
1864
1990
|
*/
|
|
1991
|
+
/**
|
|
1992
|
+
* Whether this conversation reads its inherited history from a parent that no
|
|
1993
|
+
* longer exists.
|
|
1994
|
+
*
|
|
1995
|
+
* Deleting a conversation ORPHANS the referential forks taken from it: the
|
|
1996
|
+
* delete touches only what it names, and each fork keeps the rows it owns
|
|
1997
|
+
* while the lineage walk truncates at the missing parent. Cascading would make
|
|
1998
|
+
* a delete cost time proportional to how many forks were ever taken from a
|
|
1999
|
+
* conversation, and refusing would make most active conversations undeletable,
|
|
2000
|
+
* since a retrospective forks every few minutes. Neither is worth paying on
|
|
2001
|
+
* every delete for a state that renders fine and can be explained.
|
|
2002
|
+
*
|
|
2003
|
+
* Explaining it is what this is for: an orphan otherwise looks like a
|
|
2004
|
+
* conversation that mysteriously starts mid-thought. Costs nothing on ordinary
|
|
2005
|
+
* conversations, which fail `isReferentialFork` before any query runs.
|
|
2006
|
+
*/
|
|
2007
|
+
export function isReferentialHistoryOrphaned(row: {
|
|
2008
|
+
forkStrategy: string | null;
|
|
2009
|
+
forkParentConversationId: string | null;
|
|
2010
|
+
forkParentMessageId: string | null;
|
|
2011
|
+
}): boolean {
|
|
2012
|
+
if (!isReferentialFork(row)) {
|
|
2013
|
+
return false;
|
|
2014
|
+
}
|
|
2015
|
+
return getConversation(row.forkParentConversationId as string) === null;
|
|
2016
|
+
}
|
|
2017
|
+
|
|
1865
2018
|
export function deleteConversation(id: string): DeletedMemoryIds {
|
|
1866
2019
|
const db = getDb();
|
|
1867
2020
|
const result: DeletedMemoryIds = {
|
|
@@ -2198,18 +2351,73 @@ export async function addMessage(
|
|
|
2198
2351
|
return message;
|
|
2199
2352
|
}
|
|
2200
2353
|
|
|
2354
|
+
/**
|
|
2355
|
+
* Load only the columns the lineage walk reads. A conversation-row primary-key
|
|
2356
|
+
* lookup per message read is the cost of referential forks; selecting four
|
|
2357
|
+
* columns instead of the full row keeps it to an index hit.
|
|
2358
|
+
*/
|
|
2359
|
+
function loadLineageRow(id: string): LineageConversationRow | null {
|
|
2360
|
+
const row = getDb()
|
|
2361
|
+
.select({
|
|
2362
|
+
id: conversations.id,
|
|
2363
|
+
forkStrategy: conversations.forkStrategy,
|
|
2364
|
+
forkParentConversationId: conversations.forkParentConversationId,
|
|
2365
|
+
forkParentMessageId: conversations.forkParentMessageId,
|
|
2366
|
+
})
|
|
2367
|
+
.from(conversations)
|
|
2368
|
+
.where(eq(conversations.id, id))
|
|
2369
|
+
.get();
|
|
2370
|
+
return row ?? null;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
/** The `(createdAt, id)` bound of a single message, or null when it is gone. */
|
|
2374
|
+
function loadMessageBound(messageId: string): LineageBound | null {
|
|
2375
|
+
const row = getDb()
|
|
2376
|
+
.select({ createdAt: messages.createdAt, id: messages.id })
|
|
2377
|
+
.from(messages)
|
|
2378
|
+
.where(eq(messages.id, messageId))
|
|
2379
|
+
.get();
|
|
2380
|
+
return row ?? null;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/**
|
|
2384
|
+
* The segments making up a conversation's logical message set. Non-forks and
|
|
2385
|
+
* copied forks resolve to a single unbounded segment after one row lookup, so
|
|
2386
|
+
* the referential path costs them nothing beyond that lookup.
|
|
2387
|
+
*/
|
|
2388
|
+
function resolveLineage(conversationId: string): LineageSegment[] {
|
|
2389
|
+
return resolveConversationLineage(conversationId, {
|
|
2390
|
+
loadConversation: loadLineageRow,
|
|
2391
|
+
loadMessageBound,
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/** `messages` predicate covering every row a conversation logically owns. */
|
|
2396
|
+
function lineageFilter(conversationId: string): SQL {
|
|
2397
|
+
return lineageMessageFilter(resolveLineage(conversationId));
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2201
2400
|
export function getMessages(conversationId: string): MessageRow[] {
|
|
2202
2401
|
const db = getDb();
|
|
2203
2402
|
// Synchronous read of every row for the conversation — the dominant
|
|
2204
2403
|
// per-turn main-thread cost on large conversations. Timed so a freeze the
|
|
2205
2404
|
// event-loop watchdog detects can be attributed here (see slow-sync-log).
|
|
2405
|
+
//
|
|
2406
|
+
// Ordered by `createdAt` ALONE. Rows sharing a millisecond fall back to
|
|
2407
|
+
// SQLite's rowid order, which is insertion order, and callers depend on
|
|
2408
|
+
// that: the compaction render slices a row count off the front of this
|
|
2409
|
+
// exact order, and a fork's copied prefix must match the order the source
|
|
2410
|
+
// renders. Adding an `id` tiebreak here reorders same-millisecond rows and
|
|
2411
|
+
// silently shifts that boundary. A lineage read inherits the same rule, and
|
|
2412
|
+
// an ancestor's rows sort ahead of the fork's own on a tie because they were
|
|
2413
|
+
// inserted first.
|
|
2206
2414
|
return timeSyncSection(
|
|
2207
2415
|
"conversation-crud:get-messages",
|
|
2208
2416
|
() =>
|
|
2209
2417
|
db
|
|
2210
2418
|
.select()
|
|
2211
2419
|
.from(messages)
|
|
2212
|
-
.where(
|
|
2420
|
+
.where(lineageFilter(conversationId))
|
|
2213
2421
|
.orderBy(asc(messages.createdAt))
|
|
2214
2422
|
.all()
|
|
2215
2423
|
.map(parseMessage),
|
|
@@ -2242,7 +2450,7 @@ export function selectSlackMetaCandidateMetadata(
|
|
|
2242
2450
|
.from(messages)
|
|
2243
2451
|
.where(
|
|
2244
2452
|
and(
|
|
2245
|
-
|
|
2453
|
+
lineageFilter(conversationId),
|
|
2246
2454
|
opts?.includeFlatLegacy
|
|
2247
2455
|
? or(
|
|
2248
2456
|
like(messages.metadata, '%"slackMeta"%'),
|
|
@@ -2280,19 +2488,16 @@ export function countMessagesAfter(
|
|
|
2280
2488
|
afterMessageId: string | null,
|
|
2281
2489
|
): number {
|
|
2282
2490
|
const db = getDb();
|
|
2491
|
+
const segments = resolveLineage(conversationId);
|
|
2283
2492
|
if (afterMessageId === null || afterMessageId === "") {
|
|
2284
2493
|
const row = db
|
|
2285
2494
|
.select({ c: count() })
|
|
2286
2495
|
.from(messages)
|
|
2287
|
-
.where(
|
|
2496
|
+
.where(lineageMessageFilter(segments))
|
|
2288
2497
|
.get();
|
|
2289
2498
|
return row?.c ?? 0;
|
|
2290
2499
|
}
|
|
2291
|
-
const ref =
|
|
2292
|
-
.select({ createdAt: messages.createdAt })
|
|
2293
|
-
.from(messages)
|
|
2294
|
-
.where(eq(messages.id, afterMessageId))
|
|
2295
|
-
.get();
|
|
2500
|
+
const ref = loadMessageBound(afterMessageId);
|
|
2296
2501
|
if (!ref) {
|
|
2297
2502
|
return 0;
|
|
2298
2503
|
}
|
|
@@ -2303,18 +2508,7 @@ export function countMessagesAfter(
|
|
|
2303
2508
|
const row = db
|
|
2304
2509
|
.select({ c: count() })
|
|
2305
2510
|
.from(messages)
|
|
2306
|
-
.where(
|
|
2307
|
-
and(
|
|
2308
|
-
eq(messages.conversationId, conversationId),
|
|
2309
|
-
or(
|
|
2310
|
-
gt(messages.createdAt, ref.createdAt),
|
|
2311
|
-
and(
|
|
2312
|
-
eq(messages.createdAt, ref.createdAt),
|
|
2313
|
-
gt(messages.id, afterMessageId),
|
|
2314
|
-
),
|
|
2315
|
-
),
|
|
2316
|
-
),
|
|
2317
|
-
)
|
|
2511
|
+
.where(lineageMessagesAfterFilter(segments, ref))
|
|
2318
2512
|
.get();
|
|
2319
2513
|
return row?.c ?? 0;
|
|
2320
2514
|
}
|
|
@@ -2331,6 +2525,7 @@ export function getMessagesAfter(
|
|
|
2331
2525
|
afterMessageId: string | null,
|
|
2332
2526
|
): MessageRow[] {
|
|
2333
2527
|
const db = getDb();
|
|
2528
|
+
const segments = resolveLineage(conversationId);
|
|
2334
2529
|
if (afterMessageId === null || afterMessageId === "") {
|
|
2335
2530
|
// Secondary `asc(messages.id)` matches the non-null path's cursor
|
|
2336
2531
|
// ordering, so callers tracking `cutoffMessageId` across runs see a
|
|
@@ -2338,16 +2533,12 @@ export function getMessagesAfter(
|
|
|
2338
2533
|
return db
|
|
2339
2534
|
.select()
|
|
2340
2535
|
.from(messages)
|
|
2341
|
-
.where(
|
|
2536
|
+
.where(lineageMessageFilter(segments))
|
|
2342
2537
|
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
2343
2538
|
.all()
|
|
2344
2539
|
.map(parseMessage);
|
|
2345
2540
|
}
|
|
2346
|
-
const ref =
|
|
2347
|
-
.select({ createdAt: messages.createdAt })
|
|
2348
|
-
.from(messages)
|
|
2349
|
-
.where(eq(messages.id, afterMessageId))
|
|
2350
|
-
.get();
|
|
2541
|
+
const ref = loadMessageBound(afterMessageId);
|
|
2351
2542
|
if (!ref) {
|
|
2352
2543
|
return [];
|
|
2353
2544
|
}
|
|
@@ -2356,18 +2547,7 @@ export function getMessagesAfter(
|
|
|
2356
2547
|
return db
|
|
2357
2548
|
.select()
|
|
2358
2549
|
.from(messages)
|
|
2359
|
-
.where(
|
|
2360
|
-
and(
|
|
2361
|
-
eq(messages.conversationId, conversationId),
|
|
2362
|
-
or(
|
|
2363
|
-
gt(messages.createdAt, ref.createdAt),
|
|
2364
|
-
and(
|
|
2365
|
-
eq(messages.createdAt, ref.createdAt),
|
|
2366
|
-
gt(messages.id, afterMessageId),
|
|
2367
|
-
),
|
|
2368
|
-
),
|
|
2369
|
-
),
|
|
2370
|
-
)
|
|
2550
|
+
.where(lineageMessagesAfterFilter(segments, ref))
|
|
2371
2551
|
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
2372
2552
|
.all()
|
|
2373
2553
|
.map(parseMessage);
|
|
@@ -2383,7 +2563,7 @@ export function hasMessages(conversationId: string): boolean {
|
|
|
2383
2563
|
const row = db
|
|
2384
2564
|
.select({ one: sql`1` })
|
|
2385
2565
|
.from(messages)
|
|
2386
|
-
.where(
|
|
2566
|
+
.where(lineageFilter(conversationId))
|
|
2387
2567
|
.limit(1)
|
|
2388
2568
|
.get();
|
|
2389
2569
|
return row !== undefined;
|
|
@@ -2424,8 +2604,10 @@ export function getMessagesPaginated(
|
|
|
2424
2604
|
): PaginatedMessagesResult {
|
|
2425
2605
|
const db = getDb();
|
|
2426
2606
|
|
|
2607
|
+
const segments = resolveLineage(conversationId);
|
|
2608
|
+
|
|
2427
2609
|
if (limit === undefined) {
|
|
2428
|
-
const conditions = [
|
|
2610
|
+
const conditions = [lineageMessageFilter(segments)];
|
|
2429
2611
|
if (beforeTimestamp !== undefined) {
|
|
2430
2612
|
conditions.push(lt(messages.createdAt, beforeTimestamp));
|
|
2431
2613
|
}
|
|
@@ -2485,7 +2667,7 @@ export function getMessagesPaginated(
|
|
|
2485
2667
|
const chunk = db
|
|
2486
2668
|
.select()
|
|
2487
2669
|
.from(messages)
|
|
2488
|
-
.where(and(
|
|
2670
|
+
.where(and(lineageMessageFilter(segments), cursorPredicate))
|
|
2489
2671
|
.orderBy(desc(messages.createdAt), desc(messages.id))
|
|
2490
2672
|
.limit(chunkSize)
|
|
2491
2673
|
.all()
|
|
@@ -2824,6 +3006,21 @@ export function isConversationProcessing(id: string): boolean {
|
|
|
2824
3006
|
return row?.processing_started_at != null;
|
|
2825
3007
|
}
|
|
2826
3008
|
|
|
3009
|
+
/**
|
|
3010
|
+
* The persisted `processing_started_at` stamp, or null when idle or missing.
|
|
3011
|
+
* Deliberately reads only the column (no in-memory fallback): consumers use
|
|
3012
|
+
* it to age a flag {@link isConversationProcessing} already reported set, and
|
|
3013
|
+
* the column is the only signal that carries a start time.
|
|
3014
|
+
*/
|
|
3015
|
+
export function getConversationProcessingStartedAt(id: string): number | null {
|
|
3016
|
+
const row = rawGet<{ processing_started_at: number | null }>(
|
|
3017
|
+
"conversation:getProcessingStartedAt",
|
|
3018
|
+
"SELECT processing_started_at FROM conversations WHERE id = ?",
|
|
3019
|
+
id,
|
|
3020
|
+
);
|
|
3021
|
+
return row?.processing_started_at ?? null;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2827
3024
|
/**
|
|
2828
3025
|
* Conversations currently mid-turn, longest-running first. Throws on a read
|
|
2829
3026
|
* failure — drain callers must distinguish "nothing processing" from "could
|
|
@@ -3214,6 +3411,50 @@ export function getConversationOverrideProfile(
|
|
|
3214
3411
|
return resolveOverrideProfile(getConversation(conversationId));
|
|
3215
3412
|
}
|
|
3216
3413
|
|
|
3414
|
+
/**
|
|
3415
|
+
* The conversation fields needed to tell a durable inference-profile pin from
|
|
3416
|
+
* a session-backed one. Extends {@link OverrideProfileFields} with the session
|
|
3417
|
+
* marker, so callers can decide from a {@link ConversationRow} or a live
|
|
3418
|
+
* in-memory `Conversation` without a redundant row fetch.
|
|
3419
|
+
*/
|
|
3420
|
+
export interface DurableProfileFields extends OverrideProfileFields {
|
|
3421
|
+
inferenceProfileSessionId?: string | null;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
/**
|
|
3425
|
+
* Resolve a conversation's inference-profile pin only when the user set it to
|
|
3426
|
+
* stick, ignoring a TTL-limited profile session.
|
|
3427
|
+
*
|
|
3428
|
+
* {@link resolveOverrideProfile} is read at use time, so a lapsed session
|
|
3429
|
+
* simply reads as absent on the next turn. Anything that copies a pin into a
|
|
3430
|
+
* record outliving that read (a schedule row that fires days later) needs the
|
|
3431
|
+
* stricter question: a copied session would keep billing the model the user
|
|
3432
|
+
* picked for ten minutes long after the session ended.
|
|
3433
|
+
*
|
|
3434
|
+
* A session-backed pin carries a non-null `inferenceProfileSessionId`;
|
|
3435
|
+
* {@link setConversationInferenceProfile}, the sticky setter, clears those
|
|
3436
|
+
* columns. So a null session id is exactly the durable case.
|
|
3437
|
+
*/
|
|
3438
|
+
export function resolveDurableProfile(
|
|
3439
|
+
fields: DurableProfileFields | null,
|
|
3440
|
+
): string | undefined {
|
|
3441
|
+
if (fields?.inferenceProfileSessionId != null) {
|
|
3442
|
+
return undefined;
|
|
3443
|
+
}
|
|
3444
|
+
return resolveOverrideProfile(fields);
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
/**
|
|
3448
|
+
* Resolve a conversation's durable inference-profile pin by id. Convenience
|
|
3449
|
+
* wrapper over {@link resolveDurableProfile} for callers that don't already
|
|
3450
|
+
* hold the row.
|
|
3451
|
+
*/
|
|
3452
|
+
export function getConversationDurableProfile(
|
|
3453
|
+
conversationId: string,
|
|
3454
|
+
): string | undefined {
|
|
3455
|
+
return resolveDurableProfile(getConversation(conversationId));
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3217
3458
|
export function setLastNotifiedInferenceProfile(
|
|
3218
3459
|
conversationId: string,
|
|
3219
3460
|
profileKey: string | null,
|
|
@@ -3968,13 +4209,19 @@ export function getConversationRecentProvenanceTrustClass(
|
|
|
3968
4209
|
}
|
|
3969
4210
|
|
|
3970
4211
|
// ---------------------------------------------------------------------------
|
|
3971
|
-
//
|
|
4212
|
+
// Conversation placement (group + pin) and display metadata
|
|
3972
4213
|
// ---------------------------------------------------------------------------
|
|
3973
4214
|
|
|
3974
|
-
|
|
4215
|
+
/**
|
|
4216
|
+
* Move conversations between sections: set the group, and the pin state
|
|
4217
|
+
* derived from it.
|
|
4218
|
+
*
|
|
4219
|
+
* Writes no ordering. Every list is ordered by recency, so a conversation's
|
|
4220
|
+
* placement is which section holds it, not where it sits inside one.
|
|
4221
|
+
*/
|
|
4222
|
+
export function batchSetConversationPlacement(
|
|
3975
4223
|
updates: Array<{
|
|
3976
4224
|
id: string;
|
|
3977
|
-
displayOrder: number | null;
|
|
3978
4225
|
isPinned?: boolean;
|
|
3979
4226
|
groupId?: string | null;
|
|
3980
4227
|
}>,
|
|
@@ -3992,15 +4239,15 @@ export function batchSetDisplayOrders(
|
|
|
3992
4239
|
// the entire batch.
|
|
3993
4240
|
let safeGroupId = update.groupId;
|
|
3994
4241
|
if (safeGroupId === null) {
|
|
3995
|
-
safeGroupId =
|
|
4242
|
+
safeGroupId = UNGROUPED_GROUP_ID;
|
|
3996
4243
|
} else if (
|
|
3997
4244
|
!rawGet<{ id: string }>(
|
|
3998
|
-
"conversation:
|
|
4245
|
+
"conversation:batchSetConversationPlacement:groupCheck",
|
|
3999
4246
|
"SELECT id FROM conversation_groups WHERE id = ?",
|
|
4000
4247
|
safeGroupId,
|
|
4001
4248
|
)
|
|
4002
4249
|
) {
|
|
4003
|
-
safeGroupId =
|
|
4250
|
+
safeGroupId = UNGROUPED_GROUP_ID;
|
|
4004
4251
|
}
|
|
4005
4252
|
// Moving a conversation into the Scheduled/Background system groups
|
|
4006
4253
|
// is an explicit demotion out of Recents, so clear any `surfaced_at`
|
|
@@ -4010,37 +4257,57 @@ export function batchSetDisplayOrders(
|
|
|
4010
4257
|
const clearsSurfaced =
|
|
4011
4258
|
safeGroupId === "system:background" ||
|
|
4012
4259
|
safeGroupId === "system:scheduled";
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4260
|
+
// Filing a conversation into a section the user reads (Pinned or one
|
|
4261
|
+
// of their own groups) is the mirror image: an explicit promotion, so
|
|
4262
|
+
// it stamps `surfaced_at` the way the surface API does.
|
|
4263
|
+
//
|
|
4264
|
+
// The stamp is what makes the promotion durable. Visibility must not
|
|
4265
|
+
// depend on where the row currently sits, or removing it from a group
|
|
4266
|
+
// would hide it instead of returning it to Recents, and pinning would
|
|
4267
|
+
// not show it at all: `system:pinned` fails the custom-group arm of
|
|
4268
|
+
// `standardListingVisibilitySql` on that arm's `system:` prefix check.
|
|
4269
|
+
//
|
|
4270
|
+
// Only background and scheduled rows need it: everything else is
|
|
4271
|
+
// already visible, and stamping them would leave `surfaced_at`
|
|
4272
|
+
// meaning nothing. `COALESCE` keeps the original promotion time when
|
|
4273
|
+
// the row is re-filed later.
|
|
4274
|
+
const promotes =
|
|
4275
|
+
!clearsSurfaced &&
|
|
4276
|
+
safeGroupId !== UNGROUPED_GROUP_ID &&
|
|
4277
|
+
(safeGroupId === PINNED_GROUP_ID ||
|
|
4278
|
+
!safeGroupId.startsWith("system:"));
|
|
4279
|
+
const setClauses = ["is_pinned = ?", "group_id = ?"];
|
|
4280
|
+
const params: Array<string | number | null> = [
|
|
4281
|
+
safeGroupId === PINNED_GROUP_ID ? 1 : 0,
|
|
4020
4282
|
safeGroupId,
|
|
4021
|
-
|
|
4022
|
-
)
|
|
4023
|
-
|
|
4024
|
-
|
|
4283
|
+
];
|
|
4284
|
+
if (clearsSurfaced) {
|
|
4285
|
+
setClauses.push("surfaced_at = NULL");
|
|
4286
|
+
} else if (promotes) {
|
|
4287
|
+
setClauses.push(
|
|
4288
|
+
"surfaced_at = CASE WHEN conversation_type IN ('background', 'scheduled')" +
|
|
4289
|
+
" THEN COALESCE(surfaced_at, ?) ELSE surfaced_at END",
|
|
4290
|
+
);
|
|
4291
|
+
params.push(Date.now());
|
|
4292
|
+
}
|
|
4293
|
+
params.push(update.id);
|
|
4025
4294
|
rawRun(
|
|
4026
|
-
"conversation:
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
update.id,
|
|
4295
|
+
"conversation:batchSetConversationPlacement:group",
|
|
4296
|
+
`UPDATE conversations SET ${setClauses.join(", ")} WHERE id = ?`,
|
|
4297
|
+
...params,
|
|
4030
4298
|
);
|
|
4031
|
-
} else if (update.isPinned) {
|
|
4299
|
+
} else if (update.isPinned === true) {
|
|
4032
4300
|
rawRun(
|
|
4033
|
-
"conversation:
|
|
4034
|
-
"UPDATE conversations SET
|
|
4035
|
-
update.displayOrder,
|
|
4301
|
+
"conversation:batchSetConversationPlacement:pin",
|
|
4302
|
+
"UPDATE conversations SET is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
4036
4303
|
update.id,
|
|
4037
4304
|
);
|
|
4038
|
-
} else {
|
|
4305
|
+
} else if (update.isPinned === false) {
|
|
4039
4306
|
// Restore system group from source/conversationType when unpinning,
|
|
4040
4307
|
// instead of clearing to NULL (which would lose provenance).
|
|
4041
4308
|
rawRun(
|
|
4042
|
-
"conversation:
|
|
4043
|
-
`UPDATE conversations SET
|
|
4309
|
+
"conversation:batchSetConversationPlacement:unpin",
|
|
4310
|
+
`UPDATE conversations SET is_pinned = 0,
|
|
4044
4311
|
group_id = CASE WHEN group_id = 'system:pinned' THEN
|
|
4045
4312
|
CASE
|
|
4046
4313
|
WHEN source IN ('schedule', 'reminder') THEN 'system:scheduled'
|
|
@@ -4050,7 +4317,6 @@ export function batchSetDisplayOrders(
|
|
|
4050
4317
|
END
|
|
4051
4318
|
ELSE group_id END
|
|
4052
4319
|
WHERE id = ?`,
|
|
4053
|
-
update.displayOrder,
|
|
4054
4320
|
update.id,
|
|
4055
4321
|
);
|
|
4056
4322
|
}
|