@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
@@ -1,3 +1,5 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
4
 
3
5
  mock.module("../heartbeat/heartbeat-service.js", () => ({
@@ -58,12 +60,17 @@ import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
58
60
  import { ROUTES as HEARTBEAT_ROUTES } from "../runtime/routes/heartbeat-routes.js";
59
61
  import { ROUTES as SCHEDULE_ROUTES } from "../runtime/routes/schedule-routes.js";
60
62
  import type { RouteDefinition } from "../runtime/routes/types.js";
63
+ import { resolveDefaultScheduleInferenceProfile } from "../schedule/inference-profile.js";
61
64
  import {
62
65
  completeScheduleRun,
63
66
  createSchedule,
64
67
  createScheduleRun,
68
+ type DeclaredScheduleDefinition,
65
69
  listSchedules,
70
+ upsertDeclaredSchedule,
66
71
  } from "../schedule/schedule-store.js";
72
+ import { getWorkspacePluginsDir } from "../util/platform.js";
73
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
67
74
 
68
75
  await initializeDb();
69
76
 
@@ -1230,14 +1237,50 @@ describe("POST /schedules — create", () => {
1230
1237
  expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
1231
1238
  });
1232
1239
 
1233
- test("defaults inferenceProfile to null when omitted", async () => {
1240
+ // The endpoint validates inferenceProfile once, before branching on mode, so
1241
+ // every mode it accepts has to honour the value it just validated.
1242
+ test("persists a valid inferenceProfile on a workflow-mode create", async () => {
1243
+ const result = await postCreate({
1244
+ name: "Cheap triage",
1245
+ description: "Workflow run on a cheap model",
1246
+ expression: "0 * * * *",
1247
+ message: "trigger",
1248
+ mode: "workflow",
1249
+ workflowName: "triage-inbox",
1250
+ inferenceProfile: "cost-optimized",
1251
+ });
1252
+ expect(result.schedule.inferenceProfile).toBe("cost-optimized");
1253
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
1254
+ });
1255
+
1256
+ test("persists a valid inferenceProfile on a script-mode create", async () => {
1257
+ const result = await postCreate({
1258
+ name: "Cheap script",
1259
+ description: "Script run on a cheap model",
1260
+ expression: "0 * * * *",
1261
+ message: "trigger",
1262
+ mode: "script",
1263
+ script: "console.log('hi')",
1264
+ inferenceProfile: "cost-optimized",
1265
+ });
1266
+ expect(result.schedule.inferenceProfile).toBe("cost-optimized");
1267
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
1268
+ });
1269
+
1270
+ test("pins inferenceProfile to the resolved default when omitted", async () => {
1271
+ const expected = resolveDefaultScheduleInferenceProfile();
1272
+ // A schedule that followed the global default would change model whenever
1273
+ // the user changed that default, so creation snapshots it instead.
1274
+ expect(expected).not.toBeNull();
1275
+
1234
1276
  const result = await postCreate({
1235
1277
  name: "Default profile",
1236
- description: "Runs on the main-agent selection",
1278
+ description: "Runs on the resolved default profile",
1237
1279
  expression: "0 9 * * *",
1238
1280
  message: "hi",
1239
1281
  });
1240
- expect(result.schedule.inferenceProfile).toBeNull();
1282
+ expect(result.schedule.inferenceProfile).toBe(expected!);
1283
+ expect(listSchedules()[0].inferenceProfile).toBe(expected!);
1241
1284
  });
1242
1285
 
1243
1286
  test("rejects an unknown inferenceProfile", async () => {
@@ -1296,7 +1339,10 @@ describe("PATCH /schedules/:id — description", () => {
1296
1339
  expect(listSchedules()[0].description).toBe("Updated description");
1297
1340
  });
1298
1341
 
1299
- test("sets, validates, and clears inferenceProfile", async () => {
1342
+ test("sets, validates, and re-snapshots inferenceProfile", async () => {
1343
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
1344
+ expect(resolvedDefault).not.toBe("cost-optimized");
1345
+
1300
1346
  const schedule = await createSchedule({
1301
1347
  name: "Profile pin",
1302
1348
  description: "Pinned profile schedule",
@@ -1308,9 +1354,9 @@ describe("PATCH /schedules/:id — description", () => {
1308
1354
 
1309
1355
  await route.handler({
1310
1356
  pathParams: { id: schedule.id },
1311
- body: { inferenceProfile: "balanced" },
1357
+ body: { inferenceProfile: "cost-optimized" },
1312
1358
  });
1313
- expect(listSchedules()[0].inferenceProfile).toBe("balanced");
1359
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
1314
1360
 
1315
1361
  await expect(
1316
1362
  route.handler({
@@ -1318,13 +1364,14 @@ describe("PATCH /schedules/:id — description", () => {
1318
1364
  body: { inferenceProfile: "does-not-exist" },
1319
1365
  }),
1320
1366
  ).rejects.toThrow('Inference profile "does-not-exist" is not defined');
1321
- expect(listSchedules()[0].inferenceProfile).toBe("balanced");
1367
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
1322
1368
 
1369
+ // Null is a reset to the current default, not an unpin.
1323
1370
  await route.handler({
1324
1371
  pathParams: { id: schedule.id },
1325
1372
  body: { inferenceProfile: null },
1326
1373
  });
1327
- expect(listSchedules()[0].inferenceProfile).toBeNull();
1374
+ expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault);
1328
1375
  });
1329
1376
 
1330
1377
  test("re-derives syntax when the expression switches cron to rrule", async () => {
@@ -1367,6 +1414,242 @@ describe("PATCH /schedules/:id — description", () => {
1367
1414
  });
1368
1415
  });
1369
1416
 
1417
+ // ── Plugin-sourced schedules ──────────────────────────────────────────────
1418
+
1419
+ describe("plugin-sourced schedules over routes", () => {
1420
+ const SOURCE_KEY = "plugin:example-plugin/daily-digest";
1421
+
1422
+ // The store's enable path probes the declaration's on-disk presence and its
1423
+ // plugin's manifest before re-arming, so the suite keeps a declaration
1424
+ // directory and a valid package.json in place.
1425
+ beforeEach(() => {
1426
+ clearTables();
1427
+ const pluginDir = join(getWorkspacePluginsDir(), "example-plugin");
1428
+ rmSync(pluginDir, { recursive: true, force: true });
1429
+ const declarationDir = join(pluginDir, "schedules", "daily-digest");
1430
+ mkdirSync(declarationDir, { recursive: true });
1431
+ writeFileSync(
1432
+ join(declarationDir, "config.json"),
1433
+ JSON.stringify({ expression: "0 9 * * *" }),
1434
+ );
1435
+ writeFileSync(join(declarationDir, "index.md"), "produce the digest\n");
1436
+ writeFileSync(
1437
+ join(pluginDir, "package.json"),
1438
+ JSON.stringify({ name: "example-plugin", version: "1.0.0" }),
1439
+ );
1440
+ // The feature ships off, so the cases below run under an explicit flag.
1441
+ setOverridesForTesting({ "plugin-schedules": true });
1442
+ });
1443
+
1444
+ function seedSourcedSchedule(
1445
+ overrides: Partial<DeclaredScheduleDefinition> = {},
1446
+ ) {
1447
+ return upsertDeclaredSchedule(SOURCE_KEY, {
1448
+ name: "Daily digest",
1449
+ description: "Summarize the day",
1450
+ syntax: "cron",
1451
+ expression: "0 9 * * *",
1452
+ message: "produce the digest",
1453
+ mode: "execute",
1454
+ enabled: true,
1455
+ definitionHash: "hash-1",
1456
+ ...overrides,
1457
+ });
1458
+ }
1459
+
1460
+ type SerializedEntry = {
1461
+ id: string;
1462
+ enabled: boolean;
1463
+ sourceKey: string | null;
1464
+ userEnabled: boolean | null;
1465
+ };
1466
+
1467
+ test("list serializes sourceKey and userEnabled for sourced and imperative rows", async () => {
1468
+ const sourced = await seedSourcedSchedule();
1469
+ const imperative = await createSchedule({
1470
+ name: "Imperative schedule",
1471
+ cronExpression: "* * * * *",
1472
+ message: "hi",
1473
+ syntax: "cron",
1474
+ });
1475
+
1476
+ const route = findRoute("schedules", "GET");
1477
+ const result = (await route.handler({})) as {
1478
+ schedules: SerializedEntry[];
1479
+ };
1480
+ const byId = new Map(result.schedules.map((s) => [s.id, s]));
1481
+
1482
+ expect(byId.get(sourced.id)).toMatchObject({
1483
+ sourceKey: SOURCE_KEY,
1484
+ userEnabled: null,
1485
+ });
1486
+ expect(byId.get(imperative.id)).toMatchObject({
1487
+ sourceKey: null,
1488
+ userEnabled: null,
1489
+ });
1490
+ });
1491
+
1492
+ test("toggle on a sourced row records and round-trips the user override", async () => {
1493
+ const sourced = await seedSourcedSchedule();
1494
+ const route = findRoute("schedules/:id/toggle", "POST");
1495
+
1496
+ const disabled = (await route.handler({
1497
+ pathParams: { id: sourced.id },
1498
+ body: { enabled: false },
1499
+ })) as { schedules: SerializedEntry[] };
1500
+ expect(disabled.schedules.find((s) => s.id === sourced.id)).toMatchObject({
1501
+ enabled: false,
1502
+ userEnabled: false,
1503
+ });
1504
+
1505
+ const enabled = (await route.handler({
1506
+ pathParams: { id: sourced.id },
1507
+ body: { enabled: true },
1508
+ })) as { schedules: SerializedEntry[] };
1509
+ expect(enabled.schedules.find((s) => s.id === sourced.id)).toMatchObject({
1510
+ enabled: true,
1511
+ userEnabled: true,
1512
+ });
1513
+ });
1514
+
1515
+ test("toggle on an imperative row keeps userEnabled null", async () => {
1516
+ const imperative = await createSchedule({
1517
+ name: "Imperative toggle",
1518
+ cronExpression: "* * * * *",
1519
+ message: "hi",
1520
+ syntax: "cron",
1521
+ });
1522
+
1523
+ const route = findRoute("schedules/:id/toggle", "POST");
1524
+ const result = (await route.handler({
1525
+ pathParams: { id: imperative.id },
1526
+ body: { enabled: false },
1527
+ })) as { schedules: SerializedEntry[] };
1528
+
1529
+ expect(result.schedules.find((s) => s.id === imperative.id)).toMatchObject({
1530
+ enabled: false,
1531
+ userEnabled: null,
1532
+ });
1533
+ });
1534
+
1535
+ test("PATCH on a sourced row surfaces the store refusal as a 400", async () => {
1536
+ const sourced = await seedSourcedSchedule();
1537
+ const route = findRoute("schedules/:id", "PATCH");
1538
+ const patch = () =>
1539
+ route.handler({
1540
+ pathParams: { id: sourced.id },
1541
+ body: { name: "Renamed" },
1542
+ });
1543
+
1544
+ await expect(patch()).rejects.toThrow(BadRequestError);
1545
+ await expect(patch()).rejects.toThrow(
1546
+ "This schedule is managed by a plugin. Edit the plugin's schedule file instead.",
1547
+ );
1548
+ });
1549
+
1550
+ test("DELETE on a sourced row surfaces the store refusal as a 400", async () => {
1551
+ const sourced = await seedSourcedSchedule();
1552
+ const route = findRoute("schedules/:id", "DELETE");
1553
+ const remove = () => route.handler({ pathParams: { id: sourced.id } });
1554
+
1555
+ await expect(remove()).rejects.toThrow(BadRequestError);
1556
+ await expect(remove()).rejects.toThrow(
1557
+ "This schedule is managed by a plugin and cannot be deleted. Disable it instead, or remove the plugin's schedule file.",
1558
+ );
1559
+ });
1560
+
1561
+ test("cancel on a sourced row surfaces the store refusal as a 400", async () => {
1562
+ const sourced = await seedSourcedSchedule();
1563
+ const route = findRoute("schedules/:id/cancel", "POST");
1564
+ const cancel = () => route.handler({ pathParams: { id: sourced.id } });
1565
+
1566
+ await expect(cancel()).rejects.toThrow(BadRequestError);
1567
+ await expect(cancel()).rejects.toThrow(
1568
+ "This schedule is managed by a plugin and cannot be cancelled. Disable it instead via the enabled toggle.",
1569
+ );
1570
+ });
1571
+
1572
+ describe("run now", () => {
1573
+ const RUN_MARKER = join(getWorkspacePluginsDir(), "run-now-marker.txt");
1574
+
1575
+ beforeEach(() => {
1576
+ rmSync(RUN_MARKER, { force: true });
1577
+ });
1578
+
1579
+ function seedSourcedScript() {
1580
+ return seedSourcedSchedule({
1581
+ mode: "script",
1582
+ script: `touch ${RUN_MARKER}`,
1583
+ message: "",
1584
+ });
1585
+ }
1586
+
1587
+ async function runNow(id: string) {
1588
+ return findRoute("schedules/:id/run", "POST").handler({
1589
+ pathParams: { id },
1590
+ });
1591
+ }
1592
+
1593
+ test("refuses a sourced row whose plugin is disabled", async () => {
1594
+ const sourced = await seedSourcedScript();
1595
+ writeFileSync(
1596
+ join(getWorkspacePluginsDir(), "example-plugin", ".disabled"),
1597
+ "",
1598
+ );
1599
+
1600
+ await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
1601
+ // Refused before execution: the plugin's script never ran.
1602
+ expect(existsSync(RUN_MARKER)).toBe(false);
1603
+ });
1604
+
1605
+ test("refuses a sourced row while the feature flag is off", async () => {
1606
+ const sourced = await seedSourcedScript();
1607
+ setOverridesForTesting({ "plugin-schedules": false });
1608
+
1609
+ await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
1610
+ expect(existsSync(RUN_MARKER)).toBe(false);
1611
+ });
1612
+
1613
+ test("refuses a sourced row whose declaration is gone", async () => {
1614
+ const sourced = await seedSourcedScript();
1615
+ rmSync(
1616
+ join(
1617
+ getWorkspacePluginsDir(),
1618
+ "example-plugin",
1619
+ "schedules",
1620
+ "daily-digest",
1621
+ ),
1622
+ { recursive: true, force: true },
1623
+ );
1624
+
1625
+ await expect(runNow(sourced.id)).rejects.toThrow(BadRequestError);
1626
+ expect(existsSync(RUN_MARKER)).toBe(false);
1627
+ });
1628
+
1629
+ test("runs a sourced row whose plugin is healthy", async () => {
1630
+ const sourced = await seedSourcedScript();
1631
+
1632
+ await runNow(sourced.id);
1633
+
1634
+ expect(existsSync(RUN_MARKER)).toBe(true);
1635
+ });
1636
+
1637
+ test("runs an imperative row", async () => {
1638
+ const imperative = await createSchedule({
1639
+ name: "Imperative script",
1640
+ cronExpression: "* * * * *",
1641
+ message: "",
1642
+ mode: "script",
1643
+ script: `touch ${RUN_MARKER}`,
1644
+ });
1645
+
1646
+ await runNow(imperative.id);
1647
+
1648
+ expect(existsSync(RUN_MARKER)).toBe(true);
1649
+ });
1650
+ });
1651
+ });
1652
+
1370
1653
  // ── Wake mode support ─────────────────────────────────────────────────────
1371
1654
 
1372
1655
  describe("wake mode in schedule routes", () => {
@@ -1492,3 +1775,442 @@ describe("PATCH /schedules/:id — timeout override", () => {
1492
1775
  expect(listOne().timeoutMs).toBeNull();
1493
1776
  });
1494
1777
  });
1778
+
1779
+ // ── GET /schedules with the inference_profile filter ────────────────────────────
1780
+
1781
+ describe("GET /schedules - inference_profile filter", () => {
1782
+ beforeEach(() => {
1783
+ clearTables();
1784
+ });
1785
+
1786
+ test("narrows the list to schedules pinned to that profile", async () => {
1787
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
1788
+ expect(resolvedDefault).not.toBe("cost-optimized");
1789
+
1790
+ await createSchedule({
1791
+ name: "Cheap digest",
1792
+ description: "Cheap digest",
1793
+ cronExpression: "0 * * * *",
1794
+ message: "digest",
1795
+ syntax: "cron",
1796
+ inferenceProfile: "cost-optimized",
1797
+ });
1798
+ await createSchedule({
1799
+ name: "Default schedule",
1800
+ description: "Default schedule",
1801
+ cronExpression: "0 9 * * *",
1802
+ message: "hi",
1803
+ syntax: "cron",
1804
+ });
1805
+
1806
+ const route = findRoute("schedules", "GET");
1807
+ const filtered = (await route.handler({
1808
+ queryParams: { inference_profile: "cost-optimized" },
1809
+ })) as { schedules: Array<{ name: string; inferenceProfile: string }> };
1810
+ expect(filtered.schedules.map((s) => s.name)).toEqual(["Cheap digest"]);
1811
+
1812
+ const unfiltered = (await route.handler({})) as {
1813
+ schedules: Array<{ name: string }>;
1814
+ };
1815
+ expect(unfiltered.schedules).toHaveLength(2);
1816
+ });
1817
+
1818
+ test("flags deferred rows so a caller can count them without naming them", async () => {
1819
+ await createSchedule({
1820
+ name: "Cheap digest",
1821
+ description: "Cheap digest",
1822
+ cronExpression: "0 * * * *",
1823
+ message: "digest",
1824
+ syntax: "cron",
1825
+ inferenceProfile: "cost-optimized",
1826
+ });
1827
+ await createSchedule({
1828
+ name: "Deferred wake",
1829
+ description: "Deferred wake",
1830
+ message: "wake up",
1831
+ nextRunAt: Date.now() + 60_000,
1832
+ mode: "wake",
1833
+ wakeConversationId: "conv-abc",
1834
+ createdBy: "defer",
1835
+ inferenceProfile: "cost-optimized",
1836
+ });
1837
+
1838
+ const route = findRoute("schedules", "GET");
1839
+ const result = (await route.handler({
1840
+ queryParams: { inference_profile: "cost-optimized", include_all: "true" },
1841
+ })) as { schedules: Array<{ name: string; isDeferred: boolean }> };
1842
+
1843
+ // The count a delete warning shows must match what the reassign moves, so
1844
+ // the deferred row has to be visible AND separable from the named ones.
1845
+ expect(result.schedules).toHaveLength(2);
1846
+ expect(
1847
+ result.schedules.filter((s) => !s.isDeferred).map((s) => s.name),
1848
+ ).toEqual(["Cheap digest"]);
1849
+ expect(result.schedules.filter((s) => s.isDeferred)).toHaveLength(1);
1850
+
1851
+ const reassign = findRoute("schedules/reassign-profile", "POST");
1852
+ const moved = (await reassign.handler({
1853
+ body: {
1854
+ from: "cost-optimized",
1855
+ to: resolveDefaultScheduleInferenceProfile()!,
1856
+ },
1857
+ headers: { "x-vellum-principal-type": "local" },
1858
+ })) as { reassigned: number };
1859
+ expect(moved.reassigned).toBe(result.schedules.length);
1860
+ });
1861
+
1862
+ test("returns nothing for a profile no schedule is pinned to", async () => {
1863
+ await createSchedule({
1864
+ name: "Default schedule",
1865
+ description: "Default schedule",
1866
+ cronExpression: "0 9 * * *",
1867
+ message: "hi",
1868
+ syntax: "cron",
1869
+ });
1870
+
1871
+ const route = findRoute("schedules", "GET");
1872
+ const result = (await route.handler({
1873
+ queryParams: { inference_profile: "cost-optimized" },
1874
+ })) as { schedules: unknown[] };
1875
+ expect(result.schedules).toEqual([]);
1876
+ });
1877
+ });
1878
+
1879
+ // ── POST /schedules/reassign-profile ─────────────────────────────────────
1880
+
1881
+ describe("POST /schedules/reassign-profile", () => {
1882
+ beforeEach(() => {
1883
+ clearTables();
1884
+ });
1885
+
1886
+ function reassignRoute(): RouteDefinition {
1887
+ return findRoute("schedules/reassign-profile", "POST");
1888
+ }
1889
+
1890
+ test("moves every schedule pinned to the source profile and leaves the rest alone", async () => {
1891
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
1892
+ expect(resolvedDefault).not.toBe("cost-optimized");
1893
+
1894
+ await createSchedule({
1895
+ name: "Cheap digest",
1896
+ description: "Cheap digest",
1897
+ cronExpression: "0 * * * *",
1898
+ message: "digest",
1899
+ syntax: "cron",
1900
+ inferenceProfile: "cost-optimized",
1901
+ });
1902
+ await createSchedule({
1903
+ name: "Cheap sweep",
1904
+ description: "Cheap sweep",
1905
+ cronExpression: "30 * * * *",
1906
+ message: "sweep",
1907
+ syntax: "cron",
1908
+ inferenceProfile: "cost-optimized",
1909
+ });
1910
+ await createSchedule({
1911
+ name: "Untouched",
1912
+ description: "Untouched",
1913
+ cronExpression: "0 9 * * *",
1914
+ message: "hi",
1915
+ syntax: "cron",
1916
+ });
1917
+
1918
+ const result = (await reassignRoute().handler({
1919
+ body: { from: "cost-optimized", to: resolvedDefault! },
1920
+ })) as { reassigned: number };
1921
+
1922
+ expect(result.reassigned).toBe(2);
1923
+ expect(listSchedules({ inferenceProfile: "cost-optimized" })).toEqual([]);
1924
+ expect(
1925
+ listSchedules()
1926
+ .map((j) => j.inferenceProfile)
1927
+ .every((p) => p === resolvedDefault),
1928
+ ).toBe(true);
1929
+ });
1930
+
1931
+ test("moves deferred wake rows too when the caller holds owner authority", async () => {
1932
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
1933
+
1934
+ await createSchedule({
1935
+ name: "Deferred wake",
1936
+ description: "Deferred wake",
1937
+ message: "wake up",
1938
+ nextRunAt: Date.now() + 60_000,
1939
+ mode: "wake",
1940
+ wakeConversationId: "conv-abc",
1941
+ createdBy: "defer",
1942
+ inferenceProfile: "cost-optimized",
1943
+ });
1944
+
1945
+ const result = (await reassignRoute().handler({
1946
+ body: { from: "cost-optimized", to: resolvedDefault! },
1947
+ headers: { "x-vellum-principal-type": "local" },
1948
+ })) as { reassigned: number };
1949
+
1950
+ expect(result.reassigned).toBe(1);
1951
+ expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
1952
+ });
1953
+
1954
+ test("refuses the whole move when a wake row is in scope and the caller is not the owner", async () => {
1955
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
1956
+ await createSchedule({
1957
+ name: "Cheap digest",
1958
+ description: "Cheap digest",
1959
+ cronExpression: "0 * * * *",
1960
+ message: "digest",
1961
+ syntax: "cron",
1962
+ inferenceProfile: "cost-optimized",
1963
+ });
1964
+ await createSchedule({
1965
+ name: "Deferred wake",
1966
+ description: "Deferred wake",
1967
+ message: "wake up",
1968
+ nextRunAt: Date.now() + 60_000,
1969
+ mode: "wake",
1970
+ wakeConversationId: "conv-abc",
1971
+ createdBy: "defer",
1972
+ inferenceProfile: "cost-optimized",
1973
+ });
1974
+
1975
+ await expect(
1976
+ reassignRoute().handler({
1977
+ body: { from: "cost-optimized", to: resolvedDefault! },
1978
+ headers: { "x-vellum-principal-type": "actor" },
1979
+ }),
1980
+ ).rejects.toThrow(
1981
+ "Deferred wake schedules can only be changed by the assistant's owner",
1982
+ );
1983
+ // All-or-nothing: the ordinary schedule sharing the profile does not move
1984
+ // either.
1985
+ expect(
1986
+ listSchedules().every((j) => j.inferenceProfile === "cost-optimized"),
1987
+ ).toBe(true);
1988
+ });
1989
+
1990
+ test("rejects an unknown target profile without touching any schedule", async () => {
1991
+ await createSchedule({
1992
+ name: "Cheap digest",
1993
+ description: "Cheap digest",
1994
+ cronExpression: "0 * * * *",
1995
+ message: "digest",
1996
+ syntax: "cron",
1997
+ inferenceProfile: "cost-optimized",
1998
+ });
1999
+
2000
+ await expect(
2001
+ reassignRoute().handler({
2002
+ body: { from: "cost-optimized", to: "does-not-exist" },
2003
+ }),
2004
+ ).rejects.toThrow('Inference profile "does-not-exist" is not defined');
2005
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
2006
+ });
2007
+
2008
+ test("requires to, and rejects an empty from", async () => {
2009
+ await expect(
2010
+ reassignRoute().handler({ body: { from: "cost-optimized" } }),
2011
+ ).rejects.toThrow("to is required");
2012
+ await expect(
2013
+ reassignRoute().handler({ body: { from: " ", to: "balanced" } }),
2014
+ ).rejects.toThrow("from must name an inference profile when provided");
2015
+ });
2016
+
2017
+ test("is a no-op when nothing is pinned to the source profile", async () => {
2018
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2019
+ const schedule = await createSchedule({
2020
+ name: "Default schedule",
2021
+ description: "Default schedule",
2022
+ cronExpression: "0 9 * * *",
2023
+ message: "hi",
2024
+ syntax: "cron",
2025
+ });
2026
+ const updatedAtBefore = listSchedules()[0].updatedAt;
2027
+
2028
+ const result = (await reassignRoute().handler({
2029
+ body: { from: "cost-optimized", to: resolvedDefault! },
2030
+ })) as { reassigned: number };
2031
+
2032
+ expect(result.reassigned).toBe(0);
2033
+ expect(listSchedules()[0].id).toBe(schedule.id);
2034
+ expect(listSchedules()[0].updatedAt).toBe(updatedAtBefore);
2035
+ });
2036
+
2037
+ test("sweeps up pins left behind by an already-deleted profile", async () => {
2038
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2039
+ const schedule = await createSchedule({
2040
+ name: "Dangling pin",
2041
+ description: "Dangling pin",
2042
+ cronExpression: "0 9 * * *",
2043
+ message: "hi",
2044
+ syntax: "cron",
2045
+ });
2046
+ // A profile deleted out from under its schedules leaves rows naming a key
2047
+ // that no longer resolves; the sweep must still be able to move them.
2048
+ rawRun(
2049
+ "test:setDanglingInferenceProfile",
2050
+ "UPDATE cron_jobs SET inference_profile = ? WHERE id = ?",
2051
+ "deleted-profile",
2052
+ schedule.id,
2053
+ );
2054
+
2055
+ const result = (await reassignRoute().handler({
2056
+ body: { from: "deleted-profile", to: resolvedDefault! },
2057
+ })) as { reassigned: number };
2058
+
2059
+ expect(result.reassigned).toBe(1);
2060
+ expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
2061
+ });
2062
+
2063
+ test("omitting from moves every schedule and skips the ones already there", async () => {
2064
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2065
+ expect(resolvedDefault).not.toBe("cost-optimized");
2066
+
2067
+ await createSchedule({
2068
+ name: "Cheap digest",
2069
+ description: "Cheap digest",
2070
+ cronExpression: "0 * * * *",
2071
+ message: "digest",
2072
+ syntax: "cron",
2073
+ inferenceProfile: "cost-optimized",
2074
+ });
2075
+ const dangling = await createSchedule({
2076
+ name: "Dangling pin",
2077
+ description: "Dangling pin",
2078
+ cronExpression: "15 * * * *",
2079
+ message: "hi",
2080
+ syntax: "cron",
2081
+ });
2082
+ rawRun(
2083
+ "test:setDanglingInferenceProfile",
2084
+ "UPDATE cron_jobs SET inference_profile = ? WHERE id = ?",
2085
+ "deleted-profile",
2086
+ dangling.id,
2087
+ );
2088
+ // Already on the target: counted as untouched, and its updatedAt proves it.
2089
+ await createSchedule({
2090
+ name: "Already default",
2091
+ description: "Already default",
2092
+ cronExpression: "30 * * * *",
2093
+ message: "hi",
2094
+ syntax: "cron",
2095
+ inferenceProfile: resolvedDefault!,
2096
+ });
2097
+ const untouchedBefore = listSchedules().find(
2098
+ (job) => job.name === "Already default",
2099
+ )!.updatedAt;
2100
+
2101
+ const result = (await reassignRoute().handler({
2102
+ body: { to: resolvedDefault! },
2103
+ })) as { reassigned: number };
2104
+
2105
+ // Two moved (the cheap one and the dangling one); the third was already
2106
+ // on the target, so it is neither counted nor rewritten.
2107
+ expect(result.reassigned).toBe(2);
2108
+ expect(
2109
+ listSchedules().every((job) => job.inferenceProfile === resolvedDefault),
2110
+ ).toBe(true);
2111
+ expect(
2112
+ listSchedules().find((job) => job.name === "Already default")!.updatedAt,
2113
+ ).toBe(untouchedBefore);
2114
+ });
2115
+
2116
+ test("omitting from leaves fired and cancelled one-shots alone", async () => {
2117
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2118
+ expect(resolvedDefault).not.toBe("cost-optimized");
2119
+
2120
+ const fired = await createSchedule({
2121
+ name: "Already fired",
2122
+ description: "Already fired",
2123
+ message: "hi",
2124
+ nextRunAt: Date.now() - 60_000,
2125
+ inferenceProfile: "cost-optimized",
2126
+ });
2127
+ const cancelled = await createSchedule({
2128
+ name: "Cancelled",
2129
+ description: "Cancelled",
2130
+ message: "hi",
2131
+ nextRunAt: Date.now() + 60_000,
2132
+ inferenceProfile: "cost-optimized",
2133
+ });
2134
+ rawRun(
2135
+ "test:setTerminalScheduleStatuses",
2136
+ "UPDATE cron_jobs SET status = CASE id WHEN ? THEN 'fired' ELSE 'cancelled' END WHERE id IN (?, ?)",
2137
+ fired.id,
2138
+ fired.id,
2139
+ cancelled.id,
2140
+ );
2141
+ const live = await createSchedule({
2142
+ name: "Still running",
2143
+ description: "Still running",
2144
+ cronExpression: "0 * * * *",
2145
+ message: "digest",
2146
+ syntax: "cron",
2147
+ inferenceProfile: "cost-optimized",
2148
+ });
2149
+ const byId = new Map(listSchedules().map((job) => [job.id, job]));
2150
+ const firedBefore = byId.get(fired.id)!.updatedAt;
2151
+ const cancelledBefore = byId.get(cancelled.id)!.updatedAt;
2152
+
2153
+ const result = (await reassignRoute().handler({
2154
+ body: { to: resolvedDefault! },
2155
+ })) as { reassigned: number };
2156
+
2157
+ // Their profile is history, so counting them would report a bigger move
2158
+ // than the user was shown.
2159
+ expect(result.reassigned).toBe(1);
2160
+ const after = new Map(listSchedules().map((job) => [job.id, job]));
2161
+ expect(after.get(live.id)!.inferenceProfile).toBe(resolvedDefault!);
2162
+ expect(after.get(fired.id)!.inferenceProfile).toBe("cost-optimized");
2163
+ expect(after.get(cancelled.id)!.inferenceProfile).toBe("cost-optimized");
2164
+ expect(after.get(fired.id)!.updatedAt).toBe(firedBefore);
2165
+ expect(after.get(cancelled.id)!.updatedAt).toBe(cancelledBefore);
2166
+ });
2167
+
2168
+ test("an explicit from still sweeps a fired one-shot's dangling pin", async () => {
2169
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2170
+ const fired = await createSchedule({
2171
+ name: "Already fired",
2172
+ description: "Already fired",
2173
+ message: "hi",
2174
+ nextRunAt: Date.now() - 60_000,
2175
+ inferenceProfile: "cost-optimized",
2176
+ });
2177
+ rawRun(
2178
+ "test:setFiredScheduleStatus",
2179
+ "UPDATE cron_jobs SET status = 'fired' WHERE id = ?",
2180
+ fired.id,
2181
+ );
2182
+
2183
+ // Deleting a profile has to clear every row naming it, including rows that
2184
+ // will never fire again: the pin is what the delete is removing.
2185
+ const result = (await reassignRoute().handler({
2186
+ body: { from: "cost-optimized", to: resolvedDefault! },
2187
+ })) as { reassigned: number };
2188
+
2189
+ expect(result.reassigned).toBe(1);
2190
+ expect(listSchedules()[0].inferenceProfile).toBe(resolvedDefault!);
2191
+ });
2192
+
2193
+ test("omitting from still refuses a non-owner caller when a wake row would move", async () => {
2194
+ const resolvedDefault = resolveDefaultScheduleInferenceProfile();
2195
+ await createSchedule({
2196
+ name: "Deferred wake",
2197
+ description: "Deferred wake",
2198
+ message: "wake up",
2199
+ nextRunAt: Date.now() + 60_000,
2200
+ mode: "wake",
2201
+ wakeConversationId: "conv-abc",
2202
+ createdBy: "defer",
2203
+ inferenceProfile: "cost-optimized",
2204
+ });
2205
+
2206
+ await expect(
2207
+ reassignRoute().handler({
2208
+ body: { to: resolvedDefault! },
2209
+ headers: { "x-vellum-principal-type": "actor" },
2210
+ }),
2211
+ ).rejects.toThrow(
2212
+ "Deferred wake schedules can only be changed by the assistant's owner",
2213
+ );
2214
+ expect(listSchedules()[0].inferenceProfile).toBe("cost-optimized");
2215
+ });
2216
+ });