@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
|
@@ -77,6 +77,23 @@ mock.module("../../persistence/attachments-store.js", () => ({
|
|
|
77
77
|
},
|
|
78
78
|
}));
|
|
79
79
|
|
|
80
|
+
// Defaults to unattended, so every other case in this file exercises the
|
|
81
|
+
// unsuppressed path.
|
|
82
|
+
let desktopAttended = false;
|
|
83
|
+
let desktopPresenceShouldThrow = false;
|
|
84
|
+
const desktopPresenceArgs: unknown[][] = [];
|
|
85
|
+
const realDesktopPresence = await import("../../runtime/desktop-presence.js");
|
|
86
|
+
mock.module("../../runtime/desktop-presence.js", () => ({
|
|
87
|
+
...realDesktopPresence,
|
|
88
|
+
isDesktopAttended: (...args: unknown[]) => {
|
|
89
|
+
desktopPresenceArgs.push(args);
|
|
90
|
+
if (desktopPresenceShouldThrow) {
|
|
91
|
+
throw new Error("simulated presence read failure");
|
|
92
|
+
}
|
|
93
|
+
return desktopAttended;
|
|
94
|
+
},
|
|
95
|
+
}));
|
|
96
|
+
|
|
80
97
|
const realAttentionStore =
|
|
81
98
|
await import("../../persistence/conversation-attention-store.js");
|
|
82
99
|
mock.module("../../persistence/conversation-attention-store.js", () => ({
|
|
@@ -118,6 +135,7 @@ function makeConversation(
|
|
|
118
135
|
originInterface: null,
|
|
119
136
|
forkParentConversationId: null,
|
|
120
137
|
forkParentMessageId: null,
|
|
138
|
+
forkStrategy: null,
|
|
121
139
|
isAutoTitle: 0,
|
|
122
140
|
scheduleJobId: null,
|
|
123
141
|
lastMessageAt: null,
|
|
@@ -147,6 +165,24 @@ function makeMessage(overrides: Partial<MessageRow> = {}): MessageRow {
|
|
|
147
165
|
};
|
|
148
166
|
}
|
|
149
167
|
|
|
168
|
+
/**
|
|
169
|
+
* A turn opened from the macOS app. The `client` bag's `os` entry is the only
|
|
170
|
+
* per-platform attribution: the interface stamp is "web" for the macOS app,
|
|
171
|
+
* the iOS app, and a desktop browser alike. `clientOsFromRequest` says the
|
|
172
|
+
* send itself reported that OS, which is what separates it from a row that
|
|
173
|
+
* inherited the conversation's live client state.
|
|
174
|
+
*/
|
|
175
|
+
function makeMacOriginatedMessage(): MessageRow {
|
|
176
|
+
return makeMessage({
|
|
177
|
+
metadata: JSON.stringify({
|
|
178
|
+
userMessageChannel: "vellum",
|
|
179
|
+
userMessageInterface: "web",
|
|
180
|
+
client: { os: "macos" },
|
|
181
|
+
clientOsFromRequest: true,
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
150
186
|
function makeAssistantRow(content: ContentBlock[]): MessageRow {
|
|
151
187
|
return makeMessage({
|
|
152
188
|
id: ASSISTANT_MESSAGE_ID,
|
|
@@ -213,7 +249,10 @@ beforeEach(() => {
|
|
|
213
249
|
warnCalls.length = 0;
|
|
214
250
|
messageLookups.length = 0;
|
|
215
251
|
attachmentLookups.length = 0;
|
|
252
|
+
desktopPresenceArgs.length = 0;
|
|
216
253
|
assistantAttachments = [];
|
|
254
|
+
desktopAttended = false;
|
|
255
|
+
desktopPresenceShouldThrow = false;
|
|
217
256
|
getConversationShouldThrow = false;
|
|
218
257
|
conversationRow = makeConversation();
|
|
219
258
|
assistantRow = makeAssistantRow([
|
|
@@ -263,6 +302,137 @@ describe("emitAssistantReplyNotification", () => {
|
|
|
263
302
|
expect(emitCalls).toHaveLength(0);
|
|
264
303
|
});
|
|
265
304
|
|
|
305
|
+
// The push is suppressed downstream, at the source-active pre-gate in
|
|
306
|
+
// `emitNotificationSignal` (covered in `emit-signal-routing-intent.test.ts`),
|
|
307
|
+
// so the producer's contract here is the hint it emits, not the silence.
|
|
308
|
+
describe("desktop presence", () => {
|
|
309
|
+
beforeEach(() => {
|
|
310
|
+
initiatingRow = makeMacOriginatedMessage();
|
|
311
|
+
desktopAttended = true;
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("marks the signal source-active while a Mac reports itself attended", async () => {
|
|
315
|
+
await run();
|
|
316
|
+
|
|
317
|
+
expect(emitCalls).toHaveLength(1);
|
|
318
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(true);
|
|
319
|
+
// Unscoped by design: the push targets the assistant owner, and a pod
|
|
320
|
+
// has exactly one owner, so no principal filter belongs here.
|
|
321
|
+
expect(desktopPresenceArgs).toEqual([[]]);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("leaves the signal live when no Mac is attended", async () => {
|
|
325
|
+
desktopAttended = false;
|
|
326
|
+
|
|
327
|
+
await run();
|
|
328
|
+
|
|
329
|
+
expect(emitCalls).toHaveLength(1);
|
|
330
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(false);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("leaves the signal live when the presence flag is off", async () => {
|
|
334
|
+
setOverridesForTesting({ "desktop-presence-suppression": false });
|
|
335
|
+
|
|
336
|
+
await run();
|
|
337
|
+
|
|
338
|
+
expect(emitCalls).toHaveLength(1);
|
|
339
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(false);
|
|
340
|
+
expect(desktopPresenceArgs).toEqual([]);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("leaves the signal live when the presence read throws", async () => {
|
|
344
|
+
desktopPresenceShouldThrow = true;
|
|
345
|
+
|
|
346
|
+
await run();
|
|
347
|
+
|
|
348
|
+
expect(emitCalls).toHaveLength(1);
|
|
349
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(false);
|
|
350
|
+
expect(warnCalls).toHaveLength(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// An attended Mac only speaks for a turn the Mac itself opened: the user
|
|
354
|
+
// can send from the phone minutes after last touching the keyboard, which
|
|
355
|
+
// is exactly the reply this producer exists to push.
|
|
356
|
+
const NON_MAC_ORIGIN_CASES: Array<{ name: string; metadata: unknown }> = [
|
|
357
|
+
{
|
|
358
|
+
name: "the iOS app",
|
|
359
|
+
metadata: { client: { os: "ios" }, clientOsFromRequest: true },
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "a browser",
|
|
363
|
+
metadata: { client: { os: "web" }, clientOsFromRequest: true },
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "a client that reports no OS",
|
|
367
|
+
metadata: { client: {} },
|
|
368
|
+
},
|
|
369
|
+
{ name: "a row with no client bag", metadata: {} },
|
|
370
|
+
{
|
|
371
|
+
name: "a client reporting an unknown OS",
|
|
372
|
+
metadata: { client: { os: "bsd" }, clientOsFromRequest: true },
|
|
373
|
+
},
|
|
374
|
+
// The fail-closed shape this gate exists to refuse: a surface action
|
|
375
|
+
// tapped on the phone carries no transport, so persistence stamps the
|
|
376
|
+
// `macos` the conversation's live client state kept from an earlier
|
|
377
|
+
// desktop send. Without the marker that OS names an earlier turn, not
|
|
378
|
+
// this one, and the push the user is waiting for on their phone would
|
|
379
|
+
// be dropped by the attended Mac.
|
|
380
|
+
{
|
|
381
|
+
name: "a row whose macOS OS was inherited, not reported",
|
|
382
|
+
metadata: { userMessageInterface: "web", client: { os: "macos" } },
|
|
383
|
+
},
|
|
384
|
+
// A marker without a matching OS is not evidence of anything.
|
|
385
|
+
{
|
|
386
|
+
name: "a row marked request-reported with no client bag",
|
|
387
|
+
metadata: { clientOsFromRequest: true },
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
for (const { name, metadata } of NON_MAC_ORIGIN_CASES) {
|
|
392
|
+
test(`leaves the signal live for a turn opened from ${name}`, async () => {
|
|
393
|
+
initiatingRow = makeMessage({ metadata: JSON.stringify(metadata) });
|
|
394
|
+
|
|
395
|
+
await run();
|
|
396
|
+
|
|
397
|
+
expect(emitCalls).toHaveLength(1);
|
|
398
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// The transport interface is "web" for the macOS app, the iOS app, and a
|
|
403
|
+
// desktop browser alike, so it cannot stand in for the client OS.
|
|
404
|
+
test("leaves the signal live for a web-interface turn with no client OS", async () => {
|
|
405
|
+
initiatingRow = makeMessage({
|
|
406
|
+
metadata: JSON.stringify({
|
|
407
|
+
userMessageChannel: "vellum",
|
|
408
|
+
userMessageInterface: "web",
|
|
409
|
+
}),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
await run();
|
|
413
|
+
|
|
414
|
+
expect(emitCalls).toHaveLength(1);
|
|
415
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(false);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// An unrecognized channel fails the whole metadata schema, so the origin
|
|
419
|
+
// read has to answer off the permissive fallback too.
|
|
420
|
+
test("marks the signal source-active when only the channel is unrecognized", async () => {
|
|
421
|
+
initiatingRow = makeMessage({
|
|
422
|
+
metadata: JSON.stringify({
|
|
423
|
+
userMessageChannel: "not-a-channel",
|
|
424
|
+
client: { os: "macos" },
|
|
425
|
+
clientOsFromRequest: true,
|
|
426
|
+
}),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await run();
|
|
430
|
+
|
|
431
|
+
expect(emitCalls).toHaveLength(1);
|
|
432
|
+
expect(emitCalls[0].attentionHints.visibleInSourceNow).toBe(true);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
266
436
|
test("omits requestedTitle when the conversation has no title", async () => {
|
|
267
437
|
conversationRow = makeConversation({ title: " " });
|
|
268
438
|
|
|
@@ -333,7 +503,8 @@ describe("emitAssistantReplyNotification", () => {
|
|
|
333
503
|
});
|
|
334
504
|
|
|
335
505
|
// Blank lines and list indentation would otherwise spend the preview's
|
|
336
|
-
// length budget on whitespace.
|
|
506
|
+
// length budget on whitespace. The bullets go with them: a lock screen
|
|
507
|
+
// renders the marker as literal punctuation, not as a list.
|
|
337
508
|
test("collapses whitespace runs in the preview", async () => {
|
|
338
509
|
assistantRow = makeAssistantRow([
|
|
339
510
|
{ type: "text", text: " Here:\n\n - item\n - other " },
|
|
@@ -342,10 +513,212 @@ describe("emitAssistantReplyNotification", () => {
|
|
|
342
513
|
await run();
|
|
343
514
|
|
|
344
515
|
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
345
|
-
"Here:
|
|
516
|
+
"Here: item other",
|
|
346
517
|
);
|
|
347
518
|
});
|
|
348
519
|
|
|
520
|
+
// A lock screen renders no markdown, so syntax that survives to the APNs
|
|
521
|
+
// payload reads as punctuation soup. See `stripMarkdownForPreview`.
|
|
522
|
+
describe("markdown flattening", () => {
|
|
523
|
+
test("previews a media-only reply as its attachments", async () => {
|
|
524
|
+
assistantRow = makeAssistantRow([
|
|
525
|
+
{
|
|
526
|
+
type: "text",
|
|
527
|
+
text: [
|
|
528
|
+
"",
|
|
529
|
+
"",
|
|
530
|
+
].join(" "),
|
|
531
|
+
},
|
|
532
|
+
] as ContentBlock[]);
|
|
533
|
+
assistantAttachments = [
|
|
534
|
+
{ originalFilename: "cut.mp4" },
|
|
535
|
+
{ originalFilename: "hero.mp4" },
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
await run();
|
|
539
|
+
|
|
540
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
541
|
+
"Sent 2 attachments",
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("keeps the prose when a reply mixes text and media", async () => {
|
|
546
|
+
assistantRow = makeAssistantRow([
|
|
547
|
+
{
|
|
548
|
+
type: "text",
|
|
549
|
+
text: "Here is the scene: ",
|
|
550
|
+
},
|
|
551
|
+
] as ContentBlock[]);
|
|
552
|
+
|
|
553
|
+
await run();
|
|
554
|
+
|
|
555
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
556
|
+
"Here is the scene:",
|
|
557
|
+
);
|
|
558
|
+
// The text branch produced a preview, so the fallback never ran.
|
|
559
|
+
expect(attachmentLookups).toEqual([]);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test("previews a fenced code reply as its code", async () => {
|
|
563
|
+
assistantRow = makeAssistantRow([
|
|
564
|
+
{ type: "text", text: "Fixed it:\n```ts\nconst a = 1;\n```" },
|
|
565
|
+
] as ContentBlock[]);
|
|
566
|
+
|
|
567
|
+
await run();
|
|
568
|
+
|
|
569
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
570
|
+
"Fixed it: const a = 1;",
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("previews a table reply as its cells", async () => {
|
|
575
|
+
assistantRow = makeAssistantRow([
|
|
576
|
+
{
|
|
577
|
+
type: "text",
|
|
578
|
+
text: "## Keys\n\n| Env | Key |\n|---|---|\n| dev | 4Y4L |",
|
|
579
|
+
},
|
|
580
|
+
] as ContentBlock[]);
|
|
581
|
+
|
|
582
|
+
await run();
|
|
583
|
+
|
|
584
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
585
|
+
"Keys Env Key dev 4Y4L",
|
|
586
|
+
);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Remote embeds are as valid as `vellum://` ones per the system prompt, but
|
|
590
|
+
// they register nothing in the attachment store, so the alt text is the
|
|
591
|
+
// only thing left to name them by. Going silent here would be worse than
|
|
592
|
+
// the raw markdown this change removes.
|
|
593
|
+
test("names a remote embed by its alt text", async () => {
|
|
594
|
+
assistantRow = makeAssistantRow([
|
|
595
|
+
{
|
|
596
|
+
type: "text",
|
|
597
|
+
text: "",
|
|
598
|
+
},
|
|
599
|
+
] as ContentBlock[]);
|
|
600
|
+
|
|
601
|
+
await run();
|
|
602
|
+
|
|
603
|
+
expect(emitCalls).toHaveLength(1);
|
|
604
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
605
|
+
"Sent the Q3 chart",
|
|
606
|
+
);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test("counts several remote embeds", async () => {
|
|
610
|
+
assistantRow = makeAssistantRow([
|
|
611
|
+
{
|
|
612
|
+
type: "text",
|
|
613
|
+
text: " ",
|
|
614
|
+
},
|
|
615
|
+
] as ContentBlock[]);
|
|
616
|
+
|
|
617
|
+
await run();
|
|
618
|
+
|
|
619
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
620
|
+
"Sent 2 attachments",
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("falls back to generic copy for a remote embed with no alt", async () => {
|
|
625
|
+
assistantRow = makeAssistantRow([
|
|
626
|
+
{ type: "text", text: "" },
|
|
627
|
+
] as ContentBlock[]);
|
|
628
|
+
|
|
629
|
+
await run();
|
|
630
|
+
|
|
631
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
632
|
+
"Sent an attachment",
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// A `vellum://` embed becomes an attachment row and a remote one does not,
|
|
637
|
+
// so the two sources have to be counted together, and the tracked embed
|
|
638
|
+
// must not be counted twice.
|
|
639
|
+
test("counts local and remote media together", async () => {
|
|
640
|
+
assistantRow = makeAssistantRow([
|
|
641
|
+
{
|
|
642
|
+
type: "text",
|
|
643
|
+
text: " ",
|
|
644
|
+
},
|
|
645
|
+
] as ContentBlock[]);
|
|
646
|
+
assistantAttachments = [{ originalFilename: "a.mp4" }];
|
|
647
|
+
|
|
648
|
+
await run();
|
|
649
|
+
|
|
650
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
651
|
+
"Sent 2 attachments",
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// `resolveAssistantAttachments` skips a file that is missing, oversized,
|
|
656
|
+
// unreadable, or denied at the host-read approval, so a `vellum://` embed
|
|
657
|
+
// can leave no row. Its alt is then the only label the reply has.
|
|
658
|
+
test("names a tracked embed whose attachment never resolved", async () => {
|
|
659
|
+
assistantRow = makeAssistantRow([
|
|
660
|
+
{ type: "text", text: "" },
|
|
661
|
+
] as ContentBlock[]);
|
|
662
|
+
assistantAttachments = [];
|
|
663
|
+
|
|
664
|
+
await run();
|
|
665
|
+
|
|
666
|
+
expect(emitCalls).toHaveLength(1);
|
|
667
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
668
|
+
"Sent the diagram",
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("counts only the tracked embeds that failed to resolve", async () => {
|
|
673
|
+
assistantRow = makeAssistantRow([
|
|
674
|
+
{
|
|
675
|
+
type: "text",
|
|
676
|
+
text: " ",
|
|
677
|
+
},
|
|
678
|
+
] as ContentBlock[]);
|
|
679
|
+
assistantAttachments = [{ originalFilename: "1.png" }];
|
|
680
|
+
|
|
681
|
+
await run();
|
|
682
|
+
|
|
683
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
684
|
+
"Sent 2 attachments",
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("does not double count an embed that became an attachment", async () => {
|
|
689
|
+
assistantRow = makeAssistantRow([
|
|
690
|
+
{ type: "text", text: "" },
|
|
691
|
+
] as ContentBlock[]);
|
|
692
|
+
assistantAttachments = [{ originalFilename: "a.mp4" }];
|
|
693
|
+
|
|
694
|
+
await run();
|
|
695
|
+
|
|
696
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe("Sent a.mp4");
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("prefers the attachment row over the alt text when both exist", async () => {
|
|
700
|
+
assistantRow = makeAssistantRow([
|
|
701
|
+
{ type: "text", text: "" },
|
|
702
|
+
] as ContentBlock[]);
|
|
703
|
+
assistantAttachments = [{ originalFilename: "cut.mp4" }];
|
|
704
|
+
|
|
705
|
+
await run();
|
|
706
|
+
|
|
707
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe("Sent cut.mp4");
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
test("stays silent when a reply has no text, attachments, or embeds", async () => {
|
|
711
|
+
assistantRow = makeAssistantRow([
|
|
712
|
+
{ type: "text", text: "## \n\n---" },
|
|
713
|
+
] as ContentBlock[]);
|
|
714
|
+
|
|
715
|
+
await run();
|
|
716
|
+
|
|
717
|
+
expect(emitCalls).toHaveLength(0);
|
|
718
|
+
expect(attachmentLookups).toEqual([ASSISTANT_MESSAGE_ID]);
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
349
722
|
// Each case asserts the shared classifier's verdict alongside the silence, so
|
|
350
723
|
// the gate is exercised through `resolveConversationKind` rather than through
|
|
351
724
|
// a restatement of its branches.
|
|
@@ -61,9 +61,14 @@ mock.module("../conversation-pairing.js", () => ({
|
|
|
61
61
|
},
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
|
+
// Status writes are inert by default; tests that need a failing store swap
|
|
65
|
+
// the implementation in.
|
|
66
|
+
let updateDeliveryStatusImpl: (status: string) => void = () => {};
|
|
67
|
+
|
|
64
68
|
mock.module("../deliveries-store.js", () => ({
|
|
65
69
|
createDelivery: () => {},
|
|
66
|
-
updateDeliveryStatus: () =>
|
|
70
|
+
updateDeliveryStatus: (_deliveryId: string, status: string) =>
|
|
71
|
+
updateDeliveryStatusImpl(status),
|
|
67
72
|
findDeliveryByDecisionAndChannel: () => undefined,
|
|
68
73
|
}));
|
|
69
74
|
|
|
@@ -162,6 +167,7 @@ beforeEach(() => {
|
|
|
162
167
|
knownConversations = new Set();
|
|
163
168
|
pairingByChannel = {};
|
|
164
169
|
pairingErrorByChannel = {};
|
|
170
|
+
updateDeliveryStatusImpl = () => {};
|
|
165
171
|
});
|
|
166
172
|
|
|
167
173
|
// ── Tests ───────────────────────────────────────────────────────────────
|
|
@@ -235,6 +241,36 @@ describe("NotificationBroadcaster last-resort copy resolution", () => {
|
|
|
235
241
|
});
|
|
236
242
|
});
|
|
237
243
|
|
|
244
|
+
describe("NotificationBroadcaster delivery status recording", () => {
|
|
245
|
+
test("a successful send is recorded as sent even when its status write throws", async () => {
|
|
246
|
+
// The results array is what `emitNotificationSignal` reads to decide
|
|
247
|
+
// whether a retry would re-deliver. Reporting a real send as failed
|
|
248
|
+
// releases the signal's dedupe claim, so the retry sends this message a
|
|
249
|
+
// second time. The lost status write only costs the delivery row.
|
|
250
|
+
composeFallbackReturn = {
|
|
251
|
+
vellum: { title: "Reminder", body: "Time to drink water" },
|
|
252
|
+
};
|
|
253
|
+
updateDeliveryStatusImpl = (status: string) => {
|
|
254
|
+
if (status === "sent") {
|
|
255
|
+
throw new Error("deliveries store is locked");
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const { adapter, sends } = makeCapturingAdapter("vellum");
|
|
260
|
+
const broadcaster = new NotificationBroadcaster([adapter]);
|
|
261
|
+
|
|
262
|
+
const results = await broadcaster.broadcastDecision(
|
|
263
|
+
makeSignal(),
|
|
264
|
+
makeDecision({ renderedCopy: {} }),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(sends.length).toBe(1);
|
|
268
|
+
expect(results.length).toBe(1);
|
|
269
|
+
expect(results[0]?.status).toBe("sent");
|
|
270
|
+
expect(results[0]?.errorMessage).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
238
274
|
describe("NotificationBroadcaster platform deep-link from contextPayload", () => {
|
|
239
275
|
test("uses deepLinkConversationId from contextPayload when no pairing exists", async () => {
|
|
240
276
|
composeFallbackReturn = {
|
|
@@ -255,6 +255,171 @@ describe("composeFallbackCopy honors requestedMessage / requestedTitle", () => {
|
|
|
255
255
|
});
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
+
// ── Plugin schedule templates ─────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
describe("composeFallbackCopy plugin schedule templates", () => {
|
|
261
|
+
test("schedule.declared renders plugin, schedule name, and cadence", () => {
|
|
262
|
+
const signal = makeSignal({
|
|
263
|
+
sourceEventName: "schedule.declared",
|
|
264
|
+
contextPayload: {
|
|
265
|
+
pluginName: "news",
|
|
266
|
+
scheduleName: "digest",
|
|
267
|
+
sourceKey: "plugin:news/digest",
|
|
268
|
+
cadence: "0 9 * * *",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
272
|
+
|
|
273
|
+
expect(copy.vellum?.title).toBe("New plugin schedule: digest");
|
|
274
|
+
expect(copy.vellum?.body).toContain('Plugin "news"');
|
|
275
|
+
expect(copy.vellum?.body).toContain('"digest"');
|
|
276
|
+
expect(copy.vellum?.body).toContain("0 9 * * *");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("schedule.declared omits the cadence suffix when absent", () => {
|
|
280
|
+
const signal = makeSignal({
|
|
281
|
+
sourceEventName: "schedule.declared",
|
|
282
|
+
contextPayload: { pluginName: "news", scheduleName: "digest" },
|
|
283
|
+
});
|
|
284
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
285
|
+
|
|
286
|
+
expect(copy.vellum?.body).not.toContain("(");
|
|
287
|
+
expect(copy.vellum?.body).toContain('"digest"');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("schedule.definition_changed renders plugin and schedule name", () => {
|
|
291
|
+
const signal = makeSignal({
|
|
292
|
+
sourceEventName: "schedule.definition_changed",
|
|
293
|
+
contextPayload: {
|
|
294
|
+
pluginName: "news",
|
|
295
|
+
scheduleName: "digest",
|
|
296
|
+
sourceKey: "plugin:news/digest",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
300
|
+
|
|
301
|
+
expect(copy.vellum?.title).toBe("Plugin schedule changed: digest");
|
|
302
|
+
expect(copy.vellum?.body).toBe(
|
|
303
|
+
'Plugin "news" changed the definition of its schedule "digest".',
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("schedule.definition_error renders plugin, schedule name, and reason", () => {
|
|
308
|
+
const signal = makeSignal({
|
|
309
|
+
sourceEventName: "schedule.definition_error",
|
|
310
|
+
contextPayload: {
|
|
311
|
+
pluginName: "news",
|
|
312
|
+
scheduleName: "digest",
|
|
313
|
+
sourceKey: "plugin:news/digest",
|
|
314
|
+
reason: "invalid cron expression",
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
318
|
+
|
|
319
|
+
expect(copy.vellum?.title).toBe("Plugin schedule error: digest");
|
|
320
|
+
expect(copy.vellum?.body).toContain('Plugin "news"');
|
|
321
|
+
expect(copy.vellum?.body).toContain("invalid cron expression");
|
|
322
|
+
// A non-empty body means the broadcaster's empty-body skip never
|
|
323
|
+
// suppresses a definition error when the notification LLM is down.
|
|
324
|
+
expect(copy.vellum?.body?.length).toBeGreaterThan(0);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("schedule.definition_error says so when the schedule was paused", () => {
|
|
328
|
+
const signal = makeSignal({
|
|
329
|
+
sourceEventName: "schedule.definition_error",
|
|
330
|
+
contextPayload: {
|
|
331
|
+
pluginName: "news",
|
|
332
|
+
scheduleName: "sync",
|
|
333
|
+
sourceKey: "plugin:news/sync",
|
|
334
|
+
reason: "config.json is not valid JSON",
|
|
335
|
+
paused: true,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
339
|
+
|
|
340
|
+
expect(copy.vellum?.body).toContain("config.json is not valid JSON");
|
|
341
|
+
expect(copy.vellum?.body).toContain("paused until the declaration loads");
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("schedule.definition_error omits the pause line when the row kept running", () => {
|
|
345
|
+
const signal = makeSignal({
|
|
346
|
+
sourceEventName: "schedule.definition_error",
|
|
347
|
+
contextPayload: {
|
|
348
|
+
pluginName: "news",
|
|
349
|
+
scheduleName: "digest",
|
|
350
|
+
reason: "invalid cron expression",
|
|
351
|
+
paused: false,
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
355
|
+
|
|
356
|
+
expect(copy.vellum?.body).not.toContain("paused");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("schedule.definition_error still renders with an empty payload", () => {
|
|
360
|
+
const signal = makeSignal({
|
|
361
|
+
sourceEventName: "schedule.definition_error",
|
|
362
|
+
contextPayload: {},
|
|
363
|
+
});
|
|
364
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
365
|
+
|
|
366
|
+
expect(copy.vellum?.body?.length).toBeGreaterThan(0);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("schedule.declared sanitizes plugin-controlled fields", () => {
|
|
370
|
+
// Plugin-authored declaration strings carrying a clear-screen CSI, an OSC
|
|
371
|
+
// title write, a BEL, and a newline must not reach the rendered copy.
|
|
372
|
+
const signal = makeSignal({
|
|
373
|
+
sourceEventName: "schedule.declared",
|
|
374
|
+
contextPayload: {
|
|
375
|
+
pluginName: "news\u001b[2Jext",
|
|
376
|
+
scheduleName: "digest\u0007",
|
|
377
|
+
cadence: "0 9 * * *\u001b]0;pwned\u0007",
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
381
|
+
|
|
382
|
+
expect(copy.vellum?.title).toBe("New plugin schedule: digest");
|
|
383
|
+
expect(copy.vellum?.body).toContain('Plugin "newsext"');
|
|
384
|
+
expect(copy.vellum?.body).toContain("0 9 * * *");
|
|
385
|
+
expect(copy.vellum?.body).not.toContain("\u001b");
|
|
386
|
+
expect(copy.vellum?.body).not.toContain("\u0007");
|
|
387
|
+
expect(copy.vellum?.body).not.toContain("pwned");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("schedule.declared omits a cadence that sanitizes to nothing", () => {
|
|
391
|
+
const signal = makeSignal({
|
|
392
|
+
sourceEventName: "schedule.declared",
|
|
393
|
+
contextPayload: {
|
|
394
|
+
pluginName: "news",
|
|
395
|
+
scheduleName: "digest",
|
|
396
|
+
cadence: "\u001b[2J\u0007",
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
400
|
+
|
|
401
|
+
expect(copy.vellum?.body).not.toContain("(");
|
|
402
|
+
expect(copy.vellum?.body).not.toContain("\u001b");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("schedule.definition_error sanitizes and flattens the reason", () => {
|
|
406
|
+
const signal = makeSignal({
|
|
407
|
+
sourceEventName: "schedule.definition_error",
|
|
408
|
+
contextPayload: {
|
|
409
|
+
pluginName: "news\u001b[31m",
|
|
410
|
+
scheduleName: "digest",
|
|
411
|
+
reason: "bad\nexpression\u001b[0m",
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
const copy = composeFallbackCopy(signal, CHANNELS);
|
|
415
|
+
|
|
416
|
+
expect(copy.vellum?.body).toContain('Plugin "news"');
|
|
417
|
+
expect(copy.vellum?.body).toContain("bad expression");
|
|
418
|
+
expect(copy.vellum?.body).not.toContain("\u001b");
|
|
419
|
+
expect(copy.vellum?.body).not.toContain("\n");
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
258
423
|
// ── deriveTitle ───────────────────────────────────────────────────────
|
|
259
424
|
|
|
260
425
|
describe("deriveTitle", () => {
|