@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
|
@@ -28,7 +28,10 @@ import { deliverChannelReply } from "../runtime/gateway-client.js";
|
|
|
28
28
|
import { introductionMode } from "../runtime/introduction-policy.js";
|
|
29
29
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
30
30
|
import { getLogger } from "../util/logger.js";
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
resolveDeliverCallbackUrlForChannel,
|
|
33
|
+
resolveRequesterDeliveryTarget,
|
|
34
|
+
} from "./guardian-channel-delivery.js";
|
|
32
35
|
|
|
33
36
|
const log = getLogger("guardian-expiry-notifier");
|
|
34
37
|
|
|
@@ -108,9 +111,10 @@ function releaseExpiredInteraction(request: GuardianRequestWire): void {
|
|
|
108
111
|
*
|
|
109
112
|
* The sweep is timer-driven and holds no inbound reply callback URL, so this
|
|
110
113
|
* mirrors the resolvers' off-channel (desktop) delivery path: post to the
|
|
111
|
-
* callback-less `/deliver/<channel>` route. On Slack the notice is
|
|
112
|
-
* the requester's DM via their user id rather than the channel id,
|
|
113
|
-
* never posted into a shared channel
|
|
114
|
+
* callback-less `/deliver/<channel>` route. On Slack and Discord the notice is
|
|
115
|
+
* routed to the requester's DM via their user id rather than the channel id,
|
|
116
|
+
* so it is never posted into a shared channel (see
|
|
117
|
+
* `resolveRequesterDeliveryTarget`). No-ops on channels without a deliverable
|
|
114
118
|
* route (e.g. email, the in-app vellum surface) or when the requester chat is
|
|
115
119
|
* unknown. Best-effort: a delivery failure is logged, never thrown.
|
|
116
120
|
*/
|
|
@@ -128,13 +132,11 @@ async function notifyRequesterOfExpiry(
|
|
|
128
132
|
return;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
? requesterExternalUserId
|
|
137
|
-
: requesterChatId;
|
|
135
|
+
const targetChatId = resolveRequesterDeliveryTarget({
|
|
136
|
+
channel,
|
|
137
|
+
requesterChatId,
|
|
138
|
+
requesterExternalUserId,
|
|
139
|
+
});
|
|
138
140
|
|
|
139
141
|
try {
|
|
140
142
|
await deliverChannelReply(deliverUrl, {
|
|
@@ -56,7 +56,13 @@ import {
|
|
|
56
56
|
} from "../runtime/question-resolution.js";
|
|
57
57
|
import { TC_GRANT_WAIT_MAX_MS } from "../tools/tool-approval-handler.js";
|
|
58
58
|
import { getLogger } from "../util/logger.js";
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
channelCanAddressOneReaderInBand,
|
|
61
|
+
channelCanCompleteCodeHandshakeInDm,
|
|
62
|
+
channelDeliversToUserId,
|
|
63
|
+
resolveDeliverCallbackUrlForChannel,
|
|
64
|
+
resolveRequesterDeliveryTarget,
|
|
65
|
+
} from "./guardian-channel-delivery.js";
|
|
60
66
|
|
|
61
67
|
const log = getLogger("guardian-request-resolvers");
|
|
62
68
|
|
|
@@ -97,14 +103,15 @@ function stripThreadTsParam(replyCallbackUrl: string): string {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/**
|
|
100
|
-
* Deliver the verification code straight to the requester's
|
|
106
|
+
* Deliver the verification code straight to the requester's DM so the
|
|
101
107
|
* guardian is never an out-of-band courier for the secret.
|
|
102
108
|
*
|
|
103
|
-
*
|
|
104
|
-
* posting to
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
109
|
+
* Used on the channels with a guaranteed private path to a requester's user
|
|
110
|
+
* id: Slack, where posting to a `U…` id opens a 1:1 DM, and Discord, whose
|
|
111
|
+
* `dm`-marked route resolves a user snowflake to a DM channel. The `threadTs`
|
|
112
|
+
* query param is dropped because it points at the guardian's channel thread
|
|
113
|
+
* and would raise `thread_not_found` in the DM. The code is sent as a durable
|
|
114
|
+
* (non-ephemeral) message the requester can refer back to when verifying.
|
|
108
115
|
*
|
|
109
116
|
* The verification session is identity-bound to the requester, so delivering
|
|
110
117
|
* the code to them directly does not widen who can consume it — it only removes
|
|
@@ -112,7 +119,7 @@ function stripThreadTsParam(replyCallbackUrl: string): string {
|
|
|
112
119
|
*
|
|
113
120
|
* Returns whether the code was delivered.
|
|
114
121
|
*/
|
|
115
|
-
async function
|
|
122
|
+
async function deliverVerificationCodeToRequester(params: {
|
|
116
123
|
replyCallbackUrl: string;
|
|
117
124
|
requesterExternalUserId: string;
|
|
118
125
|
verificationCode: string;
|
|
@@ -133,12 +140,40 @@ async function deliverVerificationCodeToSlackRequester(params: {
|
|
|
133
140
|
} catch (err) {
|
|
134
141
|
log.error(
|
|
135
142
|
{ err, requesterExternalUserId: params.requesterExternalUserId },
|
|
136
|
-
"Failed to auto-deliver verification code to
|
|
143
|
+
"Failed to auto-deliver verification code to requester",
|
|
137
144
|
);
|
|
138
145
|
return false;
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
/**
|
|
150
|
+
* The guardian-facing verification-code message.
|
|
151
|
+
*
|
|
152
|
+
* Delivered on up to three routes (in band, a guardian DM follow-up, and a
|
|
153
|
+
* guardian DM when their channel has no in-band route), so the wording lives
|
|
154
|
+
* here rather than being retyped at each one.
|
|
155
|
+
*/
|
|
156
|
+
function guardianVerificationCodeText(
|
|
157
|
+
requesterLabel: string,
|
|
158
|
+
secret: string,
|
|
159
|
+
): string {
|
|
160
|
+
return (
|
|
161
|
+
`You approved access for ${requesterLabel}. ` +
|
|
162
|
+
`Give them this verification code: \`${secret}\`. ` +
|
|
163
|
+
`The code expires in 10 minutes.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Requester-facing approval notice when the guardian relays the code. */
|
|
168
|
+
const APPROVED_COURIER_NOTICE =
|
|
169
|
+
"Your access request has been approved! " +
|
|
170
|
+
"Please enter the 6-digit verification code you receive from the guardian.";
|
|
171
|
+
|
|
172
|
+
/** Requester-facing notice when the code could not be handed to the guardian. */
|
|
173
|
+
const APPROVED_CODE_UNDELIVERED_NOTICE =
|
|
174
|
+
"Your access request was approved, but we were unable to " +
|
|
175
|
+
"deliver the verification code. Please try again later.";
|
|
176
|
+
|
|
142
177
|
/**
|
|
143
178
|
* Build a requester-facing channel notice (approval/denial/courier text).
|
|
144
179
|
*
|
|
@@ -695,9 +730,15 @@ function deriveAccessRequestDecision(
|
|
|
695
730
|
* Deliver a requester-facing decision notice. On-channel decisions reply via
|
|
696
731
|
* the channel delivery context (ephemeral on Slack shared channels);
|
|
697
732
|
* off-channel (desktop) decisions post via the channel's deliver URL — on
|
|
698
|
-
* Slack routed to the requester's user ID so the notice opens a DM
|
|
699
|
-
* of posting into a shared channel. Delivery failures are logged, never
|
|
733
|
+
* Slack and Discord routed to the requester's user ID so the notice opens a DM
|
|
734
|
+
* instead of posting into a shared channel. Delivery failures are logged, never
|
|
700
735
|
* thrown: the notice is best-effort and must not fail the decision.
|
|
736
|
+
*
|
|
737
|
+
* Discord takes the deliver-URL route in both cases, because its in-band route
|
|
738
|
+
* cannot be kept to one reader (see `channelCanAddressOneReaderInBand`).
|
|
739
|
+
* Posting a decision back in-band there would announce "your access request
|
|
740
|
+
* was declined" to a whole community channel, so Discord has one route to a
|
|
741
|
+
* requester and it is a DM.
|
|
701
742
|
*/
|
|
702
743
|
async function deliverRequesterNotice(params: {
|
|
703
744
|
channel: NotificationSourceChannel;
|
|
@@ -713,11 +754,19 @@ async function deliverRequesterNotice(params: {
|
|
|
713
754
|
requesterChatId,
|
|
714
755
|
requesterExternalUserId,
|
|
715
756
|
assistantId,
|
|
716
|
-
channelDeliveryContext,
|
|
717
757
|
desktopDeliverUrl,
|
|
718
758
|
text,
|
|
719
759
|
} = params;
|
|
720
760
|
|
|
761
|
+
// Re-applied rather than assumed. The access-request resolver already drops
|
|
762
|
+
// the context for these channels, because its verification-code branch needs
|
|
763
|
+
// the same rule and never reaches this helper; repeating it here keeps the
|
|
764
|
+
// helper correct for any caller that has not, at the cost of one idempotent
|
|
765
|
+
// check.
|
|
766
|
+
const channelDeliveryContext = channelCanAddressOneReaderInBand(channel)
|
|
767
|
+
? params.channelDeliveryContext
|
|
768
|
+
: undefined;
|
|
769
|
+
|
|
721
770
|
if (channelDeliveryContext) {
|
|
722
771
|
try {
|
|
723
772
|
await deliverChannelReply(
|
|
@@ -740,10 +789,11 @@ async function deliverRequesterNotice(params: {
|
|
|
740
789
|
}
|
|
741
790
|
|
|
742
791
|
if (desktopDeliverUrl && requesterChatId) {
|
|
743
|
-
const targetChatId =
|
|
744
|
-
channel
|
|
745
|
-
|
|
746
|
-
|
|
792
|
+
const targetChatId = resolveRequesterDeliveryTarget({
|
|
793
|
+
channel,
|
|
794
|
+
requesterChatId,
|
|
795
|
+
requesterExternalUserId,
|
|
796
|
+
});
|
|
747
797
|
try {
|
|
748
798
|
await deliverChannelReply(desktopDeliverUrl, {
|
|
749
799
|
chatId: targetChatId,
|
|
@@ -775,6 +825,13 @@ async function notifyRequesterOfDenial(params: {
|
|
|
775
825
|
assistantId: string;
|
|
776
826
|
channelDeliveryContext: ChannelDeliveryContext | undefined;
|
|
777
827
|
desktopDeliverUrl: string | null;
|
|
828
|
+
/**
|
|
829
|
+
* Whether the guardian decided on a channel at all, as distinct from whether
|
|
830
|
+
* a route to the requester survived suppression. The lifecycle signal keys
|
|
831
|
+
* on the former; passing the suppressed context would drop the guardian's
|
|
832
|
+
* record of every Discord denial.
|
|
833
|
+
*/
|
|
834
|
+
decidedOnChannel: boolean;
|
|
778
835
|
deniedPayload: TrustedContactDecisionPayload;
|
|
779
836
|
requestId: string;
|
|
780
837
|
conversationId: string | null;
|
|
@@ -793,6 +850,7 @@ async function notifyRequesterOfDenial(params: {
|
|
|
793
850
|
assistantId,
|
|
794
851
|
channelDeliveryContext,
|
|
795
852
|
desktopDeliverUrl,
|
|
853
|
+
decidedOnChannel,
|
|
796
854
|
deniedPayload,
|
|
797
855
|
requestId,
|
|
798
856
|
conversationId,
|
|
@@ -820,7 +878,7 @@ async function notifyRequesterOfDenial(params: {
|
|
|
820
878
|
// conversation for the same decision. The approve path holds the same
|
|
821
879
|
// one-signal invariant via `verification_sent` standing in for
|
|
822
880
|
// `guardian_decision`.
|
|
823
|
-
if (
|
|
881
|
+
if (decidedOnChannel) {
|
|
824
882
|
void emitNotificationSignal({
|
|
825
883
|
sourceEventName: "ingress.trusted_contact.guardian_decision",
|
|
826
884
|
sourceChannel: channel,
|
|
@@ -981,6 +1039,17 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
981
1039
|
// Non-voice approvals: mint an identity-bound verification session so the
|
|
982
1040
|
// requester can verify their identity. The raw secret transits back on
|
|
983
1041
|
// the decide response for daemon-owned delivery.
|
|
1042
|
+
//
|
|
1043
|
+
// The session is addressed to the requester, not to the room the request
|
|
1044
|
+
// arrived in. On the channels that reach a person by user id, that room is
|
|
1045
|
+
// one other people can read, so recording it as the destination describes
|
|
1046
|
+
// somewhere the code is never sent. `expectedChatId` keeps the room, which
|
|
1047
|
+
// is where the conversation is, and it is not a credential: whenever both
|
|
1048
|
+
// identity fields are set, `checkIdentityMatch` requires the user match.
|
|
1049
|
+
const destinationAddress = channelDeliversToUserId(channel)
|
|
1050
|
+
? requesterExternalUserId || requesterChatId
|
|
1051
|
+
: requesterChatId;
|
|
1052
|
+
|
|
984
1053
|
return {
|
|
985
1054
|
ok: true,
|
|
986
1055
|
aclOutcome: {
|
|
@@ -989,7 +1058,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
989
1058
|
expectedExternalUserId: requesterExternalUserId,
|
|
990
1059
|
expectedChatId: requesterChatId,
|
|
991
1060
|
identityBindingStatus: "bound",
|
|
992
|
-
destinationAddress
|
|
1061
|
+
destinationAddress,
|
|
993
1062
|
verificationPurpose: "trusted_contact",
|
|
994
1063
|
},
|
|
995
1064
|
persistFailureReason: "verification_session_mint_failed",
|
|
@@ -997,7 +1066,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
997
1066
|
},
|
|
998
1067
|
|
|
999
1068
|
async resolve(ctx: ResolverContext): Promise<ResolverResult> {
|
|
1000
|
-
const { request, decision
|
|
1069
|
+
const { request, decision } = ctx;
|
|
1001
1070
|
const {
|
|
1002
1071
|
channel,
|
|
1003
1072
|
requesterExternalUserId,
|
|
@@ -1009,6 +1078,34 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1009
1078
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
1010
1079
|
const desktopDeliverUrl = resolveDeliverCallbackUrlForChannel(channel);
|
|
1011
1080
|
|
|
1081
|
+
// The reply context serves two readers on two different channels, so it is
|
|
1082
|
+
// suppressed per reader rather than wholesale.
|
|
1083
|
+
//
|
|
1084
|
+
// `requesterInBandContext` governs the requester-facing notice, which is
|
|
1085
|
+
// addressed into the REQUEST's channel: on Discord that is a guild channel
|
|
1086
|
+
// nobody can be singled out in, so it is dropped and the notice takes the
|
|
1087
|
+
// DM route the desktop path uses.
|
|
1088
|
+
//
|
|
1089
|
+
// `guardianInBandContext` governs the guardian's own copy of a verification
|
|
1090
|
+
// code, which is addressed into the channel the GUARDIAN replied on. A
|
|
1091
|
+
// Telegram guardian deciding a Discord request still has a private chat of
|
|
1092
|
+
// their own, and dropping their route because the requester's is public
|
|
1093
|
+
// would leave them with no confirmation and no code.
|
|
1094
|
+
const requesterInBandContext = channelCanAddressOneReaderInBand(channel)
|
|
1095
|
+
? ctx.channelDeliveryContext
|
|
1096
|
+
: undefined;
|
|
1097
|
+
const guardianInBandContext = channelCanAddressOneReaderInBand(
|
|
1098
|
+
ctx.actor.channel,
|
|
1099
|
+
)
|
|
1100
|
+
? ctx.channelDeliveryContext
|
|
1101
|
+
: undefined;
|
|
1102
|
+
// Where the guardian's own copy goes when their channel has no in-band
|
|
1103
|
+
// route. Resolved from THEIR channel, never the requester's, or the code
|
|
1104
|
+
// is handed to the wrong transport.
|
|
1105
|
+
const guardianDmUrl = resolveDeliverCallbackUrlForChannel(
|
|
1106
|
+
ctx.actor.channel,
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1012
1109
|
// Guardian-facing label prefers the contact display name over the raw ID.
|
|
1013
1110
|
const requesterLabel =
|
|
1014
1111
|
requesterDisplayName ||
|
|
@@ -1050,7 +1147,8 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1050
1147
|
requesterChatId,
|
|
1051
1148
|
requesterExternalUserId,
|
|
1052
1149
|
assistantId,
|
|
1053
|
-
channelDeliveryContext,
|
|
1150
|
+
channelDeliveryContext: requesterInBandContext,
|
|
1151
|
+
decidedOnChannel: ctx.channelDeliveryContext !== undefined,
|
|
1054
1152
|
desktopDeliverUrl,
|
|
1055
1153
|
deniedPayload,
|
|
1056
1154
|
requestId: request.id,
|
|
@@ -1091,7 +1189,8 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1091
1189
|
requesterChatId,
|
|
1092
1190
|
requesterExternalUserId,
|
|
1093
1191
|
assistantId,
|
|
1094
|
-
channelDeliveryContext,
|
|
1192
|
+
channelDeliveryContext: requesterInBandContext,
|
|
1193
|
+
decidedOnChannel: ctx.channelDeliveryContext !== undefined,
|
|
1095
1194
|
desktopDeliverUrl,
|
|
1096
1195
|
deniedPayload,
|
|
1097
1196
|
requestId: request.id,
|
|
@@ -1145,7 +1244,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1145
1244
|
requesterChatId,
|
|
1146
1245
|
requesterExternalUserId,
|
|
1147
1246
|
assistantId,
|
|
1148
|
-
channelDeliveryContext,
|
|
1247
|
+
channelDeliveryContext: requesterInBandContext,
|
|
1149
1248
|
desktopDeliverUrl,
|
|
1150
1249
|
text: "Your access request has been approved. You can message the assistant here.",
|
|
1151
1250
|
});
|
|
@@ -1187,39 +1286,46 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1187
1286
|
"Access request resolver: minted verification session",
|
|
1188
1287
|
);
|
|
1189
1288
|
|
|
1190
|
-
//
|
|
1191
|
-
//
|
|
1289
|
+
// Two readers, decided separately. The guardian's copy of the code is
|
|
1290
|
+
// addressed on the channel THEY replied on; the requester's notice on the
|
|
1291
|
+
// channel the request came from. Those can differ (a Telegram guardian
|
|
1292
|
+
// deciding a Discord request), and the old single `if (channelDelivery
|
|
1293
|
+
// Context)` treated them as one, so suppressing a route for either reader
|
|
1294
|
+
// silently suppressed it for both.
|
|
1192
1295
|
let requesterNotified = false;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1296
|
+
let codeDelivered = false;
|
|
1297
|
+
const codeText = guardianVerificationCodeText(
|
|
1298
|
+
requesterLabel,
|
|
1299
|
+
session.secret,
|
|
1300
|
+
);
|
|
1301
|
+
|
|
1302
|
+
if (guardianInBandContext) {
|
|
1303
|
+
codeDelivered = true;
|
|
1201
1304
|
try {
|
|
1202
1305
|
const codePayload: Parameters<typeof deliverChannelReply>[1] = {
|
|
1203
|
-
chatId:
|
|
1306
|
+
chatId: guardianInBandContext.guardianChatId,
|
|
1204
1307
|
text: codeText,
|
|
1205
1308
|
assistantId,
|
|
1206
1309
|
};
|
|
1207
1310
|
// On Slack shared channels, deliver the verification code as ephemeral
|
|
1208
1311
|
// so only the guardian sees the secret — not all channel members.
|
|
1209
1312
|
if (
|
|
1210
|
-
shouldUseEphemeral(
|
|
1313
|
+
shouldUseEphemeral(
|
|
1314
|
+
ctx.actor.channel,
|
|
1315
|
+
guardianInBandContext.guardianChatId,
|
|
1316
|
+
) &&
|
|
1211
1317
|
ctx.actor.actorExternalUserId
|
|
1212
1318
|
) {
|
|
1213
1319
|
codePayload.ephemeral = true;
|
|
1214
1320
|
codePayload.user = ctx.actor.actorExternalUserId;
|
|
1215
1321
|
}
|
|
1216
1322
|
await deliverChannelReply(
|
|
1217
|
-
|
|
1323
|
+
guardianInBandContext.replyCallbackUrl,
|
|
1218
1324
|
codePayload,
|
|
1219
1325
|
);
|
|
1220
1326
|
} catch (err) {
|
|
1221
1327
|
log.error(
|
|
1222
|
-
{ err, guardianChatId:
|
|
1328
|
+
{ err, guardianChatId: guardianInBandContext.guardianChatId },
|
|
1223
1329
|
"Failed to deliver verification code to guardian",
|
|
1224
1330
|
);
|
|
1225
1331
|
codeDelivered = false;
|
|
@@ -1231,12 +1337,12 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1231
1337
|
const guardianUserId = ctx.actor.actorExternalUserId;
|
|
1232
1338
|
if (
|
|
1233
1339
|
codeDelivered &&
|
|
1234
|
-
channel === "slack" &&
|
|
1340
|
+
ctx.actor.channel === "slack" &&
|
|
1235
1341
|
guardianUserId &&
|
|
1236
|
-
!
|
|
1342
|
+
!guardianInBandContext.guardianChatId.startsWith("D")
|
|
1237
1343
|
) {
|
|
1238
1344
|
const dmCallbackUrl = stripThreadTsParam(
|
|
1239
|
-
|
|
1345
|
+
guardianInBandContext.replyCallbackUrl,
|
|
1240
1346
|
);
|
|
1241
1347
|
|
|
1242
1348
|
try {
|
|
@@ -1253,21 +1359,43 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1253
1359
|
);
|
|
1254
1360
|
}
|
|
1255
1361
|
}
|
|
1362
|
+
} else if (ctx.actor.channel === "vellum") {
|
|
1363
|
+
// A desktop guardian receives the code inline via `guardianReplyText`.
|
|
1364
|
+
codeDelivered = true;
|
|
1365
|
+
} else if (guardianDmUrl && decidedByExternalUserId) {
|
|
1366
|
+
// A channel guardian whose own channel has no in-band route. Without
|
|
1367
|
+
// this they approve and are never given the code they are meant to pass
|
|
1368
|
+
// on, because the inline reply text only reaches a desktop actor.
|
|
1369
|
+
try {
|
|
1370
|
+
await deliverChannelReply(guardianDmUrl, {
|
|
1371
|
+
chatId: decidedByExternalUserId,
|
|
1372
|
+
text: codeText,
|
|
1373
|
+
assistantId,
|
|
1374
|
+
});
|
|
1375
|
+
codeDelivered = true;
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
log.error(
|
|
1378
|
+
{ err, decidedByExternalUserId },
|
|
1379
|
+
"Failed to deliver verification code to guardian DM",
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1256
1383
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1384
|
+
// The requester. `requesterInBandContext` is the route back along their own
|
|
1385
|
+
// inbound message; `desktopDeliverUrl` is the callback-less route to them.
|
|
1386
|
+
const requesterReplyUrl =
|
|
1387
|
+
requesterInBandContext?.replyCallbackUrl ?? desktopDeliverUrl;
|
|
1261
1388
|
|
|
1389
|
+
if (requesterReplyUrl && requesterChatId) {
|
|
1262
1390
|
if (codeDelivered) {
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
// private path to the requester elsewhere (e.g. group chats).
|
|
1391
|
+
// Where the channel can complete a code handshake in a DM, send the
|
|
1392
|
+
// code straight to the requester. Everywhere else the guardian relays
|
|
1393
|
+
// it and the requester gets a courier notice.
|
|
1267
1394
|
const requesterCodeDelivered =
|
|
1268
|
-
channel
|
|
1269
|
-
|
|
1270
|
-
|
|
1395
|
+
channelCanCompleteCodeHandshakeInDm(channel) &&
|
|
1396
|
+
requesterExternalUserId
|
|
1397
|
+
? await deliverVerificationCodeToRequester({
|
|
1398
|
+
replyCallbackUrl: requesterReplyUrl,
|
|
1271
1399
|
requesterExternalUserId,
|
|
1272
1400
|
verificationCode: session.secret,
|
|
1273
1401
|
assistantId,
|
|
@@ -1279,16 +1407,24 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1279
1407
|
} else {
|
|
1280
1408
|
try {
|
|
1281
1409
|
await deliverChannelReply(
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1410
|
+
requesterReplyUrl,
|
|
1411
|
+
requesterInBandContext
|
|
1412
|
+
? buildRequesterChannelNotice({
|
|
1413
|
+
channel,
|
|
1414
|
+
requesterChatId,
|
|
1415
|
+
requesterExternalUserId,
|
|
1416
|
+
text: APPROVED_COURIER_NOTICE,
|
|
1417
|
+
assistantId,
|
|
1418
|
+
})
|
|
1419
|
+
: {
|
|
1420
|
+
chatId: resolveRequesterDeliveryTarget({
|
|
1421
|
+
channel,
|
|
1422
|
+
requesterChatId,
|
|
1423
|
+
requesterExternalUserId,
|
|
1424
|
+
}),
|
|
1425
|
+
text: APPROVED_COURIER_NOTICE,
|
|
1426
|
+
assistantId,
|
|
1427
|
+
},
|
|
1292
1428
|
);
|
|
1293
1429
|
requesterNotified = true;
|
|
1294
1430
|
} catch (err) {
|
|
@@ -1301,16 +1437,24 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1301
1437
|
} else {
|
|
1302
1438
|
try {
|
|
1303
1439
|
await deliverChannelReply(
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1440
|
+
requesterReplyUrl,
|
|
1441
|
+
requesterInBandContext
|
|
1442
|
+
? buildRequesterChannelNotice({
|
|
1443
|
+
channel,
|
|
1444
|
+
requesterChatId,
|
|
1445
|
+
requesterExternalUserId,
|
|
1446
|
+
text: APPROVED_CODE_UNDELIVERED_NOTICE,
|
|
1447
|
+
assistantId,
|
|
1448
|
+
})
|
|
1449
|
+
: {
|
|
1450
|
+
chatId: resolveRequesterDeliveryTarget({
|
|
1451
|
+
channel,
|
|
1452
|
+
requesterChatId,
|
|
1453
|
+
requesterExternalUserId,
|
|
1454
|
+
}),
|
|
1455
|
+
text: APPROVED_CODE_UNDELIVERED_NOTICE,
|
|
1456
|
+
assistantId,
|
|
1457
|
+
},
|
|
1314
1458
|
);
|
|
1315
1459
|
} catch (err) {
|
|
1316
1460
|
log.error(
|
|
@@ -1319,69 +1463,9 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1319
1463
|
);
|
|
1320
1464
|
}
|
|
1321
1465
|
}
|
|
1466
|
+
}
|
|
1322
1467
|
|
|
1323
|
-
|
|
1324
|
-
if (codeDelivered) {
|
|
1325
|
-
emitVerificationSentSignal(
|
|
1326
|
-
{
|
|
1327
|
-
sourceChannel: channel,
|
|
1328
|
-
requesterExternalUserId,
|
|
1329
|
-
requesterChatId,
|
|
1330
|
-
requesterDisplayName,
|
|
1331
|
-
decidedByDisplayName,
|
|
1332
|
-
verificationSessionId: session.sessionId,
|
|
1333
|
-
},
|
|
1334
|
-
request.sourceConversationId,
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
} else {
|
|
1338
|
-
// Guardian decided off-channel (e.g. desktop). The guardian receives the
|
|
1339
|
-
// verification code inline via `guardianReplyText` regardless of the
|
|
1340
|
-
// requester's channel, so the lifecycle transition is recorded for every
|
|
1341
|
-
// off-channel approve — including channels with no deliverable callback
|
|
1342
|
-
// (e.g. email), where the requester cannot be auto-notified here.
|
|
1343
|
-
if (desktopDeliverUrl && requesterChatId) {
|
|
1344
|
-
// The requester is on a deliverable channel. On Slack, DM the code
|
|
1345
|
-
// directly (parity with the on-channel path); otherwise fall back to
|
|
1346
|
-
// the courier notice.
|
|
1347
|
-
const requesterCodeDelivered =
|
|
1348
|
-
channel === "slack" && requesterExternalUserId
|
|
1349
|
-
? await deliverVerificationCodeToSlackRequester({
|
|
1350
|
-
replyCallbackUrl: desktopDeliverUrl,
|
|
1351
|
-
requesterExternalUserId,
|
|
1352
|
-
verificationCode: session.secret,
|
|
1353
|
-
assistantId,
|
|
1354
|
-
})
|
|
1355
|
-
: false;
|
|
1356
|
-
|
|
1357
|
-
if (requesterCodeDelivered) {
|
|
1358
|
-
requesterNotified = true;
|
|
1359
|
-
} else {
|
|
1360
|
-
// For Slack, route to DM via requesterExternalUserId (user ID)
|
|
1361
|
-
// instead of requesterChatId (channel ID) to avoid posting in public
|
|
1362
|
-
// channels.
|
|
1363
|
-
const targetChatId =
|
|
1364
|
-
channel === "slack" && requesterExternalUserId
|
|
1365
|
-
? requesterExternalUserId
|
|
1366
|
-
: requesterChatId;
|
|
1367
|
-
try {
|
|
1368
|
-
await deliverChannelReply(desktopDeliverUrl, {
|
|
1369
|
-
chatId: targetChatId,
|
|
1370
|
-
text:
|
|
1371
|
-
"Your access request has been approved! " +
|
|
1372
|
-
"Please enter the 6-digit verification code you receive from the guardian.",
|
|
1373
|
-
assistantId,
|
|
1374
|
-
});
|
|
1375
|
-
requesterNotified = true;
|
|
1376
|
-
} catch (err) {
|
|
1377
|
-
log.error(
|
|
1378
|
-
{ err, requesterChatId },
|
|
1379
|
-
"Failed to notify requester of access request approval (desktop decision path)",
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1468
|
+
if (codeDelivered) {
|
|
1385
1469
|
// Record the verification_sent lifecycle transition for every off-channel
|
|
1386
1470
|
// approve. The session is minted and the guardian has the code via
|
|
1387
1471
|
// `guardianReplyText` regardless of whether (or how) the requester was
|
|
@@ -380,6 +380,56 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
|
|
|
380
380
|
voiceSessionTurn: true,
|
|
381
381
|
});
|
|
382
382
|
});
|
|
383
|
+
|
|
384
|
+
test("a live-voice turn persists its session attribution under metadata.client", async () => {
|
|
385
|
+
const fake = makeFakeConversation({ processing: false });
|
|
386
|
+
fakeConversation = fake.conversation;
|
|
387
|
+
|
|
388
|
+
await startVoiceTurn({
|
|
389
|
+
...makeTurnOptions(),
|
|
390
|
+
voiceTelemetry: { sessionId: "session-123", client: "ios" },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// `turn-events-store` projects `$.client` onto `TurnTelemetryEvent.client`,
|
|
394
|
+
// so this bag is what makes a voice turn countable per client and joinable
|
|
395
|
+
// to its session's funnel rows. The platform goes in `os`, the same key
|
|
396
|
+
// the HTTP send path fills, so voice turns sit in the column existing turn
|
|
397
|
+
// analytics already read.
|
|
398
|
+
expect(fake.lastPersistOpts()?.metadata).toEqual({
|
|
399
|
+
voiceSessionTurn: true,
|
|
400
|
+
client: {
|
|
401
|
+
voice: true,
|
|
402
|
+
voice_session_id: "session-123",
|
|
403
|
+
os: "ios",
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("omits the client key when the session reported no originating client", async () => {
|
|
409
|
+
const fake = makeFakeConversation({ processing: false });
|
|
410
|
+
fakeConversation = fake.conversation;
|
|
411
|
+
|
|
412
|
+
await startVoiceTurn({
|
|
413
|
+
...makeTurnOptions(),
|
|
414
|
+
voiceTelemetry: { sessionId: "session-123" },
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(fake.lastPersistOpts()?.metadata).toEqual({
|
|
418
|
+
voiceSessionTurn: true,
|
|
419
|
+
client: { voice: true, voice_session_id: "session-123" },
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("a phone turn carries no client bag", async () => {
|
|
424
|
+
const fake = makeFakeConversation({ processing: false });
|
|
425
|
+
fakeConversation = fake.conversation;
|
|
426
|
+
|
|
427
|
+
await startVoiceTurn(makeTurnOptions());
|
|
428
|
+
|
|
429
|
+
// Only live-voice sessions pass `voiceTelemetry`; a phone call has no
|
|
430
|
+
// live-voice session id to attribute a turn to.
|
|
431
|
+
expect(fake.lastPersistOpts()?.metadata).not.toHaveProperty("client");
|
|
432
|
+
});
|
|
383
433
|
});
|
|
384
434
|
|
|
385
435
|
describe("startVoiceTurn hiddenSyntheticPrompt", () => {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* - Media stream `stop` event / WebSocket close -> finalize call.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
+
import { GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE } from "@vellumai/service-contracts/ingress";
|
|
41
42
|
import type { ServerWebSocket } from "bun";
|
|
42
43
|
|
|
43
44
|
import { revokeScopedApprovalGrantsForContext } from "../approvals/scoped-approval-grants.js";
|
|
@@ -344,9 +345,16 @@ export class MediaStreamCallSession {
|
|
|
344
345
|
);
|
|
345
346
|
}
|
|
346
347
|
} else {
|
|
348
|
+
// The gateway's close reason does not survive the relay (Bun's
|
|
349
|
+
// WebSocket client drops it), so tunnel loss is decoded from the
|
|
350
|
+
// dedicated close code.
|
|
347
351
|
const detail =
|
|
348
352
|
reason ||
|
|
349
|
-
(code
|
|
353
|
+
(code === GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE
|
|
354
|
+
? "public ingress tunnel disconnected"
|
|
355
|
+
: code
|
|
356
|
+
? `media_stream_closed_${code}`
|
|
357
|
+
: "media_stream_closed_abnormal");
|
|
350
358
|
updateCallSession(this.callSessionId, {
|
|
351
359
|
status: "failed",
|
|
352
360
|
endedAt: Date.now(),
|