@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
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
* back to a shorter deterministic path so CLI commands can still connect.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { existsSync, unlinkSync } from "node:fs";
|
|
32
31
|
import { createServer, type Server, type Socket } from "node:net";
|
|
33
32
|
|
|
34
33
|
import {
|
|
35
34
|
ensureSocketDir,
|
|
36
35
|
type IpcEnvelope,
|
|
37
36
|
IpcFrameReader,
|
|
37
|
+
ipcListenOptions,
|
|
38
|
+
removeIpcEndpointFile,
|
|
38
39
|
SocketWatchdog,
|
|
39
40
|
writeLegacyMessage,
|
|
40
41
|
writeMessage,
|
|
@@ -60,6 +61,7 @@ import { CONTACTS_INFO_IPC_METHODS } from "./routes/contacts-info-ipc-routes.js"
|
|
|
60
61
|
import { CONTACTS_MIRROR_IPC_METHODS } from "./routes/contacts-mirror-ipc-routes.js";
|
|
61
62
|
import { CONVERSATION_SYNC_IPC_METHODS } from "./routes/conversation-sync-ipc-routes.js";
|
|
62
63
|
import { type DbProxyParams, handleDbProxy } from "./routes/db-proxy.js";
|
|
64
|
+
import { DOCUMENTS_SYNC_IPC_METHODS } from "./routes/documents-sync-ipc-routes.js";
|
|
63
65
|
import { EVENTS_IPC_METHODS } from "./routes/events-ipc-routes.js";
|
|
64
66
|
import { GUARDIAN_LABEL_IPC_METHODS } from "./routes/guardian-label-ipc-routes.js";
|
|
65
67
|
import { INVITE_IPC_METHODS } from "./routes/invite-ipc-routes.js";
|
|
@@ -215,6 +217,7 @@ export class AssistantIpcServer {
|
|
|
215
217
|
CONTACTS_MIRROR_IPC_METHODS,
|
|
216
218
|
GUARDIAN_LABEL_IPC_METHODS,
|
|
217
219
|
CONVERSATION_SYNC_IPC_METHODS,
|
|
220
|
+
DOCUMENTS_SYNC_IPC_METHODS,
|
|
218
221
|
EVENTS_IPC_METHODS,
|
|
219
222
|
]) {
|
|
220
223
|
for (const [operationId, handler] of Object.entries(methodMap)) {
|
|
@@ -257,8 +260,18 @@ export class AssistantIpcServer {
|
|
|
257
260
|
await ensureSocketPathFree(this.socketPath);
|
|
258
261
|
|
|
259
262
|
this.server = this.createListeningServer();
|
|
260
|
-
|
|
261
|
-
|
|
263
|
+
await new Promise<void>((resolve, reject) => {
|
|
264
|
+
const server = this.server!;
|
|
265
|
+
const onError = (err: Error): void => {
|
|
266
|
+
this.server = null;
|
|
267
|
+
reject(err);
|
|
268
|
+
};
|
|
269
|
+
server.once("error", onError);
|
|
270
|
+
server.listen(ipcListenOptions(this.socketPath), () => {
|
|
271
|
+
server.off("error", onError);
|
|
272
|
+
log.info({ path: this.socketPath }, "Assistant IPC server listening");
|
|
273
|
+
resolve();
|
|
274
|
+
});
|
|
262
275
|
});
|
|
263
276
|
|
|
264
277
|
this.watchdog.start();
|
|
@@ -290,13 +303,7 @@ export class AssistantIpcServer {
|
|
|
290
303
|
this.server = null;
|
|
291
304
|
}
|
|
292
305
|
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
unlinkSync(this.socketPath);
|
|
296
|
-
} catch {
|
|
297
|
-
// Ignore
|
|
298
|
-
}
|
|
299
|
-
}
|
|
306
|
+
removeIpcEndpointFile(this.socketPath);
|
|
300
307
|
}
|
|
301
308
|
|
|
302
309
|
/** Get the socket path (for diagnostics). */
|
package/src/ipc/cli-client.ts
CHANGED
|
@@ -58,6 +58,15 @@ export interface CliIpcCallResult<T = unknown> {
|
|
|
58
58
|
* when the originating daemon-side error carried a `details` field.
|
|
59
59
|
*/
|
|
60
60
|
errorDetails?: unknown;
|
|
61
|
+
/**
|
|
62
|
+
* Set when the call was abandoned because `timeoutMs` elapsed with no
|
|
63
|
+
* response. Distinct from every other `ok: false` shape: the request WAS
|
|
64
|
+
* delivered and the daemon may still be executing it — closing the client
|
|
65
|
+
* socket does not abort the handler. Callers that would otherwise retry a
|
|
66
|
+
* transport failure must not retry this one until the original can no
|
|
67
|
+
* longer be in flight.
|
|
68
|
+
*/
|
|
69
|
+
timedOut?: boolean;
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/**
|
|
@@ -178,7 +187,7 @@ export async function cliIpcCall<T = unknown>(
|
|
|
178
187
|
{ method, socketPath, timeoutMs: callTimeoutMs },
|
|
179
188
|
"CLI IPC call timed out waiting for response",
|
|
180
189
|
);
|
|
181
|
-
finish({ ok: false, error: "Request timed out" });
|
|
190
|
+
finish({ ok: false, error: "Request timed out", timedOut: true });
|
|
182
191
|
}, callTimeoutMs);
|
|
183
192
|
|
|
184
193
|
socket.on("data", (chunk) => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon-side handler for the worker → daemon documents-changed hand-off.
|
|
3
|
+
* A sidecar worker's own hub has no SSE subscriber, so it asks the daemon to
|
|
4
|
+
* republish the invalidation where clients actually observe it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
const publishCalls: Array<string | undefined> = [];
|
|
10
|
+
|
|
11
|
+
mock.module("../../../runtime/sync/resource-sync-events.js", () => ({
|
|
12
|
+
publishDocumentsChanged: (originClientId?: string) => {
|
|
13
|
+
publishCalls.push(originClientId);
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { DB_MIGRATION_READINESS_EXEMPT_OPERATIONS } from "../../../daemon/daemon-readiness.js";
|
|
18
|
+
import { NOTIFY_DOCUMENTS_CHANGED_IPC_METHOD } from "../../../runtime/sync/worker-daemon-notify.js";
|
|
19
|
+
import {
|
|
20
|
+
DOCUMENTS_SYNC_IPC_METHODS,
|
|
21
|
+
handleNotifyDocumentsChanged,
|
|
22
|
+
} from "../documents-sync-ipc-routes.js";
|
|
23
|
+
|
|
24
|
+
describe("documents-sync IPC route", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
publishCalls.length = 0;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("republishes the documents-changed invalidation on the daemon", () => {
|
|
30
|
+
const result = handleNotifyDocumentsChanged({ body: {} });
|
|
31
|
+
|
|
32
|
+
expect(result).toEqual({ ok: true });
|
|
33
|
+
expect(publishCalls).toEqual([undefined]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("carries no origin client id, so no client suppresses it", () => {
|
|
37
|
+
// The hand-off comes from a background turn with no originating client.
|
|
38
|
+
handleNotifyDocumentsChanged({ body: {} });
|
|
39
|
+
|
|
40
|
+
expect(publishCalls[0]).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("is reachable on the IPC surface under the shared method name", () => {
|
|
44
|
+
expect(
|
|
45
|
+
typeof DOCUMENTS_SYNC_IPC_METHODS[NOTIFY_DOCUMENTS_CHANGED_IPC_METHOD],
|
|
46
|
+
).toBe("function");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("is DB-migration readiness gated (absent from the exempt set)", () => {
|
|
50
|
+
expect(
|
|
51
|
+
DB_MIGRATION_READINESS_EXEMPT_OPERATIONS.has(
|
|
52
|
+
NOTIFY_DOCUMENTS_CHANGED_IPC_METHOD,
|
|
53
|
+
),
|
|
54
|
+
).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPC-only route the sidecar workers (schedule, memory) call after a turn
|
|
3
|
+
* changed a document.
|
|
4
|
+
*
|
|
5
|
+
* Workers disable SSE seq stamping (`disableStreamSeqStamping`) so the daemon
|
|
6
|
+
* is the sole seq authority, and the SSE subscribers live in the daemon too. A
|
|
7
|
+
* worker that published `documents:list` on its own hub would reach nobody, so
|
|
8
|
+
* a scheduled or background turn's document edit would stay invisible: the
|
|
9
|
+
* conversation assets pill and the Library would keep serving their cached
|
|
10
|
+
* list. That is precisely the case the changed-document surfacing exists for.
|
|
11
|
+
*
|
|
12
|
+
* This route runs the publish on the daemon instead, where real subscribers
|
|
13
|
+
* observe it. The hand-off carries no originating client, so nothing suppresses
|
|
14
|
+
* the broadcast as its own echo.
|
|
15
|
+
*
|
|
16
|
+
* IPC-only: registered directly on the assistant IPC server (see
|
|
17
|
+
* `assistant-server.ts`), never in the shared `ROUTES` array. The handler
|
|
18
|
+
* touches no database, but the IPC server's uniform DB-migration gate still
|
|
19
|
+
* applies (the method is not exempt); the worker's call is best-effort and
|
|
20
|
+
* tolerates that.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { RouteHandlerArgs } from "../../runtime/routes/types.js";
|
|
24
|
+
import { publishDocumentsChanged } from "../../runtime/sync/resource-sync-events.js";
|
|
25
|
+
import { NOTIFY_DOCUMENTS_CHANGED_IPC_METHOD } from "../../runtime/sync/worker-daemon-notify.js";
|
|
26
|
+
|
|
27
|
+
/** Republish a worker's documents-changed invalidation to daemon subscribers. */
|
|
28
|
+
export function handleNotifyDocumentsChanged(_args: RouteHandlerArgs) {
|
|
29
|
+
publishDocumentsChanged();
|
|
30
|
+
return { ok: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* IPC-only documents-sync methods, keyed by operationId. Registered directly on
|
|
35
|
+
* the assistant IPC server (see `assistant-server.ts`).
|
|
36
|
+
*/
|
|
37
|
+
export const DOCUMENTS_SYNC_IPC_METHODS: Record<
|
|
38
|
+
string,
|
|
39
|
+
(args: RouteHandlerArgs) => unknown
|
|
40
|
+
> = {
|
|
41
|
+
[NOTIFY_DOCUMENTS_CHANGED_IPC_METHOD]: handleNotifyDocumentsChanged,
|
|
42
|
+
};
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* inode, leaving two daemons in conflict with no error.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
11
|
import { connect } from "node:net";
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
isNamedPipePath,
|
|
15
|
+
removeIpcEndpointFile,
|
|
16
|
+
} from "@vellumai/ipc-server-utils";
|
|
17
|
+
|
|
13
18
|
/**
|
|
14
19
|
* Maximum time to wait for the probe `connect()` to settle before declaring
|
|
15
20
|
* the path occupied. Without a bound, a hung process holding the socket would
|
|
@@ -44,7 +49,8 @@ function makeAddrInUseError(message: string): NodeJS.ErrnoException {
|
|
|
44
49
|
* - Any other socket error → propagate.
|
|
45
50
|
*/
|
|
46
51
|
export async function ensureSocketPathFree(socketPath: string): Promise<void> {
|
|
47
|
-
|
|
52
|
+
const namedPipe = isNamedPipePath(socketPath);
|
|
53
|
+
if (!namedPipe && !existsSync(socketPath)) {
|
|
48
54
|
return;
|
|
49
55
|
}
|
|
50
56
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -81,11 +87,7 @@ export async function ensureSocketPathFree(socketPath: string): Promise<void> {
|
|
|
81
87
|
client.once("error", (err: NodeJS.ErrnoException) => {
|
|
82
88
|
if (err.code === "ECONNREFUSED" || err.code === "ENOENT") {
|
|
83
89
|
settle(() => {
|
|
84
|
-
|
|
85
|
-
unlinkSync(socketPath);
|
|
86
|
-
} catch {
|
|
87
|
-
// Ignore — may already be gone
|
|
88
|
-
}
|
|
90
|
+
removeIpcEndpointFile(socketPath);
|
|
89
91
|
resolve();
|
|
90
92
|
});
|
|
91
93
|
} else {
|
package/src/ipc/socket-path.ts
CHANGED
|
@@ -1,106 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
1
|
+
import { resolveIpcEndpoint } from "@vellumai/ipc-server-utils";
|
|
4
2
|
|
|
5
3
|
import { getWorkspaceDir } from "../util/platform.js";
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
const DEFAULT_UNIX_SOCKET_MAX_PATH_BYTES = 107;
|
|
9
|
-
const IPC_TMP_DIR_NAME = "vellum-ipc";
|
|
10
|
-
|
|
11
|
-
export type IpcSocketPathSource =
|
|
12
|
-
| "env-override"
|
|
13
|
-
| "workspace"
|
|
14
|
-
| "tmp-hash"
|
|
15
|
-
| "tmp-short-hash";
|
|
16
|
-
|
|
17
|
-
export interface IpcSocketPathResolution {
|
|
5
|
+
export function resolveIpcSocketPath(socketName: string): {
|
|
18
6
|
path: string;
|
|
19
|
-
source:
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getUnixSocketMaxPathBytes(): number {
|
|
23
|
-
return process.platform === "darwin"
|
|
24
|
-
? DARWIN_UNIX_SOCKET_MAX_PATH_BYTES
|
|
25
|
-
: DEFAULT_UNIX_SOCKET_MAX_PATH_BYTES;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isPathWithinSocketLimit(path: string, maxPathBytes: number): boolean {
|
|
29
|
-
return Buffer.byteLength(path, "utf8") <= maxPathBytes;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Derive the env var name and socket filename from a socket name.
|
|
34
|
-
*
|
|
35
|
-
* Examples (hyphens in the name become underscores in the env var):
|
|
36
|
-
* "gateway" → { envVar: "GATEWAY_IPC_SOCKET_DIR", fileName: "gateway.sock" }
|
|
37
|
-
* "assistant" → { envVar: "ASSISTANT_IPC_SOCKET_DIR", fileName: "assistant.sock" }
|
|
38
|
-
*/
|
|
39
|
-
function deriveSocketNames(socketName: string): {
|
|
40
|
-
envVar: string;
|
|
41
|
-
fileName: string;
|
|
7
|
+
source: string;
|
|
42
8
|
} {
|
|
43
|
-
|
|
44
|
-
const fileName = `${socketName}.sock`;
|
|
45
|
-
return { envVar, fileName };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the path to an IPC socket file.
|
|
50
|
-
*
|
|
51
|
-
* Resolution order:
|
|
52
|
-
* 1. `${SOCKET_NAME}_IPC_SOCKET_DIR` env var override — used in
|
|
53
|
-
* containerized deployments (emptyDir volume) and by hatch on macOS
|
|
54
|
-
* when the workspace path would exceed the AF_UNIX limit.
|
|
55
|
-
* 2. `{workspaceDir}/{socketName}.sock` — default for local dev.
|
|
56
|
-
* 3. tmpdir fallback — if the workspace path exceeds the platform's
|
|
57
|
-
* AF_UNIX path limit (103 bytes on macOS, 107 on Linux).
|
|
58
|
-
*/
|
|
59
|
-
export function resolveIpcSocketPath(
|
|
60
|
-
socketName: string,
|
|
61
|
-
): IpcSocketPathResolution {
|
|
62
|
-
const workspaceDir = getWorkspaceDir();
|
|
63
|
-
const { envVar, fileName } = deriveSocketNames(socketName);
|
|
64
|
-
|
|
65
|
-
// Explicit override via env var.
|
|
66
|
-
const envSocketDir = process.env[envVar]?.trim();
|
|
67
|
-
if (envSocketDir) {
|
|
68
|
-
return {
|
|
69
|
-
path: join(envSocketDir, fileName),
|
|
70
|
-
source: "env-override",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const maxPathBytes = getUnixSocketMaxPathBytes();
|
|
75
|
-
const workspacePath = join(workspaceDir, fileName);
|
|
76
|
-
|
|
77
|
-
if (isPathWithinSocketLimit(workspacePath, maxPathBytes)) {
|
|
78
|
-
return {
|
|
79
|
-
path: workspacePath,
|
|
80
|
-
source: "workspace",
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Workspace path exceeds AF_UNIX limit — fall back to tmpdir.
|
|
85
|
-
const hash = createHash("sha256")
|
|
86
|
-
.update(workspacePath)
|
|
87
|
-
.digest("hex")
|
|
88
|
-
.slice(0, 12);
|
|
89
|
-
const hashedPath = join(tmpdir(), IPC_TMP_DIR_NAME, `${hash}-${fileName}`);
|
|
90
|
-
if (isPathWithinSocketLimit(hashedPath, maxPathBytes)) {
|
|
91
|
-
return {
|
|
92
|
-
path: hashedPath,
|
|
93
|
-
source: "tmp-hash",
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
path: join(tmpdir(), `v-${hash}.sock`),
|
|
99
|
-
source: "tmp-short-hash",
|
|
100
|
-
};
|
|
9
|
+
return resolveIpcEndpoint(socketName, { workspaceDir: getWorkspaceDir() });
|
|
101
10
|
}
|
|
102
11
|
|
|
103
|
-
/** Shorthand — resolves the assistant CLI IPC socket path. */
|
|
104
12
|
export function getAssistantSocketPath(): string {
|
|
105
13
|
return resolveIpcSocketPath("assistant").path;
|
|
106
14
|
}
|
|
@@ -96,7 +96,7 @@ describe("live voice audio archive", () => {
|
|
|
96
96
|
const { message } = await createMessage("user");
|
|
97
97
|
const audio = Buffer.from("user audio bytes");
|
|
98
98
|
|
|
99
|
-
const result = archiveLiveVoiceUserUtteranceAudio({
|
|
99
|
+
const result = await archiveLiveVoiceUserUtteranceAudio({
|
|
100
100
|
messageId: message.id,
|
|
101
101
|
sessionId: "session-123",
|
|
102
102
|
turnId: "turn-abc",
|
|
@@ -148,7 +148,7 @@ describe("live voice audio archive", () => {
|
|
|
148
148
|
const audio = Buffer.from("assistant audio bytes");
|
|
149
149
|
writeFileSync(sourcePath, audio);
|
|
150
150
|
|
|
151
|
-
const result = archiveLiveVoiceAssistantResponseAudio({
|
|
151
|
+
const result = await archiveLiveVoiceAssistantResponseAudio({
|
|
152
152
|
messageId: message.id,
|
|
153
153
|
sessionId: "session-456",
|
|
154
154
|
turnId: "turn-def",
|
|
@@ -202,7 +202,7 @@ describe("live voice audio archive", () => {
|
|
|
202
202
|
test("links user utterance audio to a persisted user message id", async () => {
|
|
203
203
|
const { message } = await createMessage("user");
|
|
204
204
|
|
|
205
|
-
const result = linkLiveVoiceUserUtteranceAudioToMessage({
|
|
205
|
+
const result = await linkLiveVoiceUserUtteranceAudioToMessage({
|
|
206
206
|
messageId: message.id,
|
|
207
207
|
sessionId: "session-user-link",
|
|
208
208
|
turnId: "turn-user-link",
|
|
@@ -228,7 +228,7 @@ describe("live voice audio archive", () => {
|
|
|
228
228
|
test("links assistant response audio when the assistant message id is available", async () => {
|
|
229
229
|
const { message } = await createMessage("assistant");
|
|
230
230
|
|
|
231
|
-
const result = linkLiveVoiceAssistantResponseAudioToMessage({
|
|
231
|
+
const result = await linkLiveVoiceAssistantResponseAudioToMessage({
|
|
232
232
|
messageId: message.id,
|
|
233
233
|
sessionId: "session-assistant-link",
|
|
234
234
|
turnId: "turn-assistant-link",
|
|
@@ -254,8 +254,8 @@ describe("live voice audio archive", () => {
|
|
|
254
254
|
expect(getLiveVoiceArtifacts(message.id)).toEqual([result.artifact]);
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
test("returns an unlinked result when the assistant message id is unavailable", () => {
|
|
258
|
-
const result = linkLiveVoiceAssistantResponseAudioToMessage({
|
|
257
|
+
test("returns an unlinked result when the assistant message id is unavailable", async () => {
|
|
258
|
+
const result = await linkLiveVoiceAssistantResponseAudioToMessage({
|
|
259
259
|
messageId: undefined,
|
|
260
260
|
sessionId: "session-assistant-unlinked",
|
|
261
261
|
turnId: "turn-assistant-unlinked",
|
|
@@ -283,7 +283,7 @@ describe("live voice audio archive", () => {
|
|
|
283
283
|
test("is idempotent for the session turn role key", async () => {
|
|
284
284
|
const { message } = await createMessage("user");
|
|
285
285
|
|
|
286
|
-
const first = archiveLiveVoiceAudioArtifact({
|
|
286
|
+
const first = await archiveLiveVoiceAudioArtifact({
|
|
287
287
|
messageId: message.id,
|
|
288
288
|
sessionId: "session-repeat",
|
|
289
289
|
turnId: "turn-repeat",
|
|
@@ -295,7 +295,7 @@ describe("live voice audio archive", () => {
|
|
|
295
295
|
dataBase64: Buffer.from("first audio").toString("base64"),
|
|
296
296
|
},
|
|
297
297
|
});
|
|
298
|
-
const second = archiveLiveVoiceAudioArtifact({
|
|
298
|
+
const second = await archiveLiveVoiceAudioArtifact({
|
|
299
299
|
messageId: message.id,
|
|
300
300
|
sessionId: "session-repeat",
|
|
301
301
|
turnId: "turn-repeat",
|
|
@@ -320,7 +320,7 @@ describe("live voice audio archive", () => {
|
|
|
320
320
|
"first audio",
|
|
321
321
|
);
|
|
322
322
|
|
|
323
|
-
const assistantResult = archiveLiveVoiceAudioArtifact({
|
|
323
|
+
const assistantResult = await archiveLiveVoiceAudioArtifact({
|
|
324
324
|
messageId: message.id,
|
|
325
325
|
sessionId: "session-repeat",
|
|
326
326
|
turnId: "turn-repeat",
|
|
@@ -337,7 +337,7 @@ describe("live voice audio archive", () => {
|
|
|
337
337
|
|
|
338
338
|
test("restores metadata idempotency from the deterministic attachment filename", async () => {
|
|
339
339
|
const { message } = await createMessage("assistant");
|
|
340
|
-
const first = archiveLiveVoiceAssistantResponseAudio({
|
|
340
|
+
const first = await archiveLiveVoiceAssistantResponseAudio({
|
|
341
341
|
messageId: message.id,
|
|
342
342
|
sessionId: "session-crash",
|
|
343
343
|
turnId: "turn-crash",
|
|
@@ -360,7 +360,7 @@ describe("live voice audio archive", () => {
|
|
|
360
360
|
message.id,
|
|
361
361
|
);
|
|
362
362
|
|
|
363
|
-
const second = archiveLiveVoiceAssistantResponseAudio({
|
|
363
|
+
const second = await archiveLiveVoiceAssistantResponseAudio({
|
|
364
364
|
messageId: message.id,
|
|
365
365
|
sessionId: "session-crash",
|
|
366
366
|
turnId: "turn-crash",
|
|
@@ -408,7 +408,7 @@ describe("live voice audio archive", () => {
|
|
|
408
408
|
},
|
|
409
409
|
);
|
|
410
410
|
|
|
411
|
-
const archived = archiveLiveVoiceUserUtteranceAudio({
|
|
411
|
+
const archived = await archiveLiveVoiceUserUtteranceAudio({
|
|
412
412
|
messageId: sourceMessage.id,
|
|
413
413
|
sessionId: "session-artifact-link",
|
|
414
414
|
turnId: "turn-artifact-link",
|
|
@@ -452,7 +452,7 @@ describe("live voice audio archive", () => {
|
|
|
452
452
|
test("returns typed warnings for non-fatal archive failures", async () => {
|
|
453
453
|
const { message } = await createMessage("user");
|
|
454
454
|
|
|
455
|
-
const missingFile = archiveLiveVoiceUserUtteranceAudio({
|
|
455
|
+
const missingFile = await archiveLiveVoiceUserUtteranceAudio({
|
|
456
456
|
messageId: message.id,
|
|
457
457
|
sessionId: "session-warning",
|
|
458
458
|
turnId: "turn-warning",
|
|
@@ -470,7 +470,7 @@ describe("live voice audio archive", () => {
|
|
|
470
470
|
},
|
|
471
471
|
});
|
|
472
472
|
|
|
473
|
-
const unsupportedMime = archiveLiveVoiceUserUtteranceAudio({
|
|
473
|
+
const unsupportedMime = await archiveLiveVoiceUserUtteranceAudio({
|
|
474
474
|
messageId: message.id,
|
|
475
475
|
sessionId: "session-warning",
|
|
476
476
|
turnId: "turn-warning-2",
|
|
@@ -488,7 +488,7 @@ describe("live voice audio archive", () => {
|
|
|
488
488
|
},
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
-
const missingMessage = archiveLiveVoiceUserUtteranceAudio({
|
|
491
|
+
const missingMessage = await archiveLiveVoiceUserUtteranceAudio({
|
|
492
492
|
messageId: "missing-message",
|
|
493
493
|
sessionId: "session-warning",
|
|
494
494
|
turnId: "turn-warning-3",
|
|
@@ -509,7 +509,7 @@ describe("live voice audio archive", () => {
|
|
|
509
509
|
test("keeps archive metadata scoped to allowed live voice fields", async () => {
|
|
510
510
|
const { message } = await createMessage("assistant");
|
|
511
511
|
|
|
512
|
-
const result = archiveLiveVoiceAssistantResponseAudio({
|
|
512
|
+
const result = await archiveLiveVoiceAssistantResponseAudio({
|
|
513
513
|
messageId: message.id,
|
|
514
514
|
sessionId: "session-metadata",
|
|
515
515
|
turnId: "turn-metadata",
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Photos taken mid-call (the voice room's camera).
|
|
3
|
+
*
|
|
4
|
+
* The behaviour under test is the parking rule: an `attach_image` frame does
|
|
5
|
+
* not dispatch anything, it waits for the next turn and rides that turn's own
|
|
6
|
+
* user message. That is what makes a bare "what's this?" resolve against the
|
|
7
|
+
* picture instead of producing one turn about the image racing another about
|
|
8
|
+
* the words.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import type { VoiceTurnOptions } from "../../calls/voice-session-bridge.js";
|
|
14
|
+
import type {
|
|
15
|
+
StreamingTranscriber,
|
|
16
|
+
SttStreamServerEvent,
|
|
17
|
+
} from "../../stt/types.js";
|
|
18
|
+
import {
|
|
19
|
+
LiveVoiceSession,
|
|
20
|
+
type LiveVoiceTurnStarter,
|
|
21
|
+
} from "../live-voice-session.js";
|
|
22
|
+
import type { LiveVoiceSessionFactoryContext } from "../live-voice-session-manager.js";
|
|
23
|
+
import {
|
|
24
|
+
createLiveVoiceServerFrameSequencer,
|
|
25
|
+
type LiveVoiceClientStartFrame,
|
|
26
|
+
type LiveVoiceServerFrame,
|
|
27
|
+
validateLiveVoiceClientFrame,
|
|
28
|
+
} from "../protocol.js";
|
|
29
|
+
|
|
30
|
+
const START_FRAME = {
|
|
31
|
+
type: "start",
|
|
32
|
+
conversationId: "conversation-123",
|
|
33
|
+
audio: { mimeType: "audio/pcm", sampleRate: 24_000, channels: 1 },
|
|
34
|
+
} as const satisfies LiveVoiceClientStartFrame;
|
|
35
|
+
|
|
36
|
+
class MockStreamingTranscriber implements StreamingTranscriber {
|
|
37
|
+
readonly providerId = "deepgram" as const;
|
|
38
|
+
readonly boundaryId = "daemon-streaming" as const;
|
|
39
|
+
private onEvent: ((event: SttStreamServerEvent) => void) | null = null;
|
|
40
|
+
|
|
41
|
+
async start(onEvent: (event: SttStreamServerEvent) => void): Promise<void> {
|
|
42
|
+
this.onEvent = onEvent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sendAudio(): void {}
|
|
46
|
+
|
|
47
|
+
stop(): void {
|
|
48
|
+
this.emit({ type: "closed" });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
emit(event: SttStreamServerEvent): void {
|
|
52
|
+
this.onEvent?.(event);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createSessionHarness() {
|
|
57
|
+
const sequencer = createLiveVoiceServerFrameSequencer();
|
|
58
|
+
const frames: LiveVoiceServerFrame[] = [];
|
|
59
|
+
const context: LiveVoiceSessionFactoryContext = {
|
|
60
|
+
sessionId: "session-123",
|
|
61
|
+
startFrame: START_FRAME,
|
|
62
|
+
sendFrame: mock(async (payload) => {
|
|
63
|
+
const frame = sequencer.next(payload);
|
|
64
|
+
frames.push(frame);
|
|
65
|
+
return frame;
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const transcriber = new MockStreamingTranscriber();
|
|
70
|
+
const startVoiceTurn: LiveVoiceTurnStarter = mock(
|
|
71
|
+
async (_options: VoiceTurnOptions) => ({
|
|
72
|
+
turnId: "bridge-turn-1",
|
|
73
|
+
abort: mock(),
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const session = new LiveVoiceSession(context, {
|
|
78
|
+
resolveTranscriber: mock(async () => transcriber),
|
|
79
|
+
startVoiceTurn,
|
|
80
|
+
createTurnId: () => "live-turn-1",
|
|
81
|
+
emitMetrics: false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return { frames, session, startVoiceTurn, transcriber };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Drive one utterance to a dispatched assistant turn. */
|
|
88
|
+
async function speakAndRelease(
|
|
89
|
+
harness: Awaited<ReturnType<typeof createSessionHarness>>,
|
|
90
|
+
text: string,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
harness.transcriber.emit({ type: "final", text });
|
|
93
|
+
await harness.session.handleClientFrame({ type: "ptt_release" });
|
|
94
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
95
|
+
if (
|
|
96
|
+
(harness.startVoiceTurn as ReturnType<typeof mock>).mock.calls.length > 0
|
|
97
|
+
) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function turnOptionsAt(
|
|
105
|
+
startVoiceTurn: LiveVoiceTurnStarter,
|
|
106
|
+
index: number,
|
|
107
|
+
): VoiceTurnOptions | undefined {
|
|
108
|
+
return (startVoiceTurn as ReturnType<typeof mock>).mock.calls[index]?.[0] as
|
|
109
|
+
| VoiceTurnOptions
|
|
110
|
+
| undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe("live-voice attach_image frame", () => {
|
|
114
|
+
test("accepts a well-formed frame", () => {
|
|
115
|
+
const result = validateLiveVoiceClientFrame({
|
|
116
|
+
type: "attach_image",
|
|
117
|
+
attachmentId: "att-1",
|
|
118
|
+
});
|
|
119
|
+
expect(result).toEqual({
|
|
120
|
+
ok: true,
|
|
121
|
+
frame: { type: "attach_image", attachmentId: "att-1" },
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("rejects a missing attachmentId", () => {
|
|
126
|
+
const result = validateLiveVoiceClientFrame({ type: "attach_image" });
|
|
127
|
+
expect(result.ok).toBe(false);
|
|
128
|
+
if (!result.ok) {
|
|
129
|
+
expect(result.error.code).toBe("missing_required_field");
|
|
130
|
+
expect(result.error.field).toBe("attachmentId");
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("rejects an empty attachmentId", () => {
|
|
135
|
+
const result = validateLiveVoiceClientFrame({
|
|
136
|
+
type: "attach_image",
|
|
137
|
+
attachmentId: "",
|
|
138
|
+
});
|
|
139
|
+
expect(result.ok).toBe(false);
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
expect(result.error.code).toBe("invalid_field");
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("live-voice photos taken mid-call", () => {
|
|
147
|
+
test("a photo does not dispatch a turn of its own", async () => {
|
|
148
|
+
// The photo becomes its own user message via `persistLiveVoicePhoto`; no
|
|
149
|
+
// assistant turn runs for it. Dispatching one would answer the picture
|
|
150
|
+
// while the user is still saying the sentence it belongs to.
|
|
151
|
+
const harness = createSessionHarness();
|
|
152
|
+
await harness.session.start();
|
|
153
|
+
|
|
154
|
+
await harness.session.handleClientFrame({
|
|
155
|
+
type: "attach_image",
|
|
156
|
+
attachmentId: "att-1",
|
|
157
|
+
});
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
159
|
+
|
|
160
|
+
expect(harness.startVoiceTurn).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a spoken turn carries no attachments of its own", async () => {
|
|
164
|
+
// Photos reach the model through conversation history, not through the
|
|
165
|
+
// turn's options, which is what makes shutter-then-speak and
|
|
166
|
+
// speak-then-shutter behave identically.
|
|
167
|
+
const harness = createSessionHarness();
|
|
168
|
+
await harness.session.start();
|
|
169
|
+
|
|
170
|
+
await harness.session.handleClientFrame({
|
|
171
|
+
type: "attach_image",
|
|
172
|
+
attachmentId: "att-1",
|
|
173
|
+
});
|
|
174
|
+
await speakAndRelease(harness, "what's this");
|
|
175
|
+
|
|
176
|
+
const options = turnOptionsAt(harness.startVoiceTurn, 0) as
|
|
177
|
+
| (VoiceTurnOptions & { attachmentIds?: string[] })
|
|
178
|
+
| undefined;
|
|
179
|
+
expect(options?.content).toBe("what's this");
|
|
180
|
+
expect(options?.attachmentIds).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("a photo that cannot be stored is reported to the client", async () => {
|
|
184
|
+
// `att-missing` resolves to nothing (no attachment row in this harness),
|
|
185
|
+
// so the persist fails. Silence would leave the user believing the
|
|
186
|
+
// assistant can see something it never received.
|
|
187
|
+
const harness = createSessionHarness();
|
|
188
|
+
await harness.session.start();
|
|
189
|
+
const before = harness.frames.length;
|
|
190
|
+
|
|
191
|
+
await harness.session.handleClientFrame({
|
|
192
|
+
type: "attach_image",
|
|
193
|
+
attachmentId: "att-missing",
|
|
194
|
+
});
|
|
195
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
196
|
+
if (harness.frames.length > before) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const error = harness.frames
|
|
203
|
+
.slice(before)
|
|
204
|
+
.find((frame) => frame.type === "error");
|
|
205
|
+
expect(error).toMatchObject({ type: "error", recoverable: true });
|
|
206
|
+
// The session survives it: one failed photo is not a failed call.
|
|
207
|
+
expect(harness.startVoiceTurn).not.toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
});
|