@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for what a failed emit leaves behind in the events store.
|
|
3
|
+
*
|
|
4
|
+
* `createEvent` claims the producer's dedupe key as its first pipeline step.
|
|
5
|
+
* A failure after that point releases the claim, so the retry is not
|
|
6
|
+
* short-circuited as "deduplicated at event store level" by an emit that
|
|
7
|
+
* never reached a verdict. A completed emit keeps its claim so real
|
|
8
|
+
* duplicates still collapse.
|
|
9
|
+
*
|
|
10
|
+
* A failure that already reached a channel keeps its claim too: the retry
|
|
11
|
+
* would broadcast under a fresh decision id, and delivery dedupe is keyed to
|
|
12
|
+
* the decision, so every channel that already went out would go out twice.
|
|
13
|
+
*
|
|
14
|
+
* The events store, its DB, and the deterministic checks are the real ones;
|
|
15
|
+
* only the decision engine, the dispatch step, and the adapter graph around
|
|
16
|
+
* them are stubbed.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
const dispatchDecisionMock = mock();
|
|
22
|
+
|
|
23
|
+
mock.module("../../channels/config.js", () => ({
|
|
24
|
+
getDeliverableChannels: () => ["vellum"],
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
28
|
+
getGuardianDelivery: async () => [],
|
|
29
|
+
guardianForChannel: () => undefined,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
mock.module("../../platform/client.js", () => ({
|
|
33
|
+
VellumPlatformClient: class {
|
|
34
|
+
static async create(): Promise<null> {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
isPlatformClientConfigured: async () => false,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
mock.module("../broadcaster.js", () => ({
|
|
42
|
+
NotificationBroadcaster: class {
|
|
43
|
+
constructor(_adapters: unknown[]) {}
|
|
44
|
+
setOnConversationCreated(_fn: unknown) {}
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const DEDUPE_KEY = "schedule-definition-error:plugin:news/digest:2026-01-01";
|
|
49
|
+
|
|
50
|
+
mock.module("../decision-engine.js", () => ({
|
|
51
|
+
evaluateSignal: async () => ({
|
|
52
|
+
shouldNotify: true,
|
|
53
|
+
selectedChannels: ["vellum"],
|
|
54
|
+
reasoningSummary: "deliver",
|
|
55
|
+
renderedCopy: {
|
|
56
|
+
vellum: { title: "Plugin schedule error", body: "digest failed to load" },
|
|
57
|
+
},
|
|
58
|
+
dedupeKey: DEDUPE_KEY,
|
|
59
|
+
confidence: 0.9,
|
|
60
|
+
fallbackUsed: false,
|
|
61
|
+
}),
|
|
62
|
+
enforceRoutingIntent: (decision: unknown) => decision,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
mock.module("../decisions-store.js", () => ({
|
|
66
|
+
updateDecision: () => {},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
mock.module("../home-feed-side-effect.js", () => ({
|
|
70
|
+
writeHomeFeedItemForSignal: async () => {},
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
mock.module("../runtime-dispatch.js", () => ({
|
|
74
|
+
dispatchDecision: (...args: unknown[]) => dispatchDecisionMock(...args),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
import { getDb } from "../../persistence/db-connection.js";
|
|
78
|
+
import { initializeDb } from "../../persistence/db-init.js";
|
|
79
|
+
import { notificationEvents } from "../../persistence/schema/index.js";
|
|
80
|
+
import type { EmitSignalParams } from "../emit-signal.js";
|
|
81
|
+
import type { NotificationDeliveryResult } from "../types.js";
|
|
82
|
+
|
|
83
|
+
await initializeDb();
|
|
84
|
+
|
|
85
|
+
const { emitNotificationSignal } = await import("../emit-signal.js");
|
|
86
|
+
|
|
87
|
+
function emit() {
|
|
88
|
+
const params: EmitSignalParams<string> = {
|
|
89
|
+
sourceEventName: "schedule.definition_error",
|
|
90
|
+
sourceChannel: "scheduler",
|
|
91
|
+
sourceContextId: "plugin:news/digest",
|
|
92
|
+
dedupeKey: DEDUPE_KEY,
|
|
93
|
+
contextPayload: { pluginName: "news", scheduleName: "digest" },
|
|
94
|
+
attentionHints: {
|
|
95
|
+
requiresAction: false,
|
|
96
|
+
urgency: "medium",
|
|
97
|
+
isAsyncBackground: true,
|
|
98
|
+
visibleInSourceNow: false,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
return emitNotificationSignal(params);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stub one dispatch as a broadcast that settles the given channels and then
|
|
106
|
+
* throws on a later channel's prep. What already happened leaves the
|
|
107
|
+
* broadcast through the results sink; the return value dies with the throw.
|
|
108
|
+
*/
|
|
109
|
+
function throwAfterChannels(...settled: NotificationDeliveryResult[]): void {
|
|
110
|
+
dispatchDecisionMock.mockImplementationOnce(
|
|
111
|
+
(
|
|
112
|
+
_signal: unknown,
|
|
113
|
+
_decision: unknown,
|
|
114
|
+
_broadcaster: unknown,
|
|
115
|
+
options: { resultsSink?: NotificationDeliveryResult[] },
|
|
116
|
+
) => {
|
|
117
|
+
options.resultsSink?.push(...settled);
|
|
118
|
+
throw new Error("conversation pairing failed for a later channel");
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
getDb().delete(notificationEvents).run();
|
|
125
|
+
dispatchDecisionMock.mockReset();
|
|
126
|
+
dispatchDecisionMock.mockResolvedValue({
|
|
127
|
+
dispatched: true,
|
|
128
|
+
reason: "Dispatched to 1/1 channels",
|
|
129
|
+
deliveryResults: [],
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("emitNotificationSignal dedupe claim on failure", () => {
|
|
134
|
+
test("a retry after a mid-pipeline failure dispatches", async () => {
|
|
135
|
+
dispatchDecisionMock.mockRejectedValueOnce(new Error("transient outage"));
|
|
136
|
+
|
|
137
|
+
const failed = await emit();
|
|
138
|
+
expect(failed.pipelineFailed).toBe(true);
|
|
139
|
+
expect(failed.dispatched).toBe(false);
|
|
140
|
+
|
|
141
|
+
const retried = await emit();
|
|
142
|
+
expect(retried.pipelineFailed).toBe(false);
|
|
143
|
+
expect(retried.deduplicated).toBe(false);
|
|
144
|
+
expect(retried.dispatched).toBe(true);
|
|
145
|
+
|
|
146
|
+
// The failed attempt keeps its row for the audit trail; only its claim on
|
|
147
|
+
// the dedupe key is released.
|
|
148
|
+
const rows = getDb().select().from(notificationEvents).all();
|
|
149
|
+
expect(rows).toHaveLength(2);
|
|
150
|
+
expect(rows.find((r) => r.id === failed.signalId)?.dedupeKey).toBeNull();
|
|
151
|
+
expect(rows.find((r) => r.id === retried.signalId)?.dedupeKey).toBe(
|
|
152
|
+
DEDUPE_KEY,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// "sent" is a completed channel delivery; "pending" is the platform push
|
|
157
|
+
// that passed its outcome deadline and is still in flight. Both are
|
|
158
|
+
// side effects a retry would repeat.
|
|
159
|
+
for (const status of ["sent", "pending"] as const) {
|
|
160
|
+
test(`a failure after a ${status} channel keeps its dedupe key`, async () => {
|
|
161
|
+
throwAfterChannels({
|
|
162
|
+
channel: "vellum",
|
|
163
|
+
destination: "vellum",
|
|
164
|
+
status,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const partial = await emit();
|
|
168
|
+
expect(partial.pipelineFailed).toBe(true);
|
|
169
|
+
|
|
170
|
+
const retried = await emit();
|
|
171
|
+
expect(retried.deduplicated).toBe(true);
|
|
172
|
+
expect(retried.dispatched).toBe(false);
|
|
173
|
+
expect(dispatchDecisionMock).toHaveBeenCalledTimes(1);
|
|
174
|
+
|
|
175
|
+
const rows = getDb().select().from(notificationEvents).all();
|
|
176
|
+
expect(rows).toHaveLength(1);
|
|
177
|
+
expect(rows[0]!.dedupeKey).toBe(DEDUPE_KEY);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
test("a failure after only skipped and failed channels releases its key", async () => {
|
|
182
|
+
throwAfterChannels(
|
|
183
|
+
{ channel: "slack", destination: "slack", status: "skipped" },
|
|
184
|
+
{ channel: "telegram", destination: "telegram", status: "failed" },
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const failed = await emit();
|
|
188
|
+
expect(failed.pipelineFailed).toBe(true);
|
|
189
|
+
|
|
190
|
+
const retried = await emit();
|
|
191
|
+
expect(retried.deduplicated).toBe(false);
|
|
192
|
+
expect(retried.dispatched).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("a completed emit keeps its dedupe key, so a repeat is deduplicated", async () => {
|
|
196
|
+
const first = await emit();
|
|
197
|
+
expect(first.dispatched).toBe(true);
|
|
198
|
+
|
|
199
|
+
const second = await emit();
|
|
200
|
+
expect(second.deduplicated).toBe(true);
|
|
201
|
+
expect(second.dispatched).toBe(false);
|
|
202
|
+
expect(second.pipelineFailed).toBe(false);
|
|
203
|
+
expect(dispatchDecisionMock).toHaveBeenCalledTimes(1);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
describeMedia,
|
|
5
|
+
mediaEmbeds,
|
|
6
|
+
stripMarkdownForPreview,
|
|
7
|
+
} from "../notification-utils.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The producer collapses whitespace after flattening, so most assertions read
|
|
11
|
+
* the flattened text the way a lock screen would.
|
|
12
|
+
*/
|
|
13
|
+
function preview(value: string): string {
|
|
14
|
+
return stripMarkdownForPreview(value).replace(/\s+/g, " ").trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("stripMarkdownForPreview", () => {
|
|
18
|
+
describe("media embeds", () => {
|
|
19
|
+
test("drops an embed whole, alt text included", () => {
|
|
20
|
+
expect(
|
|
21
|
+
preview(""),
|
|
22
|
+
).toBe("");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("reduces an embed-only reply to empty so the fallback is reachable", () => {
|
|
26
|
+
const reply = [
|
|
27
|
+
"",
|
|
28
|
+
"",
|
|
29
|
+
].join(" ");
|
|
30
|
+
expect(preview(reply)).toBe("");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("keeps the surrounding prose", () => {
|
|
34
|
+
expect(
|
|
35
|
+
preview("Here is the scene: "),
|
|
36
|
+
).toBe("Here is the scene:");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("leaves no stray bang behind", () => {
|
|
40
|
+
expect(preview("")).not.toContain("!");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// An embed needs no surrounding whitespace, so dropping it outright would
|
|
44
|
+
// fuse the prose on either side into one word.
|
|
45
|
+
test("separates the prose an inline embed sat between", () => {
|
|
46
|
+
expect(preview("beforeafter")).toBe(
|
|
47
|
+
"before after",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("unwraps a plain link to its text", () => {
|
|
52
|
+
expect(preview("See [report.pdf](vellum://workspace/report.pdf)")).toBe(
|
|
53
|
+
"See report.pdf",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Fenced content is code, not prose. Flattening it would rewrite the very
|
|
59
|
+
// thing being previewed.
|
|
60
|
+
describe("fenced code", () => {
|
|
61
|
+
test("drops fence lines and keeps the fenced code", () => {
|
|
62
|
+
expect(preview("Fixed it:\n```ts\nconst a = 1;\n```")).toBe(
|
|
63
|
+
"Fixed it: const a = 1;",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("leaves a comment inside a fence with its hash", () => {
|
|
68
|
+
expect(preview("Run:\n```sh\n# build first\nmake\n```")).toBe(
|
|
69
|
+
"Run: # build first make",
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("leaves a rule and a piped expression inside a fence alone", () => {
|
|
74
|
+
expect(preview("```sh\n---\n| a | b |\n```")).toBe("--- | a | b |");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("still flattens prose after the closing fence", () => {
|
|
78
|
+
expect(preview("```\ncode\n```\n## After")).toBe("code After");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("block markers", () => {
|
|
83
|
+
test("drops heading markers", () => {
|
|
84
|
+
expect(preview("## Summary\nAll green.")).toBe("Summary All green.");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("drops blockquote markers", () => {
|
|
88
|
+
expect(preview("> quoted line")).toBe("quoted line");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("drops unordered and ordered list markers", () => {
|
|
92
|
+
expect(preview("- one\n- two")).toBe("one two");
|
|
93
|
+
expect(preview("1. first\n2) second")).toBe("first second");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("drops horizontal rules", () => {
|
|
97
|
+
expect(preview("before\n---\nafter")).toBe("before after");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("tables", () => {
|
|
102
|
+
test("unwraps cells and drops the delimiter row", () => {
|
|
103
|
+
const table = "| Env | Key |\n|---|---|\n| dev | 4Y4L |";
|
|
104
|
+
expect(preview(table)).toBe("Env Key dev 4Y4L");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("leaves a pipe in prose alone", () => {
|
|
108
|
+
expect(preview("run ls | grep foo")).toBe("run ls | grep foo");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Cases a hand-rolled marker scanner gets wrong and the parser gets right.
|
|
113
|
+
describe("syntax that needs a real parser", () => {
|
|
114
|
+
test("reads a destination whose URL has balanced parentheses", () => {
|
|
115
|
+
expect(preview(".png)")).toBe("");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("closes a fence only on its own delimiter", () => {
|
|
119
|
+
expect(preview("```js\n~~~\nconst a = 1;\n~~~\n```")).toBe(
|
|
120
|
+
"~~~ const a = 1; ~~~",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("flattens a GFM table that omits its outer pipes", () => {
|
|
125
|
+
expect(preview("Env | Key\n--- | ---\ndev | 4Y4L")).toBe(
|
|
126
|
+
"Env Key dev 4Y4L",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("keeps embed syntax quoted inside a code span", () => {
|
|
131
|
+
expect(preview("Use `` here")).toBe(
|
|
132
|
+
"Use  here",
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("keeps escaped embed syntax", () => {
|
|
137
|
+
expect(preview("\\")).toContain("alt");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// The in-app renderer registers no `rehype-raw`, so raw HTML displays as
|
|
142
|
+
// nothing there. The preview matches rather than showing markup the reader
|
|
143
|
+
// never saw.
|
|
144
|
+
describe("raw HTML", () => {
|
|
145
|
+
test("keeps the text between a tag pair and drops the tags", () => {
|
|
146
|
+
expect(preview("<strong>done</strong>")).toBe("done");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("drops an HTML comment rather than leaking its contents", () => {
|
|
150
|
+
expect(preview("<!-- internal note -->")).toBe("");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("keeps the prose around a dropped tag", () => {
|
|
154
|
+
expect(preview("All <em>green</em> now")).toBe("All green now");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("what it must not touch", () => {
|
|
159
|
+
test("passes plain prose through unchanged", () => {
|
|
160
|
+
const prose = "Done. The deploy finished and the tests are green.";
|
|
161
|
+
expect(stripMarkdownForPreview(prose)).toBe(prose);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("preserves line structure for multi-line callers", () => {
|
|
165
|
+
expect(stripMarkdownForPreview("first\n\nsecond")).toContain("\n");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("returns empty for empty input", () => {
|
|
169
|
+
expect(stripMarkdownForPreview("")).toBe("");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("mediaEmbeds", () => {
|
|
175
|
+
const alts = (value: string): string[] =>
|
|
176
|
+
mediaEmbeds(value).map((embed) => embed.alt);
|
|
177
|
+
|
|
178
|
+
test("recovers the alts a flattened reply dropped", () => {
|
|
179
|
+
expect(
|
|
180
|
+
alts(
|
|
181
|
+
" ",
|
|
182
|
+
),
|
|
183
|
+
).toEqual(["the Q3 chart", "scene"]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("keeps an empty alt as a placeholder rather than dropping it", () => {
|
|
187
|
+
expect(alts("")).toEqual([""]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("ignores plain links", () => {
|
|
191
|
+
expect(alts("[report.pdf](vellum://workspace/r.pdf)")).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("returns nothing for prose", () => {
|
|
195
|
+
expect(alts("Done, all green.")).toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// The parser resolves an image description to plain text, so a formatted alt
|
|
199
|
+
// cannot reintroduce the markdown this module exists to remove.
|
|
200
|
+
test("returns a formatted alt already flattened", () => {
|
|
201
|
+
expect(alts("")).toEqual(["chart"]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("ignores embed syntax quoted inside a code span", () => {
|
|
205
|
+
expect(alts("Use `` here")).toEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Only untracked embeds need naming from their alt: a caller holding
|
|
209
|
+
// attachment metadata has already counted the tracked ones.
|
|
210
|
+
describe("tracking", () => {
|
|
211
|
+
test("marks workspace and host destinations tracked", () => {
|
|
212
|
+
expect(
|
|
213
|
+
mediaEmbeds(
|
|
214
|
+
" ",
|
|
215
|
+
).map((embed) => embed.tracked),
|
|
216
|
+
).toEqual([true, true]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("marks a remote destination untracked", () => {
|
|
220
|
+
expect(
|
|
221
|
+
mediaEmbeds("")[0]?.tracked,
|
|
222
|
+
).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("marks a reference embed untracked", () => {
|
|
226
|
+
expect(
|
|
227
|
+
mediaEmbeds("![a][ref]\n\n[ref]: vellum://workspace/a.png")[0]?.tracked,
|
|
228
|
+
).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("describeMedia", () => {
|
|
234
|
+
test("names a single label", () => {
|
|
235
|
+
expect(describeMedia(["cut.mp4"])).toBe("Sent cut.mp4");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("counts several rather than listing them", () => {
|
|
239
|
+
expect(describeMedia(["a.png", "b.png"])).toBe("Sent 2 attachments");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("falls back to generic copy for a label that sanitizes away", () => {
|
|
243
|
+
expect(describeMedia(["\0\x07"])).toBe("Sent an attachment");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("returns empty for no labels, leaving the fallback to the caller", () => {
|
|
247
|
+
expect(describeMedia([])).toBe("");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
isRetryableStatus,
|
|
19
19
|
sleep,
|
|
20
20
|
} from "../../util/retry.js";
|
|
21
|
+
import {
|
|
22
|
+
describeMedia,
|
|
23
|
+
mediaEmbeds,
|
|
24
|
+
stripMarkdownForPreview,
|
|
25
|
+
} from "../notification-utils.js";
|
|
21
26
|
import type {
|
|
22
27
|
ChannelAdapter,
|
|
23
28
|
ChannelDeliveryObserver,
|
|
@@ -38,6 +43,30 @@ const RETRY_DELAYS_MS = [250, 1_000, 4_000] as const;
|
|
|
38
43
|
// dispatch's outcome -- a hung platform must fail the attempt, not stall it.
|
|
39
44
|
const ATTEMPT_TIMEOUT_MS = 5_000;
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Flatten one alert field for a plain-text surface, recovering copy when
|
|
48
|
+
* flattening leaves nothing behind.
|
|
49
|
+
*
|
|
50
|
+
* An APNs/FCM alert renders no markdown, so markers arrive as literal
|
|
51
|
+
* punctuation. Flattening here covers every event routed to this channel
|
|
52
|
+
* without altering what other channels receive, some of which render markdown
|
|
53
|
+
* deliberately. Newlines survive: iOS renders them, and guardian copy carries a
|
|
54
|
+
* deliberate paragraph break.
|
|
55
|
+
*
|
|
56
|
+
* The recovery is load-bearing rather than cosmetic. Copy composed entirely of
|
|
57
|
+
* media embeds, which the pass-through path copies into both the title and the
|
|
58
|
+
* body, flattens to nothing, and the pipeline's empty-copy guards all run
|
|
59
|
+
* upstream of this adapter. The platform's serializer rejects a blank title,
|
|
60
|
+
* so an unrecovered field costs the whole notification, not just its wording.
|
|
61
|
+
*/
|
|
62
|
+
function flattenAlertField(value: string): string {
|
|
63
|
+
const flattened = stripMarkdownForPreview(value);
|
|
64
|
+
if (flattened.trim().length > 0) {
|
|
65
|
+
return flattened;
|
|
66
|
+
}
|
|
67
|
+
return describeMedia(mediaEmbeds(value).map((embed) => embed.alt)) || value;
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
/** Whether a fetch error is the per-attempt abort timeout firing. */
|
|
42
71
|
function isAttemptTimeout(err: unknown): boolean {
|
|
43
72
|
const name = (err as { name?: unknown } | null)?.name;
|
|
@@ -108,8 +137,8 @@ export class PlatformPushAdapter implements ChannelAdapter {
|
|
|
108
137
|
const body: DispatchBody = {
|
|
109
138
|
delivery_id: payload.correlationId ?? payload.deliveryId,
|
|
110
139
|
source_event_name: payload.sourceEventName,
|
|
111
|
-
title: payload.copy.title,
|
|
112
|
-
body: payload.copy.body,
|
|
140
|
+
title: flattenAlertField(payload.copy.title),
|
|
141
|
+
body: flattenAlertField(payload.copy.body),
|
|
113
142
|
deep_link_metadata: payload.deepLinkTarget,
|
|
114
143
|
context_payload: payload.contextPayload,
|
|
115
144
|
target_guardian_principal_id: targetGuardianPrincipalId,
|
|
@@ -23,20 +23,28 @@ import {
|
|
|
23
23
|
parseMessageMetadata,
|
|
24
24
|
} from "../persistence/conversation-crud.js";
|
|
25
25
|
import {
|
|
26
|
+
isMacOriginatedUserMessage,
|
|
26
27
|
isReplyPushIneligibleUserMessage,
|
|
27
28
|
resolveConversationKind,
|
|
28
29
|
} from "../persistence/conversation-types.js";
|
|
29
30
|
import { stringifyMessageContent } from "../persistence/message-content.js";
|
|
31
|
+
import { isDesktopAttended } from "../runtime/desktop-presence.js";
|
|
30
32
|
import { safeParseRecord } from "../util/json.js";
|
|
31
33
|
import { emitNotificationSignal } from "./emit-signal.js";
|
|
32
34
|
import {
|
|
35
|
+
describeMedia,
|
|
36
|
+
mediaEmbeds,
|
|
33
37
|
sanitizeMessagePreview,
|
|
34
38
|
sanitizeNotificationTitle,
|
|
39
|
+
stripMarkdownForPreview,
|
|
35
40
|
} from "./notification-utils.js";
|
|
36
41
|
|
|
37
42
|
/** Kill switch for this producer, on by default. */
|
|
38
43
|
const ASSISTANT_REPLY_PUSH_FLAG = "assistant-reply-push" as const;
|
|
39
44
|
|
|
45
|
+
/** Gates the desktop-attended suppression below, on by default. */
|
|
46
|
+
const DESKTOP_PRESENCE_FLAG = "desktop-presence-suppression" as const;
|
|
47
|
+
|
|
40
48
|
/**
|
|
41
49
|
* Collapse whitespace runs ahead of the sanitizers' truncation: blank lines and
|
|
42
50
|
* list indentation would otherwise eat into the copy's length budget.
|
|
@@ -46,31 +54,42 @@ function collapseWhitespace(value: string): string {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/**
|
|
49
|
-
* Body
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
57
|
+
* Body naming every piece of media a reply produced, for a reply whose text
|
|
58
|
+
* flattened away.
|
|
59
|
+
*
|
|
60
|
+
* Two sources, counted together so a reply mixing them is not undercounted.
|
|
61
|
+
* Attachment rows cover generated files, `<vellum-attachment />` directives,
|
|
62
|
+
* and `vellum://` embeds alike; they are linked to the assistant row by
|
|
63
|
+
* `resolveAssistantAttachments` before the turn's terminal SSE and exposed
|
|
64
|
+
* separately from its content blocks. Remote embeds leave no row, so their alt
|
|
65
|
+
* text is the only label they have.
|
|
55
66
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
67
|
+
* Tracked embeds are dropped from the second source rather than added twice:
|
|
68
|
+
* a `vellum://` embed is already one of the attachment rows.
|
|
69
|
+
*
|
|
70
|
+
* Reads attachment metadata only (no base64), and the caller invokes this only
|
|
71
|
+
* once the text preview has come up empty.
|
|
58
72
|
*/
|
|
59
|
-
function
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
return "";
|
|
63
|
-
}
|
|
64
|
-
if (attachments.length > 1) {
|
|
65
|
-
return `Sent ${attachments.length} attachments`;
|
|
66
|
-
}
|
|
67
|
-
// Filenames are model- and tool-authored, so one is sanitized before it can
|
|
68
|
-
// reach the lock screen. A filename that sanitizes away leaves generic copy
|
|
69
|
-
// rather than a dangling "Sent ".
|
|
70
|
-
const filename = sanitizeMessagePreview(
|
|
71
|
-
collapseWhitespace(attachments[0].originalFilename),
|
|
73
|
+
function describeReplyMedia(text: string, assistantMessageId: string): string {
|
|
74
|
+
const filenames = getAttachmentMetadataForMessage(assistantMessageId).map(
|
|
75
|
+
(attachment) => attachment.originalFilename,
|
|
72
76
|
);
|
|
73
|
-
|
|
77
|
+
const embeds = mediaEmbeds(text);
|
|
78
|
+
const tracked = embeds.filter((embed) => embed.tracked);
|
|
79
|
+
|
|
80
|
+
// Resolution is allowed to fail: `resolveAssistantAttachments` skips a file
|
|
81
|
+
// that is missing, oversized, unreadable, or denied at the host-read
|
|
82
|
+
// approval, leaving a tracked embed with no row to stand for it. Rows at
|
|
83
|
+
// least matching the tracked embeds means each one resolved (any surplus
|
|
84
|
+
// being generated files); a shortfall is that many embeds falling back to
|
|
85
|
+
// their own alt rather than vanishing from the count.
|
|
86
|
+
const unresolved = Math.max(0, tracked.length - filenames.length);
|
|
87
|
+
|
|
88
|
+
return describeMedia([
|
|
89
|
+
...filenames,
|
|
90
|
+
...tracked.slice(tracked.length - unresolved).map((embed) => embed.alt),
|
|
91
|
+
...embeds.filter((embed) => !embed.tracked).map((embed) => embed.alt),
|
|
92
|
+
]);
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
/**
|
|
@@ -93,6 +112,23 @@ function readSuppressionMarkers(
|
|
|
93
112
|
return metadataJson ? safeParseRecord(metadataJson) : undefined;
|
|
94
113
|
}
|
|
95
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Desktop attendance, kept fail-open here: a presence read that fails has to
|
|
117
|
+
* send the push, not reach the producer's catch and silence it.
|
|
118
|
+
*
|
|
119
|
+
* No `actorPrincipalId`: the platform delivers this push to the assistant
|
|
120
|
+
* owner's device tokens, and a pod has exactly one owner, so any attended
|
|
121
|
+
* macOS client is that owner's.
|
|
122
|
+
*/
|
|
123
|
+
function readDesktopAttended(rlog: pino.Logger): boolean {
|
|
124
|
+
try {
|
|
125
|
+
return isDesktopAttended();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
rlog.warn({ err }, "Desktop presence read failed; treating as unattended");
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
96
132
|
export async function emitAssistantReplyNotification(params: {
|
|
97
133
|
conversationId: string;
|
|
98
134
|
assistantMessageId: string;
|
|
@@ -161,14 +197,16 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
161
197
|
return;
|
|
162
198
|
}
|
|
163
199
|
|
|
164
|
-
// A reply whose output is entirely
|
|
165
|
-
//
|
|
166
|
-
//
|
|
200
|
+
// A reply whose output is entirely media has no text to preview, so name
|
|
201
|
+
// the media rather than suppressing a real reply. Markdown is flattened
|
|
202
|
+
// first: a lock screen renders none of it, and an embed-only reply has to
|
|
203
|
+
// reduce to empty for the fallback to be reachable. A reply with neither
|
|
204
|
+
// text nor media stays silent.
|
|
205
|
+
const text = stringifyMessageContent(assistantRow.content);
|
|
167
206
|
const preview =
|
|
168
207
|
sanitizeMessagePreview(
|
|
169
|
-
collapseWhitespace(
|
|
170
|
-
) ||
|
|
171
|
-
sanitizeMessagePreview(describeAttachmentOnlyReply(assistantMessageId));
|
|
208
|
+
collapseWhitespace(stripMarkdownForPreview(text)),
|
|
209
|
+
) || sanitizeMessagePreview(describeReplyMedia(text, assistantMessageId));
|
|
172
210
|
if (!preview) {
|
|
173
211
|
return;
|
|
174
212
|
}
|
|
@@ -182,6 +220,15 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
182
220
|
collapseWhitespace(conversation.title ?? ""),
|
|
183
221
|
);
|
|
184
222
|
|
|
223
|
+
// Read as close to the emit as possible: nothing short-circuits on it.
|
|
224
|
+
// Presence only speaks for a turn the Mac itself opened, on that row's own
|
|
225
|
+
// OS evidence. A turn sent from the phone still needs its push while the
|
|
226
|
+
// Mac sits idle within the desktop client's attendance window.
|
|
227
|
+
const desktopAttended =
|
|
228
|
+
isAssistantFeatureFlagEnabled(DESKTOP_PRESENCE_FLAG) &&
|
|
229
|
+
isMacOriginatedUserMessage(initiatingMetadata) &&
|
|
230
|
+
readDesktopAttended(rlog);
|
|
231
|
+
|
|
185
232
|
await emitNotificationSignal({
|
|
186
233
|
sourceEventName: "chat.assistant_reply",
|
|
187
234
|
sourceChannel: "vellum",
|
|
@@ -196,10 +243,10 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
196
243
|
// opting into v2.
|
|
197
244
|
urgency: "medium",
|
|
198
245
|
isAsyncBackground: false,
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
visibleInSourceNow:
|
|
246
|
+
// Read weakly, as "at the machine this landed on": the attended Mac
|
|
247
|
+
// that opened the turn renders the reply in-app, and its Dock unread
|
|
248
|
+
// badge carries it while the window is hidden.
|
|
249
|
+
visibleInSourceNow: desktopAttended,
|
|
203
250
|
},
|
|
204
251
|
contextPayload: {
|
|
205
252
|
...(requestedTitle ? { requestedTitle } : {}),
|