@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
@@ -38,6 +38,9 @@
38
38
  * `plugins:list` tag via the canonical resource-sync publisher (enable and
39
39
  * disable emit the SAME invalidation)
40
40
  * - Threads `x-vellum-client-id` into the published event's `originClientId`
41
+ * - Pokes the plugin-declared schedule reconcile on a successful toggle (and
42
+ * not on a failed one), so the plugin's rows disarm / re-arm immediately
43
+ * instead of at the reconciler's next backstop sweep
41
44
  * - A broadcast failure does not fail a successful toggle (the publisher
42
45
  * swallows hub errors)
43
46
  * - Maps `InvalidPluginNameError` → BadRequestError (400)
@@ -311,6 +314,15 @@ mock.module("../../../cli/lib/toggle-plugin.js", () => ({
311
314
  enablePlugin: enablePluginSpy,
312
315
  }));
313
316
 
317
+ // Spy on the plugin-schedule reconcile the toggle handlers poke. The handlers
318
+ // import it lazily at call time, so this mock intercepts the dynamic import
319
+ // and keeps the reconciler's persistence graph out of the test.
320
+ const reconcilePluginSchedulesSpy = mock(() => Promise.resolve());
321
+
322
+ mock.module("../../../schedule/plugin-schedule-reconciler.js", () => ({
323
+ reconcilePluginSchedules: reconcilePluginSchedulesSpy,
324
+ }));
325
+
314
326
  // Spy on broadcastMessage so we can assert the sync_changed invalidation the
315
327
  // enable/disable handlers emit. The handlers publish through the canonical
316
328
  // `publishPluginsChanged` → `publishSyncInvalidation` path (both left real), so
@@ -1883,7 +1895,14 @@ function inspection(
1883
1895
  remoteError: overrides.remoteError ?? null,
1884
1896
  surfaces:
1885
1897
  overrides.surfaces === undefined
1886
- ? { skills: [], hooks: ["post-model-call"], tools: [] }
1898
+ ? {
1899
+ skills: [],
1900
+ hooks: ["post-model-call"],
1901
+ tools: [],
1902
+ schedules: [
1903
+ { name: "digest", cadence: "0 9 * * *", mode: "execute" },
1904
+ ],
1905
+ }
1887
1906
  : overrides.surfaces,
1888
1907
  };
1889
1908
  }
@@ -1913,6 +1932,26 @@ describe("GET /v1/plugins/:name/inspect", () => {
1913
1932
  expect(inspectSpy.mock.calls[0]?.[0]).toEqual({ name: "level-up" });
1914
1933
  });
1915
1934
 
1935
+ test("the inspect wire schema carries the schedules surface", async () => {
1936
+ // GIVEN an inspection whose surfaces declare a schedule
1937
+ inspectSpy.mockImplementation(async () => inspection());
1938
+ const result = await invokeInspect({ pathParams: { name: "level-up" } });
1939
+
1940
+ // WHEN the handler result is parsed through the route's response schema
1941
+ // (zod strips undeclared keys, so an omission would drop the field)
1942
+ const route = PLUGINS_ROUTES.find(
1943
+ (r) => r.operationId === "plugins_inspect",
1944
+ )!;
1945
+ const parsed = (
1946
+ route.responseBody as { parse: (v: unknown) => PluginInspection }
1947
+ ).parse(result);
1948
+
1949
+ // THEN the declared schedules survive the wire contract
1950
+ expect(parsed.surfaces?.schedules).toEqual([
1951
+ { name: "digest", cadence: "0 9 * * *", mode: "execute" },
1952
+ ]);
1953
+ });
1954
+
1916
1955
  test("a captured marketplace error is returned as 200 with remote-unavailable, not thrown", async () => {
1917
1956
  // GIVEN the catalog was unreachable but a local copy exists, so the lib
1918
1957
  // returns a status rather than throwing
@@ -2139,6 +2178,90 @@ describe("POST /v1/plugins/:name/upgrade", () => {
2139
2178
  });
2140
2179
  });
