@vellumai/assistant 0.11.2 → 0.11.3-staging.1
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-queue.test.ts +265 -1
- package/src/__tests__/conversation-retry-route.test.ts +3 -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-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-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-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 +216 -132
- 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/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 +296 -145
- 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 +85 -42
- package/src/daemon/conversation-process.ts +76 -28
- 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 +105 -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.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/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 +56 -8
- 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 +350 -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 +295 -42
- 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/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/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
|
@@ -1,43 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Construction-time system-prompt resolution for a new conversation.
|
|
3
3
|
*
|
|
4
|
-
* The dependency seams (`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The dependency seams (`deps.warm`, `deps.build`) exist so the warm-then-build
|
|
5
|
+
* sequencing can be unit-tested without mocking the widely-imported
|
|
6
|
+
* guardian-delivery / system-prompt modules — global module mocks of those leak
|
|
7
|
+
* across test files in the shared-process runner.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { warmGuardianBindings } from "../contacts/guardian-delivery-reader.js";
|
|
10
10
|
import { buildSystemPrompt } from "../prompts/system-prompt.js";
|
|
11
11
|
import type { ConversationCreateOptions } from "./handlers/shared.js";
|
|
12
12
|
import type { TrustContext } from "./trust-context-types.js";
|
|
13
13
|
|
|
14
|
-
type GuardianDeliveryFetch = (input?: {
|
|
15
|
-
channelTypes?: string[];
|
|
16
|
-
}) => Promise<unknown>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Warm both guardian-binding cache keys the desktop/native persona resolver
|
|
20
|
-
* reads: the `"vellum"`-channel key for `peekGuardianForChannel("vellum")` and
|
|
21
|
-
* the unfiltered key for its `peekAnyGuardian()` fallback. Warming only one
|
|
22
|
-
* would still freeze `users/default.md` when the guardian lives on a non-vellum
|
|
23
|
-
* channel (phone / Telegram).
|
|
24
|
-
*
|
|
25
|
-
* Uses the FRESH reader so warmup bypasses a stale/empty cached entry: a
|
|
26
|
-
* gateway-side binding write (onboarding, rebind) does not invalidate the daemon
|
|
27
|
-
* cache, so a non-fresh read could return an empty binding cached before the
|
|
28
|
-
* guardian existed and freeze `users/default.md` until the TTL expires. The
|
|
29
|
-
* fresh read repopulates the cache the sync `peek*` resolvers then read.
|
|
30
|
-
* Best-effort — the reader swallows failures.
|
|
31
|
-
*/
|
|
32
|
-
export async function warmGuardianBindings(
|
|
33
|
-
fetchDelivery: GuardianDeliveryFetch = getGuardianDeliveryFresh,
|
|
34
|
-
): Promise<void> {
|
|
35
|
-
await Promise.all([
|
|
36
|
-
fetchDelivery({ channelTypes: ["vellum"] }),
|
|
37
|
-
fetchDelivery(),
|
|
38
|
-
]);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
14
|
/**
|
|
42
15
|
* Resolve the system prompt to freeze onto a newly constructed conversation.
|
|
43
16
|
*
|
|
@@ -47,11 +20,16 @@ export async function warmGuardianBindings(
|
|
|
47
20
|
*
|
|
48
21
|
* - An explicit `systemPromptOverride` (including an empty string) is used
|
|
49
22
|
* verbatim.
|
|
50
|
-
* - A channel-routed conversation
|
|
51
|
-
* `trustContext` (e.g. Slack / Telegram inbound) builds with it
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* -
|
|
23
|
+
* - A channel-routed conversation carrying a non-guardian requester
|
|
24
|
+
* `trustContext` (e.g. Slack / Telegram inbound) builds with it directly:
|
|
25
|
+
* the requester's persona (`users/<slug>.md`) resolves via a DB contact
|
|
26
|
+
* lookup that needs no guardian binding.
|
|
27
|
+
* - A guardian-class `trustContext` (background and scheduled turns, memory
|
|
28
|
+
* consolidation, managed desktop) warms the guardian binding first: its
|
|
29
|
+
* persona resolution reads the sync guardian-delivery cache, which is cold
|
|
30
|
+
* in worker processes (schedule worker, memory worker) and after TTL expiry
|
|
31
|
+
* in the daemon.
|
|
32
|
+
* - Otherwise (no construction-time identity: the local vellum app sets trust
|
|
55
33
|
* after creation) the guardian binding is warmed first so the persona slot
|
|
56
34
|
* resolves the guardian's `users/<slug>.md` instead of `users/default.md` on
|
|
57
35
|
* a cold cache.
|
|
@@ -67,7 +45,7 @@ export async function resolveInitialSystemPrompt(
|
|
|
67
45
|
return storedOptions.systemPromptOverride;
|
|
68
46
|
}
|
|
69
47
|
const trustContext = storedOptions?.trustContext;
|
|
70
|
-
if (trustContext === undefined) {
|
|
48
|
+
if (trustContext === undefined || trustContext.trustClass === "guardian") {
|
|
71
49
|
await (deps.warm ?? warmGuardianBindings)();
|
|
72
50
|
}
|
|
73
51
|
const build =
|
|
@@ -12,6 +12,7 @@ import { getConfig } from "../config/loader.js";
|
|
|
12
12
|
import { usesConceptPageMemory } from "../config/memory-v3-gate.js";
|
|
13
13
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
14
14
|
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
15
|
+
import { isSuppressedQueuedMessage } from "../persistence/conversation-types.js";
|
|
15
16
|
import {
|
|
16
17
|
enqueueMemoryJob,
|
|
17
18
|
isMemoryEnabled,
|
|
@@ -23,10 +24,17 @@ import { resolveCapabilities } from "../runtime/capabilities.js";
|
|
|
23
24
|
import { isAutoAnalysisConversation } from "../runtime/services/auto-analysis-guard.js";
|
|
24
25
|
import { unregisterConversationSender } from "../tools/browser/browser-screencast.js";
|
|
25
26
|
import { disposeToolProfiler } from "../tools/tool-profiler.js";
|
|
26
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
type AbortReason,
|
|
29
|
+
createAbortReason,
|
|
30
|
+
isUserInterruptAbort,
|
|
31
|
+
} from "../util/abort-reasons.js";
|
|
27
32
|
import { getLogger } from "../util/logger.js";
|
|
28
33
|
import { unregisterCallNotifiers } from "./conversation-notifiers.js";
|
|
29
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
MessageQueue,
|
|
36
|
+
QueueDrainReason,
|
|
37
|
+
} from "./conversation-queue-manager.js";
|
|
30
38
|
import { resetSkillToolProjection } from "./conversation-skill-tools.js";
|
|
31
39
|
import type { SurfaceData, SurfaceType } from "./message-protocol.js";
|
|
32
40
|
|
|
@@ -112,6 +120,12 @@ export interface AbortContext {
|
|
|
112
120
|
>;
|
|
113
121
|
accumulatedSurfaceState: Map<string, Record<string, unknown>>;
|
|
114
122
|
readonly queue: MessageQueue;
|
|
123
|
+
/** Armed when an interrupt leaves queued messages behind, so the drain
|
|
124
|
+
* repairs tool_use blocks the killed turn abandoned. */
|
|
125
|
+
pendingInterruptRepair: boolean;
|
|
126
|
+
/** Never rejects. Called only when an interrupt preserves the queue but no
|
|
127
|
+
* live turn is left to reach its `finally` and drain it. */
|
|
128
|
+
kickDrainQueue(reason?: QueueDrainReason, origin?: string): Promise<void>;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
export interface DisposeContext extends AbortContext {
|
|
@@ -129,18 +143,133 @@ export interface DisposeContext extends AbortContext {
|
|
|
129
143
|
|
|
130
144
|
// ── abort ─────────────────────────────────────────────────────────────
|
|
131
145
|
|
|
146
|
+
/** The conversation surface {@link forceClearStaleProcessing} needs. */
|
|
147
|
+
export interface StaleProcessingContext {
|
|
148
|
+
readonly conversationId: string;
|
|
149
|
+
setProcessing(value: boolean): void;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Release a processing flag that no live turn owns.
|
|
154
|
+
*
|
|
155
|
+
* Callers reach this when `isProcessing()` is true but `abortController` is
|
|
156
|
+
* null: the turn that owned the flag already tore its controller down (the
|
|
157
|
+
* agent loop nulls `abortController` before clearing the flag) or died without
|
|
158
|
+
* ever installing one. Either way no agent-loop release is going to run to
|
|
159
|
+
* clear it, so signalling the controller would be a silent no-op (`?.abort()`
|
|
160
|
+
* on null does nothing) and the conversation would stay wedged: every later
|
|
161
|
+
* submit is rejected with "already processing" and Stop appears dead.
|
|
162
|
+
* `setProcessing(false)` also nulls the persisted column and emits the metadata
|
|
163
|
+
* invalidation that drives clients to idle.
|
|
164
|
+
*
|
|
165
|
+
* `origin` names the calling site for log correlation.
|
|
166
|
+
*/
|
|
167
|
+
export function forceClearStaleProcessing(
|
|
168
|
+
ctx: StaleProcessingContext,
|
|
169
|
+
origin: string,
|
|
170
|
+
): void {
|
|
171
|
+
log.warn(
|
|
172
|
+
{ conversationId: ctx.conversationId, origin },
|
|
173
|
+
"Processing latched with no live abort controller; force-clearing the stale processing flag",
|
|
174
|
+
);
|
|
175
|
+
ctx.setProcessing(false);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Keep the queue intact across a user interrupt so the messages queued behind
|
|
180
|
+
* the stopped turn still run.
|
|
181
|
+
*
|
|
182
|
+
* A user interrupt ends the turn in flight and nothing else: the aborted turn
|
|
183
|
+
* unwinds to the agent loop's `finally`, which calls `kickDrainQueue`, so the
|
|
184
|
+
* queued messages become the next turn. This is the same drain-after-abort
|
|
185
|
+
* sequence `steerToMessage` relies on.
|
|
186
|
+
*
|
|
187
|
+
* An interrupt can land mid-tool, leaving `tool_use` blocks with no results, so
|
|
188
|
+
* arm the same repair a steer arms: the drained turn then opens with synthetic
|
|
189
|
+
* results instead of a dangling call.
|
|
190
|
+
*
|
|
191
|
+
* `hasLiveTurn` is false when the processing flag was force-cleared because no
|
|
192
|
+
* controller was left to signal. No agent-loop `finally` runs in that case, so
|
|
193
|
+
* the drain is kicked here instead; otherwise the preserved messages wait for
|
|
194
|
+
* an unrelated later turn to finish.
|
|
195
|
+
*/
|
|
196
|
+
function preserveQueueAcrossInterrupt(
|
|
197
|
+
ctx: AbortContext,
|
|
198
|
+
{ hasLiveTurn }: { hasLiveTurn: boolean },
|
|
199
|
+
): void {
|
|
200
|
+
if (ctx.queue.isEmpty) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
ctx.pendingInterruptRepair = true;
|
|
204
|
+
log.info(
|
|
205
|
+
{
|
|
206
|
+
conversationId: ctx.conversationId,
|
|
207
|
+
queueDepth: ctx.queue.length,
|
|
208
|
+
hasLiveTurn,
|
|
209
|
+
},
|
|
210
|
+
"User interrupt: preserving queued messages for the interrupted turn's drain",
|
|
211
|
+
);
|
|
212
|
+
if (!hasLiveTurn) {
|
|
213
|
+
void ctx.kickDrainQueue("loop_complete", "abortConversation:no_live_turn");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Discard the queue when the conversation itself is going away (dispose,
|
|
219
|
+
* eviction, voice supersession, subagent teardown): there is no turn left for
|
|
220
|
+
* a queued message to run on.
|
|
221
|
+
*
|
|
222
|
+
* This runs whether or not the in-memory flag reads processing. A turn that
|
|
223
|
+
* cleared the flag can still be unwinding through its awaited turn-boundary
|
|
224
|
+
* commit with a `kickDrainQueue` ahead of it, and a queue left populated there
|
|
225
|
+
* drains onto a conversation whose state (and possibly whose durable row) is
|
|
226
|
+
* already gone.
|
|
227
|
+
*
|
|
228
|
+
* Each sender gets two events, both load-bearing. `generation_cancelled`
|
|
229
|
+
* closes out the turn the message was waiting on; `message_queued_deleted` is
|
|
230
|
+
* the terminal event for the queued row itself, the same one a user-issued
|
|
231
|
+
* DELETE emits (`deleteQueuedMessage`). Without the second, a client holds the
|
|
232
|
+
* pending indicator forever, since no `message_dequeued` is coming. Rows with
|
|
233
|
+
* no client-visible queued counterpart ({@link isSuppressedQueuedMessage} —
|
|
234
|
+
* hidden sends and daemon-injected subagent/ACP/wake notifications) are
|
|
235
|
+
* suppressed for the same reason they get no queued ack: they have no client
|
|
236
|
+
* row to close.
|
|
237
|
+
*/
|
|
238
|
+
function discardQueueOnAbort(ctx: AbortContext): void {
|
|
239
|
+
for (const queued of ctx.queue) {
|
|
240
|
+
queued.onEvent({
|
|
241
|
+
type: "generation_cancelled",
|
|
242
|
+
conversationId: ctx.conversationId,
|
|
243
|
+
});
|
|
244
|
+
if (isSuppressedQueuedMessage(queued.metadata)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
queued.onEvent({
|
|
248
|
+
type: "message_queued_deleted",
|
|
249
|
+
conversationId: ctx.conversationId,
|
|
250
|
+
requestId: queued.requestId,
|
|
251
|
+
...(queued.clientMessageId
|
|
252
|
+
? { clientMessageId: queued.clientMessageId }
|
|
253
|
+
: {}),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
ctx.queue.clear();
|
|
257
|
+
}
|
|
258
|
+
|
|
132
259
|
export function abortConversation(
|
|
133
260
|
ctx: AbortContext,
|
|
134
261
|
reason?: AbortReason,
|
|
135
262
|
): void {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
263
|
+
const effectiveReason =
|
|
264
|
+
reason ??
|
|
265
|
+
createAbortReason(
|
|
266
|
+
"preempted_by_new_message",
|
|
267
|
+
"abortConversation:default",
|
|
268
|
+
ctx.conversationId,
|
|
269
|
+
);
|
|
270
|
+
const hasLiveTurn = ctx.abortController !== null;
|
|
271
|
+
const wasProcessing = ctx.isProcessing();
|
|
272
|
+
if (wasProcessing) {
|
|
144
273
|
log.info(
|
|
145
274
|
{ conversationId: ctx.conversationId, abortReason: effectiveReason },
|
|
146
275
|
"Aborting in-flight processing",
|
|
@@ -153,21 +282,7 @@ export function abortConversation(
|
|
|
153
282
|
// clear it here and risk clobbering a client's optimistic state.
|
|
154
283
|
ctx.abortController.abort(effectiveReason);
|
|
155
284
|
} else {
|
|
156
|
-
|
|
157
|
-
// that owned it already tore its controller down (the agent-loop
|
|
158
|
-
// `finally` nulls `abortController` before clearing the flag) or died
|
|
159
|
-
// without ever installing one. Either way no agent-loop `finally` is
|
|
160
|
-
// going to run to clear the flag. Without this branch the abort is a
|
|
161
|
-
// silent no-op — `?.abort()` does nothing — and the conversation stays
|
|
162
|
-
// wedged: every later submit is rejected with "already processing" and
|
|
163
|
-
// Stop appears dead. Force-clear the flag directly so the conversation
|
|
164
|
-
// frees up. `setProcessing(false)` also nulls the persisted column and
|
|
165
|
-
// emits the metadata invalidation that drives clients to idle.
|
|
166
|
-
log.warn(
|
|
167
|
-
{ conversationId: ctx.conversationId },
|
|
168
|
-
"Abort requested while processing but no live abort controller — force-clearing stale processing flag",
|
|
169
|
-
);
|
|
170
|
-
ctx.setProcessing(false);
|
|
285
|
+
forceClearStaleProcessing(ctx, "abortConversation");
|
|
171
286
|
}
|
|
172
287
|
ctx.prompter.dispose();
|
|
173
288
|
ctx.secretPrompter.dispose();
|
|
@@ -175,13 +290,6 @@ export function abortConversation(
|
|
|
175
290
|
ctx.surfaceActionRequestIds.clear();
|
|
176
291
|
ctx.surfaceState.clear();
|
|
177
292
|
ctx.accumulatedSurfaceState.clear();
|
|
178
|
-
for (const queued of ctx.queue) {
|
|
179
|
-
queued.onEvent({
|
|
180
|
-
type: "generation_cancelled",
|
|
181
|
-
conversationId: ctx.conversationId,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
ctx.queue.clear();
|
|
185
293
|
} else {
|
|
186
294
|
// The in-memory flag reads idle, but a cancel must still clear a persisted
|
|
187
295
|
// `processing_started_at` that outlived the turn that set it. This is the
|
|
@@ -201,6 +309,20 @@ export function abortConversation(
|
|
|
201
309
|
// reports idle in-memory to clients, so no refetch needs to be pushed.
|
|
202
310
|
ctx.setProcessing(false);
|
|
203
311
|
}
|
|
312
|
+
|
|
313
|
+
// The queue decision is independent of the processing flag: an interrupt
|
|
314
|
+
// hands its queue to the drain, and every other abort kind is a teardown
|
|
315
|
+
// that must take the queue with it even when the flag already reads idle.
|
|
316
|
+
if (isUserInterruptAbort(effectiveReason)) {
|
|
317
|
+
// An idle conversation counts as having a live turn for drain purposes:
|
|
318
|
+
// either a turn is unwinding toward its own `kickDrainQueue`, or the queue
|
|
319
|
+
// is empty. Kicking a second drain here would race the first.
|
|
320
|
+
preserveQueueAcrossInterrupt(ctx, {
|
|
321
|
+
hasLiveTurn: hasLiveTurn || !wasProcessing,
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
discardQueueOnAbort(ctx);
|
|
325
|
+
}
|
|
204
326
|
}
|
|
205
327
|
|
|
206
328
|
// ── dispose ──────────────────────────────────────────────────────────
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
extractAttachmentStoredPaths,
|
|
49
49
|
extractImageSourcePaths,
|
|
50
50
|
getConversation,
|
|
51
|
-
|
|
51
|
+
isSuppressedQueuedMessage,
|
|
52
52
|
provenanceFromTrustContext,
|
|
53
53
|
setConversationOriginChannelIfUnset,
|
|
54
54
|
setConversationOriginInterfaceIfUnset,
|
|
@@ -217,11 +217,11 @@ export interface MessagingConversationContext {
|
|
|
217
217
|
currentTurnAuthContext?: AuthContext;
|
|
218
218
|
currentTurnSourceActorPrincipalId?: string;
|
|
219
219
|
/**
|
|
220
|
-
* OS surface reported by the connected client
|
|
221
|
-
*
|
|
220
|
+
* OS surface reported by the connected client, re-applied from transport
|
|
221
|
+
* metadata on every inbound message.
|
|
222
222
|
* Persisted under `metadata.client.os` so turn telemetry can attribute the
|
|
223
|
-
* real platform
|
|
224
|
-
* and
|
|
223
|
+
* real platform. The transport `interfaceId` is "web" for browser, mobile,
|
|
224
|
+
* and desktop apps because they share the web renderer.
|
|
225
225
|
*/
|
|
226
226
|
clientOs?: string;
|
|
227
227
|
getTurnChannelContext(): TurnChannelContext | null;
|
|
@@ -254,15 +254,15 @@ function serializeUserContentBlocks(
|
|
|
254
254
|
* by the attachments as inline base64 blocks. The regular upload path persists
|
|
255
255
|
* `workspace_ref` blocks instead (see `persistQueuedMessageBody`).
|
|
256
256
|
*/
|
|
257
|
-
export function serializePersistedUserMessageContent(
|
|
257
|
+
export async function serializePersistedUserMessageContent(
|
|
258
258
|
content: string,
|
|
259
259
|
displayContent: string | undefined,
|
|
260
260
|
attachments: MessageAttachmentInput[],
|
|
261
|
-
): string {
|
|
261
|
+
): Promise<string> {
|
|
262
262
|
return serializeUserContentBlocks(
|
|
263
263
|
content,
|
|
264
264
|
displayContent,
|
|
265
|
-
attachmentsToContentBlocks(attachments),
|
|
265
|
+
await attachmentsToContentBlocks(attachments),
|
|
266
266
|
);
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -273,15 +273,15 @@ export function serializePersistedUserMessageContent(
|
|
|
273
273
|
* the stored original — keeping the per-turn token estimate accurate without a
|
|
274
274
|
* disk read on the hot path.
|
|
275
275
|
*/
|
|
276
|
-
function computeReferenceImageDimensions(
|
|
276
|
+
async function computeReferenceImageDimensions(
|
|
277
277
|
attachmentId: string,
|
|
278
278
|
mediaType: string,
|
|
279
|
-
): { width: number; height: number } | null {
|
|
279
|
+
): Promise<{ width: number; height: number } | null> {
|
|
280
280
|
const bytes = getAttachmentContent(attachmentId);
|
|
281
281
|
if (!bytes) {
|
|
282
282
|
return null;
|
|
283
283
|
}
|
|
284
|
-
const optimized = optimizeImageForTransport(
|
|
284
|
+
const optimized = await optimizeImageForTransport(
|
|
285
285
|
bytes.toString("base64"),
|
|
286
286
|
mediaType,
|
|
287
287
|
);
|
|
@@ -321,11 +321,11 @@ type MaterializeOutcome =
|
|
|
321
321
|
* endpoint's own rejection); only a recoverable store-write failure is
|
|
322
322
|
* `transient` (inline fallback).
|
|
323
323
|
*/
|
|
324
|
-
function materializeUserAttachment(
|
|
324
|
+
async function materializeUserAttachment(
|
|
325
325
|
conversationId: string,
|
|
326
326
|
conversationCreatedAt: number,
|
|
327
327
|
a: MessageAttachmentInput,
|
|
328
|
-
): MaterializeOutcome {
|
|
328
|
+
): Promise<MaterializeOutcome> {
|
|
329
329
|
try {
|
|
330
330
|
if (a.id && attachmentExists(a.id)) {
|
|
331
331
|
const stored = scopeAttachmentToMessageConversation(
|
|
@@ -348,7 +348,7 @@ function materializeUserAttachment(
|
|
|
348
348
|
}
|
|
349
349
|
return {
|
|
350
350
|
kind: "stored",
|
|
351
|
-
stored: createInlineAttachment(
|
|
351
|
+
stored: await createInlineAttachment(
|
|
352
352
|
conversationId,
|
|
353
353
|
conversationCreatedAt,
|
|
354
354
|
a.filename,
|
|
@@ -376,10 +376,10 @@ function materializeUserAttachment(
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
/** Build the `workspace_ref` content block for a materialized attachment. */
|
|
379
|
-
function referenceBlockForAttachment(
|
|
379
|
+
async function referenceBlockForAttachment(
|
|
380
380
|
a: MessageAttachmentInput,
|
|
381
381
|
stored: { id: string; mimeType: string; sizeBytes: number },
|
|
382
|
-
): ContentBlock {
|
|
382
|
+
): Promise<ContentBlock> {
|
|
383
383
|
const ref: AttachmentReferenceInput = {
|
|
384
384
|
attachmentId: stored.id,
|
|
385
385
|
filename: a.filename,
|
|
@@ -388,7 +388,10 @@ function referenceBlockForAttachment(
|
|
|
388
388
|
extractedText: a.extractedText,
|
|
389
389
|
};
|
|
390
390
|
if (stored.mimeType.toLowerCase().startsWith("image/")) {
|
|
391
|
-
const dims = computeReferenceImageDimensions(
|
|
391
|
+
const dims = await computeReferenceImageDimensions(
|
|
392
|
+
stored.id,
|
|
393
|
+
stored.mimeType,
|
|
394
|
+
);
|
|
392
395
|
if (dims) {
|
|
393
396
|
ref.width = dims.width;
|
|
394
397
|
ref.height = dims.height;
|
|
@@ -399,13 +402,13 @@ function referenceBlockForAttachment(
|
|
|
399
402
|
|
|
400
403
|
/** Inline base64 fallback block for an attachment that could not be stored as
|
|
401
404
|
* a reference, so the upload survives a reload. Null when there are no bytes. */
|
|
402
|
-
function inlineBlockForAttachment(
|
|
405
|
+
async function inlineBlockForAttachment(
|
|
403
406
|
a: MessageAttachmentInput,
|
|
404
|
-
): ContentBlock | null {
|
|
407
|
+
): Promise<ContentBlock | null> {
|
|
405
408
|
if (!a.data) {
|
|
406
409
|
return null;
|
|
407
410
|
}
|
|
408
|
-
return attachmentsToContentBlocks([a])[0] ?? null;
|
|
411
|
+
return (await attachmentsToContentBlocks([a]))[0] ?? null;
|
|
409
412
|
}
|
|
410
413
|
|
|
411
414
|
/**
|
|
@@ -419,15 +422,15 @@ function inlineBlockForAttachment(
|
|
|
419
422
|
* survives; a validation/upload rejection is dropped (never reaches content or
|
|
420
423
|
* the model).
|
|
421
424
|
*/
|
|
422
|
-
function prepareUserAttachmentReferences(
|
|
425
|
+
async function prepareUserAttachmentReferences(
|
|
423
426
|
conversationId: string,
|
|
424
427
|
conversationCreatedAt: number,
|
|
425
428
|
attachments: MessageAttachmentInput[],
|
|
426
|
-
): PreparedUserAttachment[] {
|
|
429
|
+
): Promise<PreparedUserAttachment[]> {
|
|
427
430
|
const prepared: PreparedUserAttachment[] = [];
|
|
428
431
|
for (let i = 0; i < attachments.length; i++) {
|
|
429
432
|
const a = attachments[i];
|
|
430
|
-
const outcome = materializeUserAttachment(
|
|
433
|
+
const outcome = await materializeUserAttachment(
|
|
431
434
|
conversationId,
|
|
432
435
|
conversationCreatedAt,
|
|
433
436
|
a,
|
|
@@ -435,7 +438,7 @@ function prepareUserAttachmentReferences(
|
|
|
435
438
|
if (outcome.kind === "stored") {
|
|
436
439
|
prepared.push({
|
|
437
440
|
position: i,
|
|
438
|
-
block: referenceBlockForAttachment(a, outcome.stored),
|
|
441
|
+
block: await referenceBlockForAttachment(a, outcome.stored),
|
|
439
442
|
link: { attachmentId: outcome.stored.id },
|
|
440
443
|
});
|
|
441
444
|
continue;
|
|
@@ -445,7 +448,7 @@ function prepareUserAttachmentReferences(
|
|
|
445
448
|
}
|
|
446
449
|
// transient: keep the upload by inlining its bytes (dropped only when the
|
|
447
450
|
// recoverable failure left us with no bytes to inline).
|
|
448
|
-
const inline = inlineBlockForAttachment(a);
|
|
451
|
+
const inline = await inlineBlockForAttachment(a);
|
|
449
452
|
if (inline) {
|
|
450
453
|
prepared.push({ position: i, block: inline });
|
|
451
454
|
} else {
|
|
@@ -633,16 +636,17 @@ export function enqueueMessage(
|
|
|
633
636
|
}
|
|
634
637
|
// Ack the accepted enqueue on the sender's event sink. Emitting here,
|
|
635
638
|
// rather than at each ingress call site, is what guarantees every path
|
|
636
|
-
// that queues (HTTP send, surface actions,
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
|
|
639
|
+
// that queues a person's prompt (HTTP send, surface actions, CLI signal)
|
|
640
|
+
// surfaces the queued row live. Rows with no client-visible counterpart —
|
|
641
|
+
// hidden sends and daemon-injected notifications (subagent/ACP/wake) — are
|
|
642
|
+
// suppressed from the transcript at every stage, including this ack, and
|
|
643
|
+
// `position` counts visible items only: both mirror the list-messages
|
|
644
|
+
// queued-snapshot filter so a live ack and a cold reload render the same
|
|
645
|
+
// row at the same position.
|
|
646
|
+
if (!isSuppressedQueuedMessage(metadata)) {
|
|
643
647
|
const position = ctx.queue
|
|
644
648
|
.snapshot()
|
|
645
|
-
.filter((item) => !
|
|
649
|
+
.filter((item) => !isSuppressedQueuedMessage(item.metadata)).length;
|
|
646
650
|
onEvent?.({
|
|
647
651
|
type: "message_queued",
|
|
648
652
|
conversationId: ctx.conversationId,
|
|
@@ -699,6 +703,19 @@ export interface PersistMessageOptions {
|
|
|
699
703
|
* thread it: the queue round-trips `metadata`, not these options.
|
|
700
704
|
*/
|
|
701
705
|
scripted?: boolean;
|
|
706
|
+
/**
|
|
707
|
+
* OS surface this row's own request or transport reported, threaded by the
|
|
708
|
+
* ingress that built it (the send route's request body, a queued message's
|
|
709
|
+
* `transport`). Stamps `metadata.clientOsFromRequest` when it matches the
|
|
710
|
+
* `client.os` this row persists.
|
|
711
|
+
*
|
|
712
|
+
* `ctx.clientOs` alone is not that evidence: it is a live conversation
|
|
713
|
+
* field only a transport-carrying message refreshes, so a transport-less
|
|
714
|
+
* turn (surface action, signal ingress) inherits whatever an earlier send
|
|
715
|
+
* left there. Omitting this option therefore reads as "inherited", which is
|
|
716
|
+
* what a consumer that must not misattribute a turn to a surface needs.
|
|
717
|
+
*/
|
|
718
|
+
requestClientOs?: string;
|
|
702
719
|
}
|
|
703
720
|
|
|
704
721
|
// ── persistUserMessage ───────────────────────────────────────────────
|
|
@@ -767,9 +784,9 @@ export async function persistUserMessage(
|
|
|
767
784
|
return result;
|
|
768
785
|
} catch (err) {
|
|
769
786
|
// Clear the flag, but never let a clear failure mask the original error
|
|
770
|
-
// or skip the bookkeeping reset. `setProcessing`
|
|
771
|
-
//
|
|
772
|
-
//
|
|
787
|
+
// or skip the bookkeeping reset. `setProcessing(false)` releases in memory
|
|
788
|
+
// regardless of its advisory mirror write, so the guard here is purely
|
|
789
|
+
// defensive against future changes.
|
|
773
790
|
try {
|
|
774
791
|
ctx.setProcessing(false);
|
|
775
792
|
} catch (clearErr) {
|
|
@@ -807,6 +824,7 @@ export async function persistQueuedMessageBody(
|
|
|
807
824
|
displayContent,
|
|
808
825
|
clientMessageId,
|
|
809
826
|
skipIndexing,
|
|
827
|
+
requestClientOs,
|
|
810
828
|
} = options;
|
|
811
829
|
const attachmentInputs: MessageAttachmentInput[] = attachments.map(
|
|
812
830
|
(attachment) => ({
|
|
@@ -818,7 +836,7 @@ export async function persistQueuedMessageBody(
|
|
|
818
836
|
filePath: attachment.filePath,
|
|
819
837
|
}),
|
|
820
838
|
);
|
|
821
|
-
const cleanMessage = createUserMessage(content, attachmentInputs);
|
|
839
|
+
const cleanMessage = await createUserMessage(content, attachmentInputs);
|
|
822
840
|
let pushedToHistory = false;
|
|
823
841
|
|
|
824
842
|
try {
|
|
@@ -839,13 +857,19 @@ export async function persistQueuedMessageBody(
|
|
|
839
857
|
// non-boolean through would be worse than dropping it: sqlite stores it
|
|
840
858
|
// verbatim, and `turn-events-store` narrows anything that isn't 1 to
|
|
841
859
|
// `false`, turning a junk string into a confident "the user typed this".
|
|
860
|
+
// `clientOsFromRequest` comes out for the same reason and a sharper one:
|
|
861
|
+
// it is derived below from what this persist can actually see, and a bag
|
|
862
|
+
// value surviving the spread would let a caller assert an origin the row
|
|
863
|
+
// never reported.
|
|
842
864
|
const {
|
|
843
865
|
slackInbound: rawSlackInbound,
|
|
844
866
|
scripted: rawScriptedFromMetadata,
|
|
867
|
+
clientOsFromRequest: _rawClientOsFromRequest,
|
|
845
868
|
...metadataWithoutSlackInbound
|
|
846
869
|
} = (metadata ?? {}) as Record<string, unknown> & {
|
|
847
870
|
slackInbound?: SlackInboundMessageMetadata;
|
|
848
871
|
scripted?: unknown;
|
|
872
|
+
clientOsFromRequest?: unknown;
|
|
849
873
|
};
|
|
850
874
|
const slackMeta = buildSlackMetaForPersistence({
|
|
851
875
|
slackInbound: rawSlackInbound,
|
|
@@ -895,6 +919,22 @@ export async function persistQueuedMessageBody(
|
|
|
895
919
|
const clientBag =
|
|
896
920
|
Object.keys(clientEntries).length > 0 ? { client: clientEntries } : {};
|
|
897
921
|
|
|
922
|
+
// Per-row evidence for the `client.os` stamped just above, kept as a
|
|
923
|
+
// sibling of the bag so `TurnTelemetryEvent.client` stays exactly the
|
|
924
|
+
// forwarded `$.client`. Set only when this row itself reported the OS:
|
|
925
|
+
// through the caller's own client bag (the request's client-metadata
|
|
926
|
+
// headers, round-tripped through the queue) or through this row's
|
|
927
|
+
// transport, which `requestClientOs` carries. An inherited `ctx.clientOs`
|
|
928
|
+
// names the surface of an EARLIER turn, so it leaves the marker off and a
|
|
929
|
+
// consumer reading origin (the reply-push presence gate) treats the turn
|
|
930
|
+
// as coming from somewhere unknown.
|
|
931
|
+
const callerOs = callerClient?.os;
|
|
932
|
+
const resolvedRequestClientOs = parseClientOs(requestClientOs);
|
|
933
|
+
const clientOsFromRequest =
|
|
934
|
+
(typeof callerOs === "string" && callerOs.length > 0) ||
|
|
935
|
+
(resolvedRequestClientOs !== null &&
|
|
936
|
+
resolvedRequestClientOs === clientOs);
|
|
937
|
+
|
|
898
938
|
const mergedMetadata = {
|
|
899
939
|
...metadataWithoutSlackInbound,
|
|
900
940
|
...provenance,
|
|
@@ -911,6 +951,7 @@ export async function persistQueuedMessageBody(
|
|
|
911
951
|
}
|
|
912
952
|
: {}),
|
|
913
953
|
...clientBag,
|
|
954
|
+
...(clientOsFromRequest ? { clientOsFromRequest: true } : {}),
|
|
914
955
|
...(imageSourcePaths ? { imageSourcePaths } : {}),
|
|
915
956
|
...(slackMeta ? { slackMeta } : {}),
|
|
916
957
|
// Scripted-turn marker, forwarded by `turn-events-store` onto
|
|
@@ -944,7 +985,7 @@ export async function persistQueuedMessageBody(
|
|
|
944
985
|
// once the message id exists.
|
|
945
986
|
const conversationCreatedAt =
|
|
946
987
|
getConversation(ctx.conversationId)?.createdAt ?? Date.now();
|
|
947
|
-
const preparedAttachments = prepareUserAttachmentReferences(
|
|
988
|
+
const preparedAttachments = await prepareUserAttachmentReferences(
|
|
948
989
|
ctx.conversationId,
|
|
949
990
|
conversationCreatedAt,
|
|
950
991
|
attachmentInputs,
|
|
@@ -1011,9 +1052,9 @@ export async function persistQueuedMessageBody(
|
|
|
1011
1052
|
// by rewriting that block to inline base64 so the upload survives even
|
|
1012
1053
|
// though the store anchor was lost, then persist the corrected content.
|
|
1013
1054
|
let repairedBlocks: ContentBlock[] | null = null;
|
|
1014
|
-
preparedAttachments.
|
|
1055
|
+
for (const [idx, p] of preparedAttachments.entries()) {
|
|
1015
1056
|
if (!p.link) {
|
|
1016
|
-
|
|
1057
|
+
continue;
|
|
1017
1058
|
}
|
|
1018
1059
|
try {
|
|
1019
1060
|
const scopedAttachmentId = linkAttachmentToMessage(
|
|
@@ -1024,7 +1065,9 @@ export async function persistQueuedMessageBody(
|
|
|
1024
1065
|
attachmentInputs[p.position].storedPath =
|
|
1025
1066
|
getFilePathForAttachment(scopedAttachmentId) ?? undefined;
|
|
1026
1067
|
} catch (err) {
|
|
1027
|
-
const inline = inlineBlockForAttachment(
|
|
1068
|
+
const inline = await inlineBlockForAttachment(
|
|
1069
|
+
attachmentInputs[p.position],
|
|
1070
|
+
);
|
|
1028
1071
|
log.error(
|
|
1029
1072
|
{ attachmentId: p.link.attachmentId, err, repaired: inline != null },
|
|
1030
1073
|
"Failed to link user attachment; repairing persisted content to inline",
|
|
@@ -1034,7 +1077,7 @@ export async function persistQueuedMessageBody(
|
|
|
1034
1077
|
repairedBlocks[idx] = inline;
|
|
1035
1078
|
}
|
|
1036
1079
|
}
|
|
1037
|
-
}
|
|
1080
|
+
}
|
|
1038
1081
|
if (repairedBlocks) {
|
|
1039
1082
|
updateMessageContent(
|
|
1040
1083
|
persistedUserMessage.id,
|