@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
@@ -0,0 +1,204 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { DISCORD_MAX_MESSAGE_LENGTH, renderDiscordMessages } from "./render.js";
4
+
5
+ /** Count of fence lines in a chunk, which must be even for balanced markup. */
6
+ function fenceLineCount(chunk: string): number {
7
+ return chunk.split("\n").filter((line) => /^\s*```/.test(line)).length;
8
+ }
9
+
10
+ /** The chunk text with fence bookkeeping lines removed. */
11
+ function withoutFences(chunk: string): string {
12
+ return chunk
13
+ .split("\n")
14
+ .filter((line) => !/^\s*```/.test(line))
15
+ .join("\n");
16
+ }
17
+
18
+ describe("renderDiscordMessages", () => {
19
+ test("returns no chunks for blank input", () => {
20
+ expect(renderDiscordMessages("")).toEqual([]);
21
+ expect(renderDiscordMessages(" \n ")).toEqual([]);
22
+ });
23
+
24
+ test("returns text under the cap unchanged, as a single chunk", () => {
25
+ expect(renderDiscordMessages("hello **world**")).toEqual([
26
+ "hello **world**",
27
+ ]);
28
+ });
29
+
30
+ test("passes markdown through without rewriting it", () => {
31
+ const markdown = "# Heading\n\n- one\n- two\n\n> quote\n\n`code` and *em*";
32
+ expect(renderDiscordMessages(markdown)).toEqual([markdown]);
33
+ });
34
+
35
+ test("splits at the real Discord cap, not just the injectable one", () => {
36
+ const text = "x".repeat(DISCORD_MAX_MESSAGE_LENGTH + 1);
37
+ const chunks = renderDiscordMessages(text);
38
+ expect(chunks.length).toBeGreaterThan(1);
39
+ for (const chunk of chunks) {
40
+ expect(chunk.length).toBeLessThanOrEqual(DISCORD_MAX_MESSAGE_LENGTH);
41
+ }
42
+ });
43
+
44
+ describe("with a small cap", () => {
45
+ const MAX = 40;
46
+
47
+ test("breaks on line boundaries and keeps every chunk within the cap", () => {
48
+ const lines = Array.from({ length: 12 }, (_, i) => `line number ${i}`);
49
+ const chunks = renderDiscordMessages(lines.join("\n"), MAX);
50
+
51
+ expect(chunks.length).toBeGreaterThan(1);
52
+ for (const chunk of chunks) {
53
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
54
+ }
55
+ // Nothing lost, nothing duplicated.
56
+ expect(chunks.join("\n")).toBe(lines.join("\n"));
57
+ });
58
+
59
+ test("closes and reopens a code fence that spans a boundary", () => {
60
+ const body = Array.from({ length: 8 }, (_, i) => `const x${i} = ${i};`);
61
+ const text = ["```ts", ...body, "```"].join("\n");
62
+ const chunks = renderDiscordMessages(text, MAX);
63
+
64
+ expect(chunks.length).toBeGreaterThan(1);
65
+ for (const chunk of chunks) {
66
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
67
+ // Balanced markup: an odd count means a chunk renders as broken code.
68
+ expect(fenceLineCount(chunk) % 2).toBe(0);
69
+ }
70
+ // Every continuation chunk reopens with the original info string, so the
71
+ // language highlighting survives the split.
72
+ for (const chunk of chunks.slice(1)) {
73
+ expect(chunk.startsWith("```ts")).toBe(true);
74
+ }
75
+ // The code itself is intact once the injected fences are stripped.
76
+ expect(chunks.map(withoutFences).join("\n")).toBe(body.join("\n"));
77
+ });
78
+
79
+ test("closes a four-backtick block with four backticks, not three", () => {
80
+ // A closing fence must be at least as long as its opener, so a block
81
+ // opened with ```` is not terminated by a ``` line: every chunk would
82
+ // render as one unterminated code block.
83
+ const body = Array.from({ length: 8 }, (_, i) => `line ${i}`);
84
+ const chunks = renderDiscordMessages(
85
+ ["````ts", ...body, "````"].join("\n"),
86
+ MAX,
87
+ );
88
+
89
+ expect(chunks.length).toBeGreaterThan(1);
90
+ for (const chunk of chunks) {
91
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
92
+ const fences = chunk.split("\n").filter((l) => /^\s*`{3,}/.test(l));
93
+ expect(fences.length % 2).toBe(0);
94
+ // Both ends of every chunk use the opener's own delimiter.
95
+ for (const fence of fences) {
96
+ expect(fence.trimStart().startsWith("````")).toBe(true);
97
+ }
98
+ }
99
+ });
100
+
101
+ test("a three-backtick line is content inside a four-backtick block", () => {
102
+ // Opening with a longer run is exactly how a code block containing a
103
+ // fence is written, so the inner ``` must not close the outer block.
104
+ // The cap is small enough to force the splitter through its fence
105
+ // bookkeeping rather than returning the text whole.
106
+ const body = Array.from({ length: 6 }, (_, i) => `inner line ${i}`);
107
+ const chunks = renderDiscordMessages(
108
+ ["````md", "```ts", ...body, "```", "````"].join("\n"),
109
+ MAX,
110
+ );
111
+
112
+ expect(chunks.length).toBeGreaterThan(1);
113
+ // The outer block stays open throughout, so every continuation chunk
114
+ // reopens with ````. Treating the inner ``` as a close would end the
115
+ // block early and leave later chunks reopening with the wrong delimiter,
116
+ // or not at all.
117
+ for (const chunk of chunks.slice(1)) {
118
+ expect(chunk.startsWith("````md")).toBe(true);
119
+ }
120
+ for (const chunk of chunks) {
121
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
122
+ expect(chunk.trimEnd().endsWith("````")).toBe(true);
123
+ }
124
+ // The inner fences survive as content rather than being consumed.
125
+ expect(chunks.join("\n")).toContain("```ts");
126
+ });
127
+
128
+ test("does not treat a fence inside an open block as a close", () => {
129
+ // A fence line carrying an info string cannot close a block, so this
130
+ // stays one code block rather than becoming two.
131
+ const text = ["```md", "```ts", "still inside", "```"].join("\n");
132
+ expect(renderDiscordMessages(text, 200)).toEqual([text]);
133
+ });
134
+
135
+ test("hard-splits a line longer than a whole chunk", () => {
136
+ const chunks = renderDiscordMessages("y".repeat(MAX * 3), MAX);
137
+ expect(chunks.length).toBeGreaterThan(2);
138
+ for (const chunk of chunks) {
139
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
140
+ }
141
+ expect(chunks.join("")).toBe("y".repeat(MAX * 3));
142
+ });
143
+
144
+ test("hard-splits an over-long line inside a code block within the cap", () => {
145
+ // The reopened fence plus the closing fence eat into every continuation
146
+ // chunk's budget; a split that ignores them overflows the cap.
147
+ const text = ["```python", "z".repeat(MAX * 2), "```"].join("\n");
148
+ const chunks = renderDiscordMessages(text, MAX);
149
+
150
+ expect(chunks.length).toBeGreaterThan(1);
151
+ for (const chunk of chunks) {
152
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
153
+ expect(fenceLineCount(chunk) % 2).toBe(0);
154
+ }
155
+ expect(chunks.map(withoutFences).join("")).toBe("z".repeat(MAX * 2));
156
+ });
157
+
158
+ test("charges the reopened fence against a code line that fits on its own", () => {
159
+ // Geometry chosen so each body line fits a chunk when only the closing
160
+ // fence is charged (30 + 4 <= 40) but not once the reopened "```python"
161
+ // is charged too (10 + 30 + 4 > 40). Budgeting only the close overflows
162
+ // the cap on every continuation chunk.
163
+ const body = ["a".repeat(30), "b".repeat(30)];
164
+ const chunks = renderDiscordMessages(
165
+ ["```python", ...body, "```"].join("\n"),
166
+ MAX,
167
+ );
168
+
169
+ for (const chunk of chunks) {
170
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
171
+ expect(fenceLineCount(chunk) % 2).toBe(0);
172
+ // A chunk holding only a reopened fence pair renders as an empty code
173
+ // block, which is what an unbudgeted reopen produces here.
174
+ expect(withoutFences(chunk).trim().length).toBeGreaterThan(0);
175
+ }
176
+ // Each 30-char line exceeds what a continuation chunk can hold once the
177
+ // fence pair is charged, so it is cut mid-line and the newline between
178
+ // the two body lines does not survive. Character-level fidelity is the
179
+ // guarantee that does: nothing dropped, nothing duplicated.
180
+ expect(chunks.map(withoutFences).join("").replace(/\n/g, "")).toBe(
181
+ body.join(""),
182
+ );
183
+ });
184
+
185
+ test("never splits a surrogate pair", () => {
186
+ const chunks = renderDiscordMessages("😀".repeat(MAX), MAX);
187
+ for (const chunk of chunks) {
188
+ expect(chunk.length).toBeLessThanOrEqual(MAX);
189
+ // A broken pair leaves a lone surrogate at an edge.
190
+ expect(chunk).not.toMatch(/[\uD800-\uDBFF]$/);
191
+ expect(chunk).not.toMatch(/^[\uDC00-\uDFFF]/);
192
+ }
193
+ expect(chunks.join("")).toBe("😀".repeat(MAX));
194
+ });
195
+
196
+ test("emits no blank or fence-only chunks", () => {
197
+ const text = ["```ts", "a".repeat(MAX - 10), "```"].join("\n");
198
+ for (const chunk of renderDiscordMessages(text, MAX)) {
199
+ expect(chunk.trim().length).toBeGreaterThan(0);
200
+ expect(withoutFences(chunk).trim().length).toBeGreaterThan(0);
201
+ }
202
+ });
203
+ });
204
+ });
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Discord message rendering: canonical reply markdown into sendable chunks.
3
+ *
4
+ * Discord renders a markdown dialect close to what the agent already writes
5
+ * (bold, italic, strikethrough, inline code, fenced code, blockquotes, lists,
6
+ * links, and headings h1 to h3), so the text passes through unmodified. The
7
+ * work here is splitting: `content` on Create Message is capped, and a reply
8
+ * over the cap is rejected outright rather than truncated.
9
+ *
10
+ * Splitting is fence-aware and line-aware because a naive slice breaks
11
+ * rendering, not just layout. Cutting inside a fenced code block leaves the
12
+ * first chunk with an unterminated fence and the second starting with a stray
13
+ * "```", so Discord renders both as garbage. Chunks therefore break on line
14
+ * boundaries where possible, and an open fence is closed at the end of one
15
+ * chunk and reopened (with its original info string) at the start of the next.
16
+ *
17
+ * https://docs.discord.com/developers/resources/message
18
+ */
19
+
20
+ /**
21
+ * Discord's cap on the `content` field of a message. Chunks are built to fit
22
+ * inside this so the API never rejects a send for length.
23
+ */
24
+ export const DISCORD_MAX_MESSAGE_LENGTH = 2000;
25
+
26
+ /** A fence line: three or more backticks, optionally indented. */
27
+ const FENCE_PATTERN = /^\s*(`{3,})/;
28
+
29
+ /** A fence line carrying no info string, which can close an open block. */
30
+ const BARE_FENCE_PATTERN = /^\s*`{3,}\s*$/;
31
+
32
+ /**
33
+ * Length of a line's opening backtick run, or 0 when it is not a fence.
34
+ *
35
+ * The run length matters on both ends: a closing fence must be at least as
36
+ * long as the one it closes, so a block opened with four or more backticks is
37
+ * not terminated by a three-backtick line.
38
+ */
39
+ function fenceRun(line: string): number {
40
+ return FENCE_PATTERN.exec(line)?.[1].length ?? 0;
41
+ }
42
+
43
+ /** The closing fence for an open block: its own delimiter, no info string. */
44
+ function closeFor(openFence: string): string {
45
+ return "`".repeat(fenceRun(openFence));
46
+ }
47
+
48
+ /** Cost of appending a block's closing fence to a chunk: the line plus its newline. */
49
+ function closeCostFor(openFence: string | undefined): number {
50
+ return openFence === undefined ? 0 : fenceRun(openFence) + 1;
51
+ }
52
+
53
+ /**
54
+ * The open-fence state after `line` is appended, given the state before it.
55
+ * `undefined` means no code block is open; otherwise it is the opening fence
56
+ * line verbatim, so a split can reopen the block with its original delimiter
57
+ * and info string.
58
+ */
59
+ function fenceStateAfter(
60
+ line: string,
61
+ openFence: string | undefined,
62
+ ): string | undefined {
63
+ const run = fenceRun(line);
64
+ if (run === 0) {
65
+ return openFence;
66
+ }
67
+ if (openFence === undefined) {
68
+ return line;
69
+ }
70
+ // A fence closes the block only when it carries no info string and its run
71
+ // is at least as long as the opener. Anything else is content inside it,
72
+ // which is the whole point of opening with a longer run.
73
+ return BARE_FENCE_PATTERN.test(line) && run >= fenceRun(openFence)
74
+ ? undefined
75
+ : openFence;
76
+ }
77
+
78
+ /**
79
+ * Split a reply into Discord-sized chunks, preserving fenced code blocks
80
+ * across the boundaries.
81
+ *
82
+ * Returns an empty array for blank input so callers can skip the send
83
+ * entirely rather than posting an empty message, which Discord rejects.
84
+ */
85
+ export function renderDiscordMessages(
86
+ text: string,
87
+ maxLength: number = DISCORD_MAX_MESSAGE_LENGTH,
88
+ ): string[] {
89
+ if (!text || text.trim().length === 0) {
90
+ return [];
91
+ }
92
+ if (text.length <= maxLength) {
93
+ return [text];
94
+ }
95
+
96
+ const chunks: string[] = [];
97
+ let current: string[] = [];
98
+ let currentLength = 0;
99
+ /** Fence state at the start of the line being placed. */
100
+ let openFence: string | undefined;
101
+
102
+ /** Close the chunk in progress, reopening an in-flight fence in the next. */
103
+ const flush = (fenceToReopen: string | undefined): void => {
104
+ if (current.length === 0) {
105
+ return;
106
+ }
107
+ if (fenceToReopen !== undefined) {
108
+ current.push(closeFor(fenceToReopen));
109
+ }
110
+ chunks.push(current.join("\n"));
111
+ current = [];
112
+ currentLength = 0;
113
+ if (fenceToReopen !== undefined) {
114
+ current.push(fenceToReopen);
115
+ currentLength = fenceToReopen.length;
116
+ }
117
+ };
118
+
119
+ /**
120
+ * Append one line. `reserve` is the room the closing fence will need once
121
+ * this line is in place, which is why the caller computes the fence state
122
+ * first: an opening fence must budget for its own close.
123
+ */
124
+ const push = (line: string, reserve: number): void => {
125
+ const separator = current.length === 0 ? 0 : 1;
126
+ if (currentLength + separator + line.length + reserve > maxLength) {
127
+ flush(openFence);
128
+ }
129
+ current.push(line);
130
+ currentLength += (current.length === 1 ? 0 : 1) + line.length;
131
+ };
132
+
133
+ for (const line of text.split("\n")) {
134
+ const nextFence = fenceStateAfter(line, openFence);
135
+ // Room the closing fence needs once this line is placed, which is why the
136
+ // fence state is computed first: an opening fence must budget for its own
137
+ // close.
138
+ const closeCost = closeCostFor(nextFence);
139
+ // Room the reopened fence takes at the head of a fresh chunk. Charged
140
+ // against the state *before* this line, because that is the fence a flush
141
+ // would carry over.
142
+ const reopenCost = openFence === undefined ? 0 : openFence.length + 1;
143
+
144
+ if (reopenCost + line.length + closeCost > maxLength) {
145
+ // The line cannot fit even alone in a fresh chunk, so it cannot break on
146
+ // a newline and is cut at the character level. The pieces are fragments
147
+ // of one logical line, so the fence state does not advance across them.
148
+ for (const piece of hardSplit(line, maxLength - reopenCost - closeCost)) {
149
+ push(piece, closeCost);
150
+ }
151
+ openFence = nextFence;
152
+ continue;
153
+ }
154
+
155
+ push(line, closeCost);
156
+ openFence = nextFence;
157
+ }
158
+
159
+ // A trailing chunk holding nothing but a reopened fence would render as an
160
+ // empty code block, so it is dropped rather than sent.
161
+ const onlyReopenedFence =
162
+ openFence !== undefined && current.length === 1 && current[0] === openFence;
163
+ if (current.length > 0 && !onlyReopenedFence) {
164
+ if (openFence !== undefined) {
165
+ current.push(closeFor(openFence));
166
+ }
167
+ chunks.push(current.join("\n"));
168
+ }
169
+
170
+ return chunks.filter((chunk) => chunk.trim().length > 0);
171
+ }
172
+
173
+ /**
174
+ * Cut an over-long line into pieces of at most `maxLength`, never splitting a
175
+ * surrogate pair (which would emit a lone half and render as a replacement
176
+ * character).
177
+ */
178
+ function hardSplit(line: string, maxLength: number): string[] {
179
+ const safeMax = Math.max(1, maxLength);
180
+ const pieces: string[] = [];
181
+ let cursor = 0;
182
+ while (cursor < line.length) {
183
+ let end = Math.min(cursor + safeMax, line.length);
184
+ if (
185
+ end < line.length &&
186
+ line.charCodeAt(end - 1) >= 0xd800 &&
187
+ line.charCodeAt(end - 1) <= 0xdbff
188
+ ) {
189
+ end--;
190
+ }
191
+ pieces.push(line.slice(cursor, end));
192
+ cursor = end;
193
+ }
194
+ return pieces;
195
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The Discord request shape, asserted against a stubbed fetch.
3
+ *
4
+ * `render.test.ts` covers how a reply is split; this covers what actually
5
+ * goes on the wire: the route, the auth header, the mention policy, chunk
6
+ * ordering, and the documented multipart upload form. Those are the parts a
7
+ * unit test can hold, and the parts whose breakage is silent (a wrong route
8
+ * 404s into a failed delivery, a missing `allowed_mentions` pings a server).
9
+ */
10
+
11
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ const BOT_TOKEN = "discord-bot-token";
14
+
15
+ const realSecureKeys = await import("../../../security/secure-keys.js");
16
+ mock.module("../../../security/secure-keys.js", () => ({
17
+ ...realSecureKeys,
18
+ getSecureKeyResultAsync: async () => ({
19
+ value: BOT_TOKEN,
20
+ unreachable: false,
21
+ }),
22
+ }));
23
+
24
+ mock.module("../../../persistence/attachments-store.js", () => ({
25
+ getAttachmentContent: (id: string) =>
26
+ id === "missing" ? null : Buffer.from("file-bytes"),
27
+ }));
28
+
29
+ mock.module("../../../util/logger.js", () => ({
30
+ getLogger: () => ({ debug() {}, info() {}, warn() {}, error() {} }),
31
+ }));
32
+
33
+ const { sendDiscordReply, sendDiscordAttachments } = await import("./send.js");
34
+
35
+ const originalFetch = globalThis.fetch;
36
+
37
+ interface Captured {
38
+ url: string;
39
+ method: string;
40
+ headers: Record<string, string>;
41
+ body: unknown;
42
+ }
43
+
44
+ let calls: Captured[] = [];
45
+
46
+ function stubFetch(status = 200, payload: unknown = { id: "msg-1" }): void {
47
+ globalThis.fetch = (async (url: string | URL, init?: RequestInit) => {
48
+ const headers = Object.fromEntries(
49
+ new Headers(init?.headers ?? {}).entries(),
50
+ );
51
+ calls.push({
52
+ url: String(url),
53
+ method: init?.method ?? "GET",
54
+ headers,
55
+ body: init?.body,
56
+ });
57
+ return new Response(status === 204 ? null : JSON.stringify(payload), {
58
+ status,
59
+ headers: { "content-type": "application/json" },
60
+ });
61
+ }) as unknown as typeof fetch;
62
+ }
63
+
64
+ beforeEach(() => {
65
+ calls = [];
66
+ stubFetch();
67
+ });
68
+
69
+ afterEach(() => {
70
+ globalThis.fetch = originalFetch;
71
+ });
72
+
73
+ describe("sendDiscordReply", () => {
74
+ test("posts to the channel's messages route with bot auth", async () => {
75
+ await sendDiscordReply({ channelId: "C1" }, "hello");
76
+
77
+ expect(calls).toHaveLength(1);
78
+ expect(calls[0].url).toBe(
79
+ "https://discord.com/api/v10/channels/C1/messages",
80
+ );
81
+ expect(calls[0].method).toBe("POST");
82
+ // Discord rejects a bare token: the scheme prefix is required.
83
+ expect(calls[0].headers.authorization).toBe(`Bot ${BOT_TOKEN}`);
84
+ });
85
+
86
+ test("withholds everyone and role mentions", async () => {
87
+ // The agent composes this text and may echo mention markup from an
88
+ // untrusted inbound message, so it must never be able to ping a whole
89
+ // server. Losing this field is silent until it pings 5000 people.
90
+ await sendDiscordReply({ channelId: "C1" }, "@everyone hi <@&12345>");
91
+
92
+ const body = JSON.parse(calls[0].body as string);
93
+ expect(body.allowed_mentions).toEqual({ parse: ["users"] });
94
+ expect(body.allowed_mentions.parse).not.toContain("everyone");
95
+ expect(body.allowed_mentions.parse).not.toContain("roles");
96
+ // The text itself is passed through unaltered; the policy does the work.
97
+ expect(body.content).toBe("@everyone hi <@&12345>");
98
+ });
99
+
100
+ test("targets a thread by its own id, never the parent channel", async () => {
101
+ await sendDiscordReply({ channelId: "T9" }, "in thread");
102
+ expect(calls[0].url).toContain("/channels/T9/messages");
103
+ });
104
+
105
+ test("sends long replies as ordered chunks, each carrying the policy", async () => {
106
+ const long = Array.from({ length: 400 }, (_, i) => `line ${i}`).join("\n");
107
+ const result = await sendDiscordReply({ channelId: "C1" }, long);
108
+
109
+ expect(calls.length).toBeGreaterThan(1);
110
+ const contents = calls.map((c) => JSON.parse(c.body as string).content);
111
+ // Reassembles in send order, so the reply reads top to bottom.
112
+ expect(contents.join("\n")).toBe(long);
113
+ for (const call of calls) {
114
+ expect(JSON.parse(call.body as string).allowed_mentions).toEqual({
115
+ parse: ["users"],
116
+ });
117
+ }
118
+ expect(result.lastMessageId).toBe("msg-1");
119
+ });
120
+
121
+ test("sends nothing for blank text", async () => {
122
+ await sendDiscordReply({ channelId: "C1" }, " ");
123
+ expect(calls).toHaveLength(0);
124
+ });
125
+ });
126
+
127
+ describe("sendDiscordAttachments", () => {
128
+ test("uploads with the documented multipart form", async () => {
129
+ const result = await sendDiscordAttachments({ channelId: "C1" }, [
130
+ { id: "a1", filename: "report.pdf", mimeType: "application/pdf" },
131
+ ] as never);
132
+
133
+ expect(result.allFailed).toBe(false);
134
+ expect(calls).toHaveLength(1);
135
+ expect(calls[0].url).toBe(
136
+ "https://discord.com/api/v10/channels/C1/messages",
137
+ );
138
+
139
+ const form = calls[0].body as FormData;
140
+ expect(form).toBeInstanceOf(FormData);
141
+ // Files ride as `files[n]`, with the JSON body moved to `payload_json`
142
+ // and each file declared in `attachments` keyed by the same n.
143
+ expect(form.get("files[0]")).toBeInstanceOf(Blob);
144
+ const payload = JSON.parse(form.get("payload_json") as string);
145
+ expect(payload.attachments).toEqual([{ id: 0, filename: "report.pdf" }]);
146
+ expect(payload.allowed_mentions).toEqual({ parse: ["users"] });
147
+
148
+ // fetch must derive the multipart boundary, so Content-Type stays unset.
149
+ expect(calls[0].headers["content-type"]).toBeUndefined();
150
+ });
151
+
152
+ test("reports a refused upload instead of claiming success", async () => {
153
+ stubFetch(413, { message: "Request entity too large" });
154
+
155
+ const result = await sendDiscordAttachments({ channelId: "C1" }, [
156
+ { id: "a1", filename: "huge.bin" },
157
+ ] as never);
158
+
159
+ expect(result.allFailed).toBe(true);
160
+ expect(result.failureCount).toBe(1);
161
+ // The user gets a notice naming the file, not silence.
162
+ const notice = calls
163
+ .filter((c) => typeof c.body === "string")
164
+ .map((c) => JSON.parse(c.body as string).content)
165
+ .join(" ");
166
+ expect(notice).toContain("huge.bin");
167
+ expect(notice).toContain("413");
168
+ });
169
+
170
+ test("does not upload an attachment whose content is gone", async () => {
171
+ const result = await sendDiscordAttachments({ channelId: "C1" }, [
172
+ { id: "missing", filename: "ghost.txt" },
173
+ ] as never);
174
+
175
+ expect(result.allFailed).toBe(true);
176
+ // Only the failure notice, never a zero-byte upload.
177
+ expect(calls.every((c) => typeof c.body === "string")).toBe(true);
178
+ });
179
+ });