2141
2180
 
2181
+ test("refuses a concurrent upgrade of the same plugin with ConflictError (409)", async () => {
2182
+ // Two upgrades of one plugin derive the same staging path in this
2183
+ // process, so the second would delete the first's tree mid-swap. It is
2184
+ // refused instead \u2014 the monitor's auto-update sweep and a user clicking
2185
+ // Upgrade can now land at the same moment.
2186
+ let releaseFirst: () => void = () => {};
2187
+ const firstStarted = Promise.withResolvers<void>();
2188
+ upgradeSpy.mockImplementation(async () => {
2189
+ firstStarted.resolve();
2190
+ await new Promise<void>((resolve) => {
2191
+ releaseFirst = resolve;
2192
+ });
2193
+ return upgradeResult();
2194
+ });
2195
+
2196
+ const first = invokeUpgrade({ pathParams: { name: "level-up" } });
2197
+ await firstStarted.promise;
2198
+
2199
+ await expect(
2200
+ invokeUpgrade({ pathParams: { name: "level-up" } }),
2201
+ ).rejects.toBeInstanceOf(ConflictError);
2202
+
2203
+ releaseFirst();
2204
+ await expect(first).resolves.toMatchObject({ outcome: "upgraded" });
2205
+
2206
+ // The guard is released once the first settles, so the next one runs.
2207
+ upgradeSpy.mockImplementation(async () => upgradeResult());
2208
+ await expect(
2209
+ invokeUpgrade({ pathParams: { name: "level-up" } }),
2210
+ ).resolves.toMatchObject({ outcome: "upgraded" });
2211
+ });
2212
+
2213
+ test("a different plugin is not blocked by an in-flight upgrade", async () => {
2214
+ let releaseFirst: () => void = () => {};
2215
+ const firstStarted = Promise.withResolvers<void>();
2216
+ upgradeSpy.mockImplementation(async (opts) => {
2217
+ if (opts.name === "level-up") {
2218
+ firstStarted.resolve();
2219
+ await new Promise<void>((resolve) => {
2220
+ releaseFirst = resolve;
2221
+ });
2222
+ }
2223
+ return upgradeResult({ name: opts.name });
2224
+ });
2225
+
2226
+ const first = invokeUpgrade({ pathParams: { name: "level-up" } });
2227
+ await firstStarted.promise;
2228
+
2229
+ await expect(
2230
+ invokeUpgrade({ pathParams: { name: "other-plugin" } }),
2231
+ ).resolves.toMatchObject({ name: "other-plugin" });
2232
+
2233
+ releaseFirst();
2234
+ await first;
2235
+ });
2236
+
2237
+ test("a dry run neither takes nor respects the in-flight guard", async () => {
2238
+ // Dry runs return before the swap boundary, so they never stage.
2239
+ let releaseFirst: () => void = () => {};
2240
+ const firstStarted = Promise.withResolvers<void>();
2241
+ upgradeSpy.mockImplementation(async (opts) => {
2242
+ if (!opts.dryRun) {
2243
+ firstStarted.resolve();
2244
+ await new Promise<void>((resolve) => {
2245
+ releaseFirst = resolve;
2246
+ });
2247
+ }
2248
+ return upgradeResult();
2249
+ });
2250
+
2251
+ const first = invokeUpgrade({ pathParams: { name: "level-up" } });
2252
+ await firstStarted.promise;
2253
+
2254
+ await expect(
2255
+ invokeUpgrade({
2256
+ pathParams: { name: "level-up" },
2257
+ body: { dryRun: true },
2258
+ }),
2259
+ ).resolves.toMatchObject({ outcome: "upgraded" });
2260
+
2261
+ releaseFirst();
2262
+ await first;
2263
+ });
2264
+
2142
2265
  test("PluginMergeBaselineError \u2192 ConflictError (409)", async () => {
2143
2266
  // A merge strategy whose install-time baseline can't be reconstructed: a
2144
2267
  // well-formed request that isn't actionable in the current state.
@@ -2414,6 +2537,21 @@ function invokeDisable(args: RouteHandlerArgs = {}): { ok: boolean } {
2414
2537
  return disableHandler(args) as { ok: boolean };
2415
2538
  }
2416
2539
 
2540
+ /**
2541
+ * Wait for the toggle handlers' fire-and-forget reconcile poke to land. The
2542
+ * lazy `import()` resolves off the microtask queue, so the assertion cannot
2543
+ * run on the handler's synchronous return.
2544
+ */
2545
+ async function waitForScheduleReconcile(): Promise<void> {
2546
+ for (let i = 0; i < 50; i++) {
2547
+ if (reconcilePluginSchedulesSpy.mock.calls.length > 0) {
2548
+ return;
2549
+ }
2550
+ await new Promise((resolve) => setTimeout(resolve, 2));
2551
+ }
2552
+ throw new Error("plugin schedule reconcile was never triggered");
2553
+ }
2554
+
2417
2555
  /** Assert the spy received exactly one sync_changed carrying `plugins:list`. */
2418
2556
  function expectPluginsListBroadcast(): void {
2419
2557
  expect(broadcastMessageSpy.mock.calls).toHaveLength(1);
@@ -2426,6 +2564,15 @@ describe("POST /v1/plugins/:name/enable", () => {
2426
2564
  beforeEach(() => {
2427
2565
  enablePluginSpy.mockReset();
2428
2566
  broadcastMessageSpy.mockReset();
2567
+ reconcilePluginSchedulesSpy.mockClear();
2568
+ });
2569
+
2570
+ test("reconciles plugin-declared schedules so the plugin's rows re-arm now", async () => {
2571
+ enablePluginSpy.mockImplementation((name) => toggleResult(name, "enable"));
2572
+
2573
+ invokeEnable({ pathParams: { name: "simple-memory" } });
2574
+
2575
+ await waitForScheduleReconcile();
2429
2576
  });
2430
2577
 
2431
2578
  test("enables the plugin and broadcasts sync_changed(plugins:list)", () => {
@@ -2504,6 +2651,31 @@ describe("POST /v1/plugins/:name/disable", () => {
2504
2651
  beforeEach(() => {
2505
2652
  disablePluginSpy.mockReset();
2506
2653
  broadcastMessageSpy.mockReset();
2654
+ reconcilePluginSchedulesSpy.mockClear();
2655
+ });
2656
+
2657
+ test("reconciles plugin-declared schedules so the plugin's rows disarm now", async () => {
2658
+ disablePluginSpy.mockImplementation((name) =>
2659
+ toggleResult(name, "disable"),
2660
+ );
2661
+
2662
+ invokeDisable({ pathParams: { name: "simple-memory" } });
2663
+
2664
+ // Without this poke the rows stay armed until the reconciler's next
2665
+ // backstop sweep.
2666
+ await waitForScheduleReconcile();
2667
+ });
2668
+
2669
+ test("a failed toggle does not poke the schedule reconcile", async () => {
2670
+ disablePluginSpy.mockImplementation((name) => {
2671
+ throw new PluginDirectoryNotFoundError(name);
2672
+ });
2673
+
2674
+ expect(() => invokeDisable({ pathParams: { name: "ghost" } })).toThrow(
2675
+ NotFoundError,
2676
+ );
2677
+ await new Promise((resolve) => setTimeout(resolve, 10));
2678
+ expect(reconcilePluginSchedulesSpy).not.toHaveBeenCalled();
2507
2679
  });
2508
2680
 
2509
2681
  test("disables the plugin and broadcasts sync_changed(plugins:list)", () => {
@@ -394,4 +394,68 @@ describe("POST /v1/question-response", () => {
394
394
  // Pending interaction left in place.
395
395
  expect(pendingInteractions.get("req-legacy-multi")).toBeDefined();
396
396
  });
397
+
398
+ // A gone interaction must read as 404 for every body shape. Clients treat
399
+ // 404 as the terminal signal that retires a stale card; a 400 leaves the
400
+ // card on screen and surfaces a body-validation string the user cannot act
401
+ // on. The legacy shapes are the ones that matter here: the web client sends
402
+ // them for every single-question answer, which is the common prompt.
403
+ test("legacy option against a gone interaction is 404, not a validation 400", async () => {
404
+ let thrown: unknown;
405
+ try {
406
+ await call({
407
+ body: { requestId: "req-gone", kind: "option", optionId: "yes" },
408
+ });
409
+ } catch (err) {
410
+ thrown = err;
411
+ }
412
+ expect(thrown).toBeInstanceOf(NotFoundError);
413
+ });
414
+
415
+ test("legacy free-text against a gone interaction is 404, not a validation 400", async () => {
416
+ let thrown: unknown;
417
+ try {
418
+ await call({
419
+ body: { requestId: "req-gone", kind: "free_text", text: "maybe" },
420
+ });
421
+ } catch (err) {
422
+ thrown = err;
423
+ }
424
+ expect(thrown).toBeInstanceOf(NotFoundError);
425
+ });
426
+
427
+ test("close against a gone interaction is 404", async () => {
428
+ let thrown: unknown;
429
+ try {
430
+ await call({ body: { requestId: "req-gone", kind: "close" } });
431
+ } catch (err) {
432
+ thrown = err;
433
+ }
434
+ expect(thrown).toBeInstanceOf(NotFoundError);
435
+ });
436
+
437
+ test("legacy option against a confirmation requestId is 404, not a validation 400", async () => {
438
+ // Cross-talk safety has to hold for the legacy shapes too: the wrong-kind
439
+ // interaction carries no question metadata for the shim to read.
440
+ pendingInteractions.register("req-confirm-legacy", {
441
+ conversationId: "conv-1",
442
+ kind: "confirmation",
443
+ });
444
+
445
+ let thrown: unknown;
446
+ try {
447
+ await call({
448
+ body: {
449
+ requestId: "req-confirm-legacy",
450
+ kind: "option",
451
+ optionId: "y",
452
+ },
453
+ });
454
+ } catch (err) {
455
+ thrown = err;
456
+ }
457
+ expect(thrown).toBeInstanceOf(NotFoundError);
458
+ // The confirmation is left intact rather than consumed.
459
+ expect(pendingInteractions.get("req-confirm-legacy")).toBeDefined();
460
+ });
397
461
  });
@@ -413,6 +413,26 @@ describe("getRetrospectiveConfig handler", () => {
413
413
  expect(result.success).toBe(true);
414
414
  });
415
415
 
416
+ test("available but not enabled when only the retrospective switch is off", async () => {
417
+ writeFileSync(
418
+ configPath,
419
+ JSON.stringify(
420
+ { memory: { retrospective: { enabled: false } } },
421
+ null,
422
+ 2,
423
+ ) + "\n",
424
+ );
425
+
426
+ const handler = findHandler("getRetrospectiveConfig");
427
+ const result = (await handler({})) as ConfigResponse;
428
+
429
+ // `available` stays true so the Settings row still renders. It reads
430
+ // "Paused" rather than disappearing as it would with memory off.
431
+ expect(result.available).toBe(true);
432
+ expect(result.enabled).toBe(false);
433
+ expect(result.success).toBe(true);
434
+ });
435
+
416
436
  test("stays available when memory v2 is disabled (retrospectives do not gate on v2)", async () => {
417
437
  writeFileSync(
418
438
  configPath,
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Tests for the `getSkillHistory` route's current-resource boundary.
3
+ *
4
+ * The service layer reads retained git history, which outlives the skill it
5
+ * describes: a deleted skill's commits stay in the workspace repository
6
+ * forever. The route is therefore the only place that can decide whether an id
7
+ * still names something, and it must reach the same answer as the sibling file
8
+ * routes rather than serving history for a resource the rest of the API
9
+ * reports as gone.
10
+ *
11
+ * Both collaborators are mocked, because what is under test is the ordering
12
+ * between them: the existence check has to gate the history read, not run
13
+ * alongside it.
14
+ */
15
+
16
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
17
+
18
+ const skillExistsLocallyMock = mock((_id: string): boolean => true);
19
+ const getSkillHistoryMock = mock(async (_id: string, _opts?: unknown) => ({
20
+ skillId: _id,
21
+ revisions: [{ id: "abc1234", changedAt: "", files: ["SKILL.md"], diff: "" }],
22
+ truncatedByCompaction: false,
23
+ }));
24
+
25
+ mock.module("../../../daemon/handlers/skills.js", () => ({
26
+ skillExistsLocally: skillExistsLocallyMock,
27
+ // The route module imports the whole handler surface; the rest is unused
28
+ // here and only needs to exist.
29
+ checkSkillUpdates: mock(),
30
+ configureSkill: mock(),
31
+ createSkill: mock(),
32
+ disableSkill: mock(),
33
+ draftSkill: mock(),
34
+ enableSkill: mock(),
35
+ getSkill: mock(),
36
+ getSkillFileContent: mock(),
37
+ getSkillFiles: mock(),
38
+ getSkillLocalDetail: mock(),
39
+ inspectSkill: mock(),
40
+ installSkill: mock(),
41
+ listSkills: mock(),
42
+ listSkillsFiltered: mock(),
43
+ searchSkills: mock(),
44
+ uninstallSkill: mock(),
45
+ updateSkill: mock(),
46
+ }));
47
+
48
+ mock.module("../../../skills/skill-history.js", () => ({
49
+ getSkillHistory: getSkillHistoryMock,
50
+ }));
51
+
52
+ const { ROUTES } = await import("../skills-routes.js");
53
+
54
+ const handler = ROUTES.find(
55
+ (r) => r.operationId === "getSkillHistory",
56
+ )!.handler;
57
+
58
+ beforeEach(() => {
59
+ skillExistsLocallyMock.mockClear();
60
+ getSkillHistoryMock.mockClear();
61
+ skillExistsLocallyMock.mockImplementation(() => true);
62
+ });
63
+
64
+ describe("getSkillHistory route", () => {
65
+ test("returns history for a skill that currently exists", async () => {
66
+ const result = (await handler({
67
+ pathParams: { id: "release-triage" },
68
+ })) as {
69
+ revisions: unknown[];
70
+ };
71
+
72
+ expect(result.revisions).toHaveLength(1);
73
+ expect(getSkillHistoryMock).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ test("404s for a deleted skill whose commits are still in the repository", async () => {
77
+ // The skill is gone from the resolver, but git would still answer.
78
+ skillExistsLocallyMock.mockImplementation(() => false);
79
+
80
+ await expect(
81
+ handler({ pathParams: { id: "release-triage" } }),
82
+ ).rejects.toThrow(/not found/i);
83
+ });
84
+
85
+ test("does not read history at all when the skill is gone", async () => {
86
+ skillExistsLocallyMock.mockImplementation(() => false);
87
+
88
+ await Promise.resolve(
89
+ handler({ pathParams: { id: "release-triage" } }),
90
+ ).catch(() => {});
91
+
92
+ // Ordering is the point: a check that ran after the read would still
93
+ // reject, but would have spent the git traversal to do it.
94
+ expect(getSkillHistoryMock).not.toHaveBeenCalled();
95
+ });
96
+
97
+ test("rejects a malformed id as the caller's error, not a server fault", async () => {
98
+ getSkillHistoryMock.mockImplementation(() => {
99
+ throw new Error("Invalid skill id: contains a path separator");
100
+ });
101
+
102
+ await expect(handler({ pathParams: { id: "bad" } })).rejects.toMatchObject({
103
+ statusCode: 400,
104
+ });
105
+ });
106
+
107
+ test("surfaces an unexpected service failure as a server error", async () => {
108
+ getSkillHistoryMock.mockImplementation(() => {
109
+ throw new Error("git exploded");
110
+ });
111
+
112
+ await expect(handler({ pathParams: { id: "ok" } })).rejects.toMatchObject({
113
+ statusCode: 500,
114
+ });
115
+ });
116
+ });
@@ -2,8 +2,10 @@
2
2
  * Asserts the `wake_conversation` route elevates trust from the caller's
3
3
  * verified principal type, never the request body: a `local` (CLI/IPC) caller
4
4
  * runs the woken turn as a non-interactive guardian (clientless + guardian
5
- * trustContext) and may attribute cost to a body-supplied `cronRunId`; a remote
6
- * `actor` stays `unknown`/interactive and its body `cronRunId` is ignored.
5
+ * trustContext) and may attribute cost to a body-supplied `cronRunId` and run
6
+ * on a body-supplied schedule's pinned inference profile; a remote `actor`
7
+ * stays `unknown`/interactive and its body `cronRunId` and `scheduleId` are
8
+ * ignored.
7
9
  */
8
10
 
9
11
  import { describe, expect, mock, test } from "bun:test";
@@ -13,6 +15,7 @@ interface CapturedWake {
13
15
  hint: string;
14
16
  trustContext?: { sourceChannel: string; trustClass: string };
15
17
  cronRunId?: string;
18
+ forceOverrideProfile?: string;
16
19
  clientless?: boolean;
17
20
  persistTriggerAsEvent?: boolean;
18
21
  untrustedOutput?: { content: string; source: string };
@@ -25,6 +28,14 @@ mock.module("../../agent-wake.js", () => ({
25
28
  },
26
29
  }));
27
30
 
31
+ const schedules = new Map<string, { inferenceProfile: string | null }>([
32
+ ["sched-pinned", { inferenceProfile: "profile-cheap" }],
33
+ ["sched-unpinned", { inferenceProfile: null }],
34
+ ]);
35
+ mock.module("../../../schedule/schedule-store.js", () => ({
36
+ getSchedule: (id: string) => schedules.get(id) ?? null,
37
+ }));
38
+
28
39
  import { createConversation } from "../../../persistence/conversation-crud.js";
29
40
  import { initializeDb } from "../../../persistence/db-init.js";
30
41
  import { ROUTES } from "../wake-conversation-routes.js";
@@ -85,6 +96,53 @@ describe("wake_conversation principal-gated elevation", () => {
85
96
  });
86
97
  });
87
98
 
99
+ describe("wake_conversation schedule profile pin", () => {
100
+ test("local principal → woken turn runs on the schedule's pinned profile", async () => {
101
+ wakeCalls.length = 0;
102
+ const conversationId = makeConversation();
103
+ await handler({
104
+ body: { conversationId, hint: "poll result", scheduleId: "sched-pinned" },
105
+ headers: { "x-vellum-principal-type": "local" },
106
+ });
107
+ expect(wakeCalls[0].forceOverrideProfile).toBe("profile-cheap");
108
+ });
109
+
110
+ test("schedule with no pinned profile leaves resolution unchanged", async () => {
111
+ wakeCalls.length = 0;
112
+ const conversationId = makeConversation();
113
+ await handler({
114
+ body: {
115
+ conversationId,
116
+ hint: "poll result",
117
+ scheduleId: "sched-unpinned",
118
+ },
119
+ headers: { "x-vellum-principal-type": "local" },
120
+ });
121
+ expect(wakeCalls[0].forceOverrideProfile).toBeUndefined();
122
+ });
123
+
124
+ test("unknown scheduleId resolves as an ordinary wake", async () => {
125
+ wakeCalls.length = 0;
126
+ const conversationId = makeConversation();
127
+ const result = await handler({
128
+ body: { conversationId, hint: "poll result", scheduleId: "sched-gone" },
129
+ headers: { "x-vellum-principal-type": "local" },
130
+ });
131
+ expect(result).toEqual({ invoked: true, producedToolCalls: false });
132
+ expect(wakeCalls[0].forceOverrideProfile).toBeUndefined();
133
+ });
134
+
135
+ test("actor principal → body scheduleId is ignored", async () => {
136
+ wakeCalls.length = 0;
137
+ const conversationId = makeConversation();
138
+ await handler({
139
+ body: { conversationId, hint: "poll result", scheduleId: "sched-pinned" },
140
+ headers: { "x-vellum-principal-type": "actor" },
141
+ });
142
+ expect(wakeCalls[0].forceOverrideProfile).toBeUndefined();
143
+ });
144
+ });
145
+
88
146
  describe("wake_conversation fencing", () => {
89
147
  test("persist + externalContent fence untrusted data, keep hint trusted", async () => {
90
148
  wakeCalls.length = 0;
@@ -3,7 +3,10 @@
3
3
  * log export and profiler export routes.
4
4
  */
5
5
 
6
- import { spawnSync } from "node:child_process";
6
+ import { execFile } from "node:child_process";
7
+ import { promisify } from "node:util";
8
+
9
+ const execFileAsync = promisify(execFile);
7
10
 
8
11
  /** Maximum compressed archive size (50 MB). */
9
12
  export const MAX_ARCHIVE_BYTES = 50 * 1024 * 1024;
@@ -13,19 +16,20 @@ export const MAX_ARCHIVE_BYTES = 50 * 1024 * 1024;
13
16
  * Returns the Buffer on success, or `undefined` if the archive exceeds
14
17
  * the size limit or tar otherwise fails.
15
18
  */
16
- export function createTarGz(
19
+ export async function createTarGz(
17
20
  staging: string,
18
21
  maxBytes: number = MAX_ARCHIVE_BYTES,
19
- ): ArrayBuffer | undefined {
20
- const proc = spawnSync("tar", ["czf", "-", "-C", staging, "."], {
21
- maxBuffer: maxBytes,
22
- timeout: 30_000,
23
- });
24
- if (proc.status !== 0) {
22
+ ): Promise<ArrayBuffer | undefined> {
23
+ try {
24
+ // Exceeding maxBuffer rejects, which maps to the undefined failure return.
25
+ const { stdout } = await execFileAsync(
26
+ "tar",
27
+ ["czf", "-", "-C", staging, "."],
28
+ { maxBuffer: maxBytes, timeout: 30_000, encoding: "buffer" },
29
+ );
30
+ const buf = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout);
31
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
32
+ } catch {
25
33
  return undefined;
26
34
  }
27
- const buf = Buffer.isBuffer(proc.stdout)
28
- ? proc.stdout
29
- : Buffer.from(proc.stdout);
30
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
31
35
  }
@@ -238,7 +238,7 @@ async function handleMultipartUpload(
238
238
 
239
239
  const bytes = new Uint8Array(await file.arrayBuffer());
240
240
 
241
- const attachment = uploadAttachmentFromBytes(filename, mimeType, bytes);
241
+ const attachment = await uploadAttachmentFromBytes(filename, mimeType, bytes);
242
242
  return attachmentPayload(attachment);
243
243
  }
244
244
 
@@ -248,7 +248,7 @@ async function handleMultipartUpload(
248
248
  *
249
249
  * See `handleMultipartUpload` for `gatewayTrustedSource` semantics.
250
250
  */
251
- function handleOctetStreamUpload(
251
+ async function handleOctetStreamUpload(
252
252
  rawBody: Uint8Array,
253
253
  headers: Record<string, string>,
254
254
  queryParams: Record<string, string>,
@@ -287,7 +287,11 @@ function handleOctetStreamUpload(
287
287
  throw new UnsupportedMediaTypeError(validation.error);
288
288
  }
289
289
 
290
- const attachment = uploadAttachmentFromBytes(filename, mimeType, rawBody);
290
+ const attachment = await uploadAttachmentFromBytes(
291
+ filename,
292
+ mimeType,
293
+ rawBody,
294
+ );
291
295
  return attachmentPayload(attachment);
292
296
  }
293
297
 
@@ -296,7 +300,7 @@ function handleOctetStreamUpload(
296
300
  *
297
301
  * See `handleMultipartUpload` for `gatewayTrustedSource` semantics.
298
302
  */
299
- function handleJsonUpload(
303
+ async function handleJsonUpload(
300
304
  body: Record<string, unknown>,
301
305
  rawBody: Uint8Array | undefined,
302
306
  gatewayTrustedSource: boolean,
@@ -369,7 +373,10 @@ function handleJsonUpload(
369
373
  // non-HEIF files (e.g. large videos) from being read into memory.
370
374
  try {
371
375
  if (isHeifImage(readFileHead(destPath, 12))) {
372
- const norm = normalizeImageBytes(mimeType, readFileSync(destPath));
376
+ const norm = await normalizeImageBytes(
377
+ mimeType,
378
+ readFileSync(destPath),
379
+ );
373
380
  if (
374
381
  norm.converted &&
375
382
  norm.bytes.length <= MAX_FILE_BACKED_UPLOAD_BYTES
@@ -407,7 +414,7 @@ function handleJsonUpload(
407
414
  }
408
415
 
409
416
  try {
410
- attachment = uploadAttachment(
417
+ attachment = await uploadAttachment(
411
418
  filename,
412
419
  mimeType,
413
420
  data,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Deferred release for the canned-reply routes (canned first greeting, unknown
3
+ * slash command).
4
+ *
5
+ * These routes answer without an agent loop, so they hold the processing lock
6
+ * themselves and are the only thing that will ever clear it. They publish their
7
+ * client events on the next tick so the HTTP 202 reaches the client first and
8
+ * its `serverToLocalConversationMap` is populated before any SSE arrives, and
9
+ * they release only after that burst so the next queued message cannot start
10
+ * processing ahead of the reply the client is still being told about.
11
+ *
12
+ * The release therefore has to be unconditional: a throw from any broadcast in
13
+ * the burst would otherwise latch the conversation "processing" with no agent
14
+ * loop, no `finally`, and no later path left to clear it, so every subsequent
15
+ * send is queued behind an idle conversation for the life of the daemon.
16
+ */
17
+
18
+ import type { Conversation } from "../../daemon/conversation.js";
19
+ import { getLogger } from "../../util/logger.js";
20
+
21
+ const log = getLogger("canned-reply-release");
22
+
23
+ /** The conversation surface {@link scheduleCannedReplyRelease} touches. */
24
+ export type CannedReplyReleaseTarget = Pick<
25
+ Conversation,
26
+ "setProcessing" | "kickDrainQueue"
27
+ >;
28
+
29
+ export function scheduleCannedReplyRelease(params: {
30
+ conversation: CannedReplyReleaseTarget;
31
+ /** Names the release site in drain logs (e.g. `"canned_greeting"`). */
32
+ origin: string;
33
+ /** The deferred client event burst. Runs before the release. */
34
+ emit: () => void;
35
+ /** Optional work that only makes sense once the lock is free. */
36
+ afterRelease?: () => void;
37
+ }): void {
38
+ const { conversation, origin, emit, afterRelease } = params;
39
+ setTimeout(() => {
40
+ try {
41
+ emit();
42
+ } catch (err) {
43
+ // Swallowed rather than rethrown: this runs from a timer with no caller
44
+ // left to handle it, so an escaping error is an uncaught exception in
45
+ // the daemon process. The reply is already persisted, and clients
46
+ // reconcile a missed event burst on their next fetch.
47
+ log.error({ err, origin }, "Canned reply event burst failed");
48
+ } finally {
49
+ conversation.setProcessing(false);
50
+ void conversation.kickDrainQueue("loop_complete", origin);
51
+ }
52
+ afterRelease?.();
53
+ }, 0);
54
+ }