@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
@@ -229,6 +229,13 @@ export interface BroadcastDecisionOptions {
229
229
  onConversationCreated?: OnConversationCreatedFn;
230
230
  /** Deadline override for tests; defaults to PLATFORM_OUTCOME_DEADLINE_MS. */
231
231
  platformOutcomeDeadlineMs?: number;
232
+ /**
233
+ * Array each channel's result is appended to as it settles, and the same
234
+ * array `broadcastDecision` returns. Pass one when a broadcast that throws
235
+ * partway still has to be told apart from one that touched no channel: the
236
+ * return value is lost to the throw, the sink is not.
237
+ */
238
+ resultsSink?: NotificationDeliveryResult[];
232
239
  }
233
240
 
234
241
  // Cap on how long the deferred vellum send waits for the platform dispatch
@@ -348,7 +355,7 @@ export class NotificationBroadcaster {
348
355
  signal.contextPayload
349
356
  ? parseAccessRequestPayload(signal.contextPayload)
350
357
  : undefined;
351
- const results: NotificationDeliveryResult[] = [];
358
+ const results: NotificationDeliveryResult[] = options?.resultsSink ?? [];
352
359
 
353
360
  // Vellum pairing carried forward for the platform channel's deep link.
354
361
  // A single-pass carry is safe because orderedChannels sorts vellum first.
@@ -794,7 +801,9 @@ export class NotificationBroadcaster {
794
801
  /**
795
802
  * Dispatch a prepared payload through its channel adapter and record the
796
803
  * outcome (delivery row status + results entry). Returns the adapter's
797
- * result, or null when the send threw.
804
+ * result, or null when the send threw. The recorded result tracks what the
805
+ * adapter actually did, so a status write that fails after a successful
806
+ * send is logged and swallowed rather than downgrading the result.
798
807
  */
799
808
  private async sendAndRecord(
800
809
  dispatch: PendingChannelDispatch,
@@ -822,14 +831,25 @@ export class NotificationBroadcaster {
822
831
  const resolvedMessageId =
823
832
  adapterResult.messageId ?? pairing.messageId ?? undefined;
824
833
  if (hasPersistedDecision) {
825
- updateDeliveryStatus(
826
- deliveryId,
827
- "sent",
828
- undefined,
829
- adapterResult.messageId
830
- ? { messageId: adapterResult.messageId }
831
- : undefined,
832
- );
834
+ try {
835
+ updateDeliveryStatus(
836
+ deliveryId,
837
+ "sent",
838
+ undefined,
839
+ adapterResult.messageId
840
+ ? { messageId: adapterResult.messageId }
841
+ : undefined,
842
+ );
843
+ } catch (statusErr) {
844
+ // The message is already out; a lost status write must not turn
845
+ // this into a `failed` result. Callers read the results to decide
846
+ // whether a retry would re-deliver, so reporting a real send as
847
+ // failed releases the signal's dedupe claim and double-sends.
848
+ log.warn(
849
+ { err: statusErr, channel, signalId: signal.signalId },
850
+ "Failed to record a sent delivery; the send itself succeeded",
851
+ );
852
+ }
833
853
  }
834
854
  results.push(
835
855
  buildDeliveryResult(dispatch, "sent", {
@@ -30,6 +30,7 @@ import {
30
30
  NOTIFICATION_TITLE_MAX_LENGTH,
31
31
  readPayloadString,
32
32
  sanitizeIdentityField,
33
+ sanitizeMessagePreview,
33
34
  } from "./notification-utils.js";
34
35
  import type {
35
36
  NotificationSignal,
@@ -47,6 +48,36 @@ function str(value: unknown, fallback: string): string {
47
48
  return fallback;
48
49
  }
49
50
 
51
+ /**
52
+ * Read an untrusted payload string for interpolation into copy: sanitized
53
+ * like an identity field, undefined when the value is absent, not a string,
54
+ * or sanitizes down to nothing.
55
+ */
56
+ function optionalSanitizedPayloadField(value: unknown): string | undefined {
57
+ return typeof value === "string"
58
+ ? nonEmpty(sanitizeIdentityField(value))
59
+ : undefined;
60
+ }
61
+
62
+ /** {@link optionalSanitizedPayloadField} with a fallback for the empty case. */
63
+ function sanitizedPayloadField(value: unknown, fallback: string): string {
64
+ return optionalSanitizedPayloadField(value) ?? fallback;
65
+ }
66
+
67
+ /**
68
+ * Plugin and schedule names from a `schedule.*` payload, with the fallbacks
69
+ * every schedule template shares.
70
+ */
71
+ function schedulePayloadNames(payload: Record<string, unknown>): {
72
+ scheduleName: string;
73
+ pluginName: string;
74
+ } {
75
+ return {
76
+ scheduleName: sanitizedPayloadField(payload.scheduleName, "a schedule"),
77
+ pluginName: sanitizedPayloadField(payload.pluginName, "A plugin"),
78
+ };
79
+ }
80
+
50
81
  /**
51
82
  * Derive a short notification title from a message body. Trims to the
52
83
  * first sentence terminator when present, then caps the result at
@@ -115,6 +146,44 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
115
146
  body: str(payload.message, "A reminder has fired"),
116
147
  }),
117
148
 
149
+ // The schedule.* fields below (names, cadence, error reason) originate in
150
+ // plugin-authored declaration files, so they are sanitized like any other
151
+ // untrusted actor-controlled string before interpolation.
152
+ "schedule.declared": (payload) => {
153
+ const { scheduleName, pluginName } = schedulePayloadNames(payload);
154
+ const cadence = optionalSanitizedPayloadField(payload.cadence);
155
+ const cadenceSuffix = cadence ? ` (${cadence})` : "";
156
+ return {
157
+ title: `New plugin schedule: ${scheduleName}`,
158
+ body: `Plugin "${pluginName}" armed the schedule "${scheduleName}"${cadenceSuffix}. It will run automatically; you can disable it from your schedules.`,
159
+ };
160
+ },
161
+
162
+ "schedule.definition_changed": (payload) => {
163
+ const { scheduleName, pluginName } = schedulePayloadNames(payload);
164
+ return {
165
+ title: `Plugin schedule changed: ${scheduleName}`,
166
+ body: `Plugin "${pluginName}" changed the definition of its schedule "${scheduleName}".`,
167
+ };
168
+ },
169
+
170
+ "schedule.definition_error": (payload) => {
171
+ const { scheduleName, pluginName } = schedulePayloadNames(payload);
172
+ const reason = sanitizeMessagePreview(
173
+ str(payload.reason, "the declaration is invalid"),
174
+ );
175
+ // `paused` says the schedule stopped running over this error rather than
176
+ // carrying on with what it last loaded, which is the part the user acts on.
177
+ const pausedSuffix =
178
+ payload.paused === true
179
+ ? " It is paused until the declaration loads again."
180
+ : "";
181
+ return {
182
+ title: `Plugin schedule error: ${scheduleName}`,
183
+ body: `Plugin "${pluginName}" has a schedule "${scheduleName}" that could not be loaded: ${reason}${pausedSuffix}`,
184
+ };
185
+ },
186
+
118
187
  "guardian.question": (payload) => {
119
188
  const question = str(
120
189
  payload.questionText,
@@ -21,6 +21,7 @@ import type { ConversationCreateType } from "../persistence/conversation-types.j
21
21
  import { isPlatformClientConfigured } from "../platform/client.js";
22
22
  import { broadcastMessage } from "../runtime/assistant-event-hub.js";
23
23
  import { getLogger } from "../util/logger.js";
24
+ import { withSqliteRetry } from "../util/sqlite-retry.js";
24
25
  import { VellumAdapter } from "./adapters/macos.js";
25
26
  import { PlatformPushAdapter } from "./adapters/platform.js";
26
27
  import { SlackAdapter } from "./adapters/slack.js";
@@ -36,7 +37,7 @@ import {
36
37
  type DeterministicCheckContext,
37
38
  runDeterministicChecks,
38
39
  } from "./deterministic-checks.js";
39
- import { createEvent, updateEventDedupeKey } from "./events-store.js";
40
+ import { createEvent, setEventDedupeKey } from "./events-store.js";
40
41
  import { writeHomeFeedItemForSignal } from "./home-feed-side-effect.js";
41
42
  import { dispatchDecision } from "./runtime-dispatch.js";
42
43
  import type {
@@ -226,6 +227,25 @@ export interface EmitSignalResult {
226
227
  dispatched: boolean;
227
228
  reason: string;
228
229
  deliveryResults: NotificationDeliveryResult[];
230
+ /**
231
+ * True when the pipeline threw before reaching a verdict. Dispatch, dedupe,
232
+ * suppression, and a blocked deterministic check are all verdicts. Callers
233
+ * that latch their own guard on a completed emit branch on this flag;
234
+ * `reason` is a human-facing log string and never a control signal.
235
+ */
236
+ pipelineFailed: boolean;
237
+ }
238
+
239
+ /**
240
+ * True when at least one channel adapter has already been handed the signal.
241
+ * `sent` is a completed delivery; `pending` is the platform push that passed
242
+ * its outcome deadline and is still in flight. Both mean a retry would
243
+ * re-deliver.
244
+ */
245
+ function hasChannelSideEffect(
246
+ results: readonly NotificationDeliveryResult[],
247
+ ): boolean {
248
+ return results.some((r) => r.status === "sent" || r.status === "pending");
229
249
  }
230
250
 
231
251
  /**
@@ -245,6 +265,15 @@ export async function emitNotificationSignal<TEventName extends string>(
245
265
  ): Promise<EmitSignalResult> {
246
266
  const signalId = uuid();
247
267
 
268
+ // The event row claims `params.dedupeKey` when it lands, and the failure
269
+ // path below releases that claim.
270
+ let eventPersisted = false;
271
+
272
+ // Every channel result the broadcast produces, appended as each channel
273
+ // settles. A throw discards the dispatch return value, so this is how the
274
+ // failure path learns which channels already ran.
275
+ const channelResults: NotificationDeliveryResult[] = [];
276
+
248
277
  const signal: NotificationSignal<TEventName> = {
249
278
  signalId,
250
279
  createdAt: Date.now(),
@@ -263,16 +292,27 @@ export async function emitNotificationSignal<TEventName extends string>(
263
292
  };
264
293
 
265
294
  try {
266
- // Step 1: Persist the event
267
- const eventRow = createEvent({
268
- id: signalId,
269
- sourceEventName: params.sourceEventName,
270
- sourceChannel: params.sourceChannel,
271
- sourceContextId: params.sourceContextId,
272
- attentionHints: params.attentionHints,
273
- payload: (params.contextPayload ?? {}) as Record<string, unknown>,
274
- dedupeKey: params.dedupeKey,
275
- });
295
+ // Step 1: Persist the event. The insert contends with other writers on
296
+ // the shared database (notably the memory worker's bulk writes), and a
297
+ // lost signal is unrecoverable (the producer has already returned by
298
+ // the time contention surfaces), so transient `SQLITE_BUSY`/`SQLITE_IOERR`
299
+ // rides the shared retry helper instead of failing the pipeline.
300
+ const eventRow = await withSqliteRetry(
301
+ () =>
302
+ createEvent({
303
+ id: signalId,
304
+ sourceEventName: params.sourceEventName,
305
+ sourceChannel: params.sourceChannel,
306
+ sourceContextId: params.sourceContextId,
307
+ attentionHints: params.attentionHints,
308
+ payload: (params.contextPayload ?? {}) as Record<string, unknown>,
309
+ dedupeKey: params.dedupeKey,
310
+ }),
311
+ {
312
+ op: "notification-create-event",
313
+ context: { signalId, sourceEventName: params.sourceEventName },
314
+ },
315
+ );
276
316
 
277
317
  if (!eventRow) {
278
318
  log.info(
@@ -285,8 +325,10 @@ export async function emitNotificationSignal<TEventName extends string>(
285
325
  dispatched: false,
286
326
  reason: "Signal deduplicated at event store level",
287
327
  deliveryResults: [],
328
+ pipelineFailed: false,
288
329
  };
289
330
  }
331
+ eventPersisted = true;
290
332
 
291
333
  // Step 1.5: Source-active pre-gate. visibleInSourceNow is a hard invariant
292
334
  // the decision engine cannot override, and it depends only on the signal —
@@ -307,6 +349,7 @@ export async function emitNotificationSignal<TEventName extends string>(
307
349
  dispatched: false,
308
350
  reason: `Signal suppressed: ${sourceActiveCheck.reason}`,
309
351
  deliveryResults: [],
352
+ pipelineFailed: false,
310
353
  };
311
354
  }
312
355
 
@@ -427,7 +470,7 @@ export async function emitNotificationSignal<TEventName extends string>(
427
470
  // only the producer's dedupeKey, which may be null).
428
471
  if (decision.dedupeKey && !params.dedupeKey) {
429
472
  try {
430
- updateEventDedupeKey(signalId, decision.dedupeKey);
473
+ setEventDedupeKey(signalId, decision.dedupeKey);
431
474
  } catch (err) {
432
475
  log.warn(
433
476
  { err, signalId },
@@ -458,6 +501,7 @@ export async function emitNotificationSignal<TEventName extends string>(
458
501
  dispatched: false,
459
502
  reason: `Signal blocked by deterministic checks: ${checkResult.reason}`,
460
503
  deliveryResults: [],
504
+ pipelineFailed: false,
461
505
  };
462
506
  }
463
507
  }
@@ -472,9 +516,10 @@ export async function emitNotificationSignal<TEventName extends string>(
472
516
  signal,
473
517
  decision,
474
518
  broadcaster,
475
- params.onConversationCreated
476
- ? { onConversationCreated: params.onConversationCreated }
477
- : undefined,
519
+ {
520
+ onConversationCreated: params.onConversationCreated,
521
+ resultsSink: channelResults,
522
+ },
478
523
  );
479
524
 
480
525
  // Step 5: Mirror background-origin signals into the home activity feed.
@@ -511,6 +556,7 @@ export async function emitNotificationSignal<TEventName extends string>(
511
556
  dispatched: dispatchResult.dispatched,
512
557
  reason: dispatchResult.reason,
513
558
  deliveryResults: dispatchResult.deliveryResults,
559
+ pipelineFailed: false,
514
560
  };
515
561
  } catch (err) {
516
562
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -518,6 +564,26 @@ export async function emitNotificationSignal<TEventName extends string>(
518
564
  { err: errMsg, signalId, sourceEventName: params.sourceEventName },
519
565
  "Signal pipeline failed",
520
566
  );
567
+ // The persisted event claimed this signal's dedupe key before the
568
+ // pipeline threw. Leaving the claim in place makes every retry
569
+ // short-circuit as a duplicate of an emit that never reached a verdict,
570
+ // so release it. The row itself stays as the audit trail of the attempt.
571
+ //
572
+ // A partially dispatched broadcast is the exception. Its retry gets a
573
+ // fresh decision id, and delivery dedupe is keyed to the decision, so
574
+ // the channels that already went out would go out again. Keeping the
575
+ // claim costs the channels that did not get the signal; releasing it
576
+ // double-sends to the ones that did.
577
+ if (eventPersisted && !hasChannelSideEffect(channelResults)) {
578
+ try {
579
+ setEventDedupeKey(signalId, null);
580
+ } catch (releaseErr) {
581
+ log.warn(
582
+ { err: releaseErr, signalId },
583
+ "Failed to release the dedupe key of a failed signal",
584
+ );
585
+ }
586
+ }
521
587
  if (params.throwOnError) {
522
588
  throw err instanceof Error ? err : new Error(errMsg);
523
589
  }
@@ -527,6 +593,7 @@ export async function emitNotificationSignal<TEventName extends string>(
527
593
  dispatched: false,
528
594
  reason: `Signal pipeline failed: ${errMsg}`,
529
595
  deliveryResults: [],
596
+ pipelineFailed: true,
530
597
  };
531
598
  }
532
599
  }
@@ -91,8 +91,16 @@ export function createEvent(
91
91
  return row;
92
92
  }
93
93
 
94
- /** Update the dedupeKey on an existing event (e.g. when the decision engine generates one). */
95
- export function updateEventDedupeKey(eventId: string, dedupeKey: string): void {
94
+ /**
95
+ * Write an existing event's dedupe key. A string claims that key, which is
96
+ * how a decision-engine-generated key lands on the row. `null` releases the
97
+ * claim so a later event may take it while this row stays for the audit
98
+ * trail.
99
+ */
100
+ export function setEventDedupeKey(
101
+ eventId: string,
102
+ dedupeKey: string | null,
103
+ ): void {
96
104
  const db = getDb();
97
105
  db.update(notificationEvents)
98
106
  .set({ dedupeKey, updatedAt: Date.now() })
@@ -170,9 +170,12 @@ function deriveFallbackTitle(summary: string): string {
170
170
  // fence into a stray backtick). Ordered markers matter most: the seed prompt
171
171
  // asks the model for numbered lists, and a leading `1.` also reads as a
172
172
  // sentence terminator, which would truncate the title to the first digit.
173
- // The leading-space allowance and rule set mirror `flattenToPlainText` in
174
- // workspace migration 138, which must stay self-contained and so cannot share
175
- // this code. A backfilled title and a freshly written one have to match.
173
+ //
174
+ // Deliberately regex-based, unlike the remark-backed
175
+ // `stripMarkdownForPreview`. The leading-space allowance and rule set mirror
176
+ // `flattenToPlainText` in workspace migration 138, which must stay
177
+ // self-contained and so cannot share code with either: a backfilled title and
178
+ // a freshly written one have to match, which a parser swap would break.
176
179
  const deblocked = summary
177
180
  .replace(/^\s{0,3}(?:```|~~~).*$/gm, "")
178
181
  .replace(/^\s{0,3}#{1,6}\s+/gm, "")
@@ -6,6 +6,10 @@
6
6
  * circular dependencies.
7
7
  */
8
8
 
9
+ import type { Root, RootContent } from "mdast";
10
+
11
+ import { parseMarkdown } from "../messaging/content/parse.js";
12
+ import { stripAnsiAndControlChars } from "../util/ansi.js";
9
13
  import { isPlainObject } from "../util/object.js";
10
14
 
11
15
  // ── String helpers ──────────────────────────────────────────────────────────
@@ -59,14 +63,178 @@ export function truncate(text: string, maxLength: number): string {
59
63
  return text.slice(0, maxLength - 1) + "…";
60
64
  }
61
65
 
66
+ // ── Markdown flattening ─────────────────────────────────────────────────────
67
+
68
+ /**
69
+ * Node types whose children are blocks rather than an inline run, so their text
70
+ * needs a paragraph break between children instead of being butted together.
71
+ */
72
+ const BLOCK_CONTAINER_TYPES = new Set([
73
+ "blockquote",
74
+ "listItem",
75
+ "footnoteDefinition",
76
+ ]);
77
+
78
+ /** Plain text of one mdast node, markers and media dropped. */
79
+ function nodeText(node: RootContent): string {
80
+ switch (node.type) {
81
+ // Literal runs. Code keeps its value: a fenced block previews as its code,
82
+ // and the parser has already decided what is code, so markdown-looking
83
+ // lines inside it are safe.
84
+ case "text":
85
+ case "inlineCode":
86
+ case "code":
87
+ return node.value;
88
+ // Raw HTML, which the parser splits into tag nodes around the text between
89
+ // them. The in-app renderer registers no `rehype-raw`, so tags and comments
90
+ // display as nothing there; dropping the tag nodes keeps the preview
91
+ // matching what the reply actually looks like, and keeps the contents of an
92
+ // HTML comment off the lock screen. The visible text between a tag pair is
93
+ // its own `text` node and survives.
94
+ case "html":
95
+ return "";
96
+ // Media carries no prose. A caller names it separately when the flattened
97
+ // text comes up empty; see {@link mediaEmbedAltTexts}.
98
+ //
99
+ // A space rather than nothing: an embed needs no surrounding whitespace, so
100
+ // `before![alt](url)after` would otherwise flatten to one run-on word. The
101
+ // callers collapse and trim, so an embed-only reply still reduces to empty
102
+ // and still reaches its media fallback.
103
+ case "image":
104
+ case "imageReference":
105
+ return " ";
106
+ case "break":
107
+ return "\n";
108
+ case "thematicBreak":
109
+ return "";
110
+ case "table":
111
+ case "list":
112
+ return node.children.map(nodeText).join("\n");
113
+ case "tableRow":
114
+ return node.children.map(nodeText).join(" ");
115
+ default:
116
+ return "children" in node
117
+ ? node.children
118
+ .map(nodeText)
119
+ .join(BLOCK_CONTAINER_TYPES.has(node.type) ? "\n\n" : "")
120
+ : "";
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Flatten markdown to the plain text a notification surface renders, where
126
+ * markers would otherwise arrive as literal punctuation.
127
+ *
128
+ * Parses with the same remark/GFM processor the web client and the channel
129
+ * renderers use, rather than matching markers by hand. That is what keeps a
130
+ * balanced-paren URL, a tilde fence nested in a backtick one, a pipeless GFM
131
+ * table, and image syntax quoted inside a code span from each needing their own
132
+ * special case.
133
+ *
134
+ * Media embeds are dropped whole, alt text included. The alt describes the
135
+ * attachment rather than adding prose, so keeping it previews a video as its
136
+ * own caption. Copy that is nothing but embeds therefore flattens to empty,
137
+ * which is a caller's signal to describe the media instead.
138
+ *
139
+ * Line structure survives, so callers needing multi-line copy keep it; collapse
140
+ * whitespace separately for a single line.
141
+ */
142
+ export function stripMarkdownForPreview(value: string): string {
143
+ return parseMarkdown(value).children.map(nodeText).join("\n\n");
144
+ }
145
+
146
+ /**
147
+ * Destinations the assistant resolves into attachment rows. Mirrors the link
148
+ * forms `daemon/assistant-attachments.ts` extracts.
149
+ */
150
+ const TRACKED_MEDIA_URL_RE = /^vellum:\/\/(?:workspace|host)\//i;
151
+
152
+ /** A media embed the flattener dropped, kept so a caller can name it. */
153
+ export interface MediaEmbed {
154
+ /**
155
+ * Image description, already plain text: the parser resolves a formatted
156
+ * description rather than handing back its markers.
157
+ */
158
+ alt: string;
159
+ /**
160
+ * Whether the destination resolves through the attachment store. A
161
+ * `vellum://` embed becomes an attachment row, so a caller holding
162
+ * attachment metadata has already counted it; a remote one leaves no row
163
+ * there and its alt is the only label it has.
164
+ */
165
+ tracked: boolean;
166
+ }
167
+
168
+ /** Collect every image node, depth first. */
169
+ function collectMediaEmbeds(
170
+ node: Root | RootContent,
171
+ into: MediaEmbed[],
172
+ ): void {
173
+ if (node.type === "image") {
174
+ into.push({
175
+ alt: node.alt ?? "",
176
+ tracked: TRACKED_MEDIA_URL_RE.test(node.url),
177
+ });
178
+ return;
179
+ }
180
+ // A reference embed names a definition rather than a destination, so it
181
+ // cannot be matched against the attachment store.
182
+ if (node.type === "imageReference") {
183
+ into.push({ alt: node.alt ?? "", tracked: false });
184
+ return;
185
+ }
186
+ if ("children" in node) {
187
+ for (const child of node.children) {
188
+ collectMediaEmbeds(child, into);
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * The media embeds in `value`, in order, empty alts included.
195
+ *
196
+ * Lets a caller describe copy whose entire content was embeds. Remote
197
+ * `https://` embeds are as valid as `vellum://` ones and register nothing in
198
+ * the attachment store, so without this a reply of one remote image would go
199
+ * out with no copy at all rather than merely an unhelpful one.
200
+ */
201
+ export function mediaEmbeds(value: string): MediaEmbed[] {
202
+ const embeds: MediaEmbed[] = [];
203
+ collectMediaEmbeds(parseMarkdown(value), embeds);
204
+ return embeds;
205
+ }
206
+
207
+ /**
208
+ * Copy naming media, from whatever labels a caller could recover: attachment
209
+ * filenames, or the alt texts {@link mediaEmbedAltTexts} pulled back out of
210
+ * copy that flattened away.
211
+ *
212
+ * Labels are model- and tool-authored, so a single one is sanitized before it
213
+ * can reach the lock screen; one that sanitizes away leaves generic copy rather
214
+ * than a dangling "Sent ".
215
+ *
216
+ * Returns an empty string for no labels, leaving the caller to decide whether
217
+ * that means suppress the notification or fall back further.
218
+ */
219
+ export function describeMedia(labels: string[]): string {
220
+ if (labels.length === 0) {
221
+ return "";
222
+ }
223
+ if (labels.length > 1) {
224
+ return `Sent ${labels.length} attachments`;
225
+ }
226
+ const label = sanitizeMessagePreview(labels[0].replace(/\s+/g, " ").trim());
227
+ return label ? `Sent ${label}` : "Sent an attachment";
228
+ }
229
+
62
230
  // ── Sanitization ────────────────────────────────────────────────────────────
63
231
 
64
- /** Strip control characters and newlines, then truncate to `maxLength`. */
232
+ /**
233
+ * Strip escape sequences, flatten control characters and newlines to spaces,
234
+ * then truncate to `maxLength`.
235
+ */
65
236
  function sanitize(value: string, maxLength: number): string {
66
- return truncate(
67
- value.replace(/[\x00-\x1f\x7f-\x9f\r\n]+/g, " ").trim(),
68
- maxLength,
69
- );
237
+ return truncate(stripAnsiAndControlChars(value, " ").trim(), maxLength);
70
238
  }
71
239
 
72
240
  /**
@@ -65,6 +65,18 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
65
65
  id: "schedule.notify",
66
66
  description: "Scheduled notification triggered (one-shot or recurring)",
67
67
  },
68
+ {
69
+ id: "schedule.definition_error",
70
+ description: "Plugin schedule declaration failed to parse or validate",
71
+ },
72
+ {
73
+ id: "schedule.declared",
74
+ description: "Plugin-declared schedule armed for the first time",
75
+ },
76
+ {
77
+ id: "schedule.definition_changed",
78
+ description: "Plugin upgrade changed an armed schedule's definition",
79
+ },
68
80
  {
69
81
  id: "guardian.question",
70
82
  description: "Guardian approval question requiring response",
@@ -35,9 +35,11 @@ Managed-sign-in users should get the integration pre-enabled by setting `service
35
35
 
36
36
  ### 4. Set the logo URL — `seed-providers.ts`
37
37
 
38
- The `logoUrl` field in `seed-providers.ts` is the source of truth for a provider's logo. Most providers use a [Simple Icons](https://simpleicons.org) (CC0-licensed) CDN URL like `https://cdn.simpleicons.org/acme`. The web client resolves logos from this field (see `clients/web/src/components/integrations/integration-icon.tsx`) and falls back to an initials avatar.
38
+ The `logoUrl` field in `seed-providers.ts` is the source of truth for a provider's logo. Most providers use a [Simple Icons](https://simpleicons.org) (CC0-licensed) CDN URL like `https://cdn.simpleicons.org/acme`.
39
39
 
40
- For brands Simple Icons doesn't host (e.g. Salesforce, which Simple Icons removed for trademark reasons), use the `glincker/thesvg` source via jsDelivr `https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/<key>/default.svg`. The recognised `logoUrl` prefixes are enforced by `oauth-provider-seed-logos.test.ts`; if you need a third source, extend that allowlist.
40
+ **Verify the URL actually resolves before you commit it.** `oauth-provider-seed-logos.test.ts` only checks the prefix, so a slug that Simple Icons has never hosted (or has since dropped) passes CI and then renders as an initials avatar in the client. Simple Icons removes brands on trademark request: Salesforce is gone, every Microsoft product went in v13, and Slack is currently absent pending permission from the trademark owner. For those, use the `glincker/thesvg` source via jsDelivr: `https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/<key>/default.svg`. The recognised `logoUrl` prefixes are enforced by `oauth-provider-seed-logos.test.ts`; if you need a third source, extend that allowlist.
41
+
42
+ `logoUrl` is a fallback, not the last word. The web client prefers a logo bundled in `clients/web/public/images/integrations/` when one exists for the provider key, uses `logoUrl` when it doesn't, and only then falls back to an initials avatar (see `BUNDLED_LOGO_URLS` in `clients/web/src/components/integrations/integration-icon.tsx`). Bundling an asset for a new first-class provider is optional but preferred: it survives a CDN removal, works offline, and keeps the integrations list from telling a third party which providers a user is looking at.
41
43
 
42
44
  ### 5. Secret patterns (if applicable) — `packages/service-contracts/src/secret-detection.ts`
43
45
 
@@ -47,6 +47,11 @@ export class BYOOAuthConnection implements OAuthConnection {
47
47
  async (token) => {
48
48
  const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
49
49
  const isTelegram = this.provider === "telegram";
50
+ // Discord bot tokens authenticate with the `Bot ` scheme, not
51
+ // `Bearer`. Sending Bearer here reaches Discord as an unusable
52
+ // credential and comes back 401, which reads as a revoked token.
53
+ const authScheme =
54
+ this.provider === "discord_channel" ? "Bot" : "Bearer";
50
55
  const requestPath = isTelegram
51
56
  ? buildTelegramBotApiPath(req.path, token)
52
57
  : req.path;
@@ -87,7 +92,7 @@ export class BYOOAuthConnection implements OAuthConnection {
87
92
  }
88
93
  }
89
94
  if (!isTelegram) {
90
- headers.set("Authorization", `Bearer ${token}`);
95
+ headers.set("Authorization", `${authScheme} ${token}`);
91
96
  }
92
97
 
93
98
  const resp = await fetch(fullUrl, {
@@ -19,6 +19,7 @@ import {
19
19
  getSecureKeyResultAsync,
20
20
  type SecureKeyResult,
21
21
  } from "../security/secure-keys.js";
22
+ import { manualTokenProvider } from "./manual-token-providers.js";
22
23
 
23
24
  // ── Types ─────────────────────────────────────────────────────────────
24
25
 
@@ -44,16 +45,8 @@ export interface ConnectionAccessTokenResult {
44
45
  * rather than assuming the OAuth access-token path.
45
46
  */
46
47
  function manualTokenAccessCredentialKey(provider: string): string | null {
47
- switch (provider) {
48
- case "slack_channel":
49
- return credentialKey("slack_channel", "bot_token");
50
- case "telegram":
51
- return credentialKey("telegram", "bot_token");
52
- case "sanity":
53
- return credentialKey("sanity", "api_token");
54
- default:
55
- return null;
56
- }
48
+ const spec = manualTokenProvider(provider);
49
+ return spec ? credentialKey(provider, spec.accessField) : null;
57
50
  }
58
51
 
59
52
  /**