@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.
Files changed (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. 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: - item - other",
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
+ "![vellum scene](vellum://workspace/clients/web/public/cut.mp4)",
529
+ "![hero animation](vellum://workspace/repos/hero.mp4)",
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: ![vellum scene](vellum://workspace/a.mp4)",
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: "![the Q3 chart](https://cdn.example.com/q3.png)",
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: "![one](https://e.com/1.png) ![two](https://e.com/2.png)",
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: "![](https://cdn.example.com/q3.png)" },
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: "![local](vellum://workspace/a.mp4) ![remote](https://e.com/b.png)",
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: "![the diagram](vellum://workspace/gone.png)" },
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: "![one](vellum://workspace/1.png) ![two](vellum://workspace/2.png)",
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: "![local](vellum://workspace/a.mp4)" },
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: "![scene](vellum://workspace/cut.mp4)" },
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", () => {