@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,7 +1,19 @@
1
1
  import { Cron } from "croner";
2
- import { and, asc, desc, eq, inArray, isNull, lt, lte, sql } from "drizzle-orm";
2
+ import {
3
+ and,
4
+ asc,
5
+ desc,
6
+ eq,
7
+ inArray,
8
+ isNotNull,
9
+ isNull,
10
+ lt,
11
+ lte,
12
+ sql,
13
+ } from "drizzle-orm";
3
14
  import { v4 as uuid } from "uuid";
4
15
 
16
+ import { getConversation } from "../persistence/conversation-crud.js";
5
17
  import { getDb } from "../persistence/db-connection.js";
6
18
  import { getGroup } from "../persistence/group-crud.js";
7
19
  import { isLifecycleQuiesced } from "../persistence/lifecycle-quiesce.js";
@@ -17,6 +29,11 @@ import {
17
29
  LEGACY_DEFER_CREATED_BY,
18
30
  OWNER_DEFER_CREATED_BY,
19
31
  } from "./defer-provenance.js";
32
+ import {
33
+ resolveDefaultScheduleInferenceProfile,
34
+ resolveWakeScheduleInferenceProfile,
35
+ } from "./inference-profile.js";
36
+ import { declarationExistsOnDisk } from "./plugin-schedule-declarations.js";
20
37
  import {
21
38
  computeNextRunAt as computeNextRunAtEngine,
22
39
  isValidScheduleExpression,
@@ -77,7 +94,11 @@ export interface ScheduleJob {
77
94
  timeoutMs: number | null;
78
95
  /**
79
96
  * Inference profile (`llm.profiles` key) applied to the schedule's
80
- * LLM-executed runs; null = default main-agent model selection.
97
+ * LLM-executed runs. Pinned at creation from the caller's choice or a
98
+ * snapshot of the resolved default, so the schedule's model (and cost) does
99
+ * not move when the global default changes. Null only where no named profile
100
+ * resolves at all, in which case runs follow the `mainAgent` call-site
101
+ * configuration.
81
102
  */
82
103
  inferenceProfile: string | null;
83
104
  /**
@@ -89,6 +110,24 @@ export interface ScheduleJob {
89
110
  groupId: string | null;
90
111
  createdFromConversationId: string | null;
91
112
  createdBy: string;
113
+ /**
114
+ * Plugin declaration this row was reconciled from
115
+ * (`plugin:<pluginName>/<scheduleName>`); null = imperative schedule.
116
+ * Sourced rows are read-only through {@link updateSchedule} and
117
+ * {@link deleteSchedule}: the declaration file is the source of truth for
118
+ * definition columns, and the reconciler is their only writer.
119
+ */
120
+ sourceKey: string | null;
121
+ /**
122
+ * Reconciler change detector over the plugin's declaration files; null on
123
+ * imperative rows.
124
+ */
125
+ definitionHash: string | null;
126
+ /**
127
+ * User enable/disable override for a sourced row; null = the declaration's
128
+ * own enabled value applies. Always null on imperative rows.
129
+ */
130
+ userEnabled: boolean | null;
92
131
  mode: ScheduleMode;
93
132
  routingIntent: RoutingIntent;
94
133
  routingHints: Record<string, unknown>;
@@ -156,6 +195,8 @@ interface InsertScheduleParams {
156
195
  inferenceProfile?: string | null;
157
196
  groupId?: string | null;
158
197
  createdFromConversationId?: string | null;
198
+ sourceKey?: string | null;
199
+ definitionHash?: string | null;
159
200
  }
160
201
 
161
202
  /**
@@ -172,11 +213,36 @@ export type OrdinaryScheduleCreator =
172
213
  | "user"
173
214
  | typeof LEGACY_DEFER_CREATED_BY;
174
215
 
175
- /** Parameters for ordinary schedule creation. */
176
- export type CreateScheduleParams = Omit<InsertScheduleParams, "createdBy"> & {
216
+ /**
217
+ * Parameters for ordinary schedule creation. `sourceKey` and `definitionHash`
218
+ * are excluded for the same structural reason `createdBy` is constrained:
219
+ * plugin-declaration provenance is minted only by
220
+ * {@link upsertDeclaredSchedule}, so an ordinary create cannot produce a row
221
+ * the reconciler believes it owns.
222
+ */
223
+ export type CreateScheduleParams = Omit<
224
+ InsertScheduleParams,
225
+ "createdBy" | "sourceKey" | "definitionHash"
226
+ > & {
177
227
  createdBy?: OrdinaryScheduleCreator;
178
228
  };
179
229
 
230
+ /**
231
+ * Timezone stored for a recurring schedule. A wall-clock expression with no
232
+ * zone otherwise evaluates in the host clock (UTC on managed containers), so
233
+ * it defaults to the user's configured/detected zone; expressions that carry
234
+ * their own zone keep the caller's value verbatim.
235
+ */
236
+ function resolveStoredTimezone(
237
+ syntax: ScheduleSyntax,
238
+ expression: string | null,
239
+ timezone: string | null | undefined,
240
+ ): string | null {
241
+ return expressionCarriesOwnTimezone(syntax, expression)
242
+ ? (timezone ?? null)
243
+ : resolveScheduleTimezone(timezone);
244
+ }
245
+
180
246
  async function insertSchedule(
181
247
  params: InsertScheduleParams,
182
248
  ): Promise<ScheduleJob> {
@@ -206,14 +272,10 @@ async function insertSchedule(
206
272
  const id = uuid();
207
273
  const now = Date.now();
208
274
  const enabled = params.enabled ?? true;
209
- // A recurring wall-clock schedule with no zone otherwise evaluates in the host
210
- // clock (UTC on managed containers). Default it to the user's configured/detected
211
- // zone so it fires at the intended local hour. One-shot schedules (absolute epoch)
212
- // and expressions that carry their own zone keep the caller's value verbatim.
213
- const timezone =
214
- isOneShot || expressionCarriesOwnTimezone(syntax, expression)
215
- ? (params.timezone ?? null)
216
- : resolveScheduleTimezone(params.timezone);
275
+ // One-shot schedules (absolute epoch) keep the caller's value verbatim.
276
+ const timezone = isOneShot
277
+ ? (params.timezone ?? null)
278
+ : resolveStoredTimezone(syntax, expression, params.timezone);
217
279
  const mode = params.mode ?? "execute";
218
280
  const routingIntent = params.routingIntent ?? "all_channels";
219
281
  const routingHints = params.routingHints ?? {};
@@ -222,7 +284,25 @@ async function insertSchedule(
222
284
  const maxRetries = params.maxRetries ?? 3;
223
285
  const retryBackoffMs = params.retryBackoffMs ?? 60000;
224
286
  const timeoutMs = params.timeoutMs ?? null;
225
- const inferenceProfile = params.inferenceProfile ?? null;
287
+ // The single chokepoint that pins every schedule to a concrete profile: a
288
+ // caller that names none gets a snapshot of what that row resolves today, so
289
+ // the schedule's cost stays put when the user changes their global default
290
+ // profile.
291
+ //
292
+ // A wake row snapshots its target conversation's durable pin instead of the
293
+ // global default, because `buildWakeScheduleOptions` forces the row's pin as
294
+ // the woken turn's override: an unpinned wake resolves the target's own
295
+ // choice live, so seeding anything else would silently re-point it. Seeding
296
+ // here rather than in `createOwnerDeferredWake` makes the rule structural —
297
+ // it holds for every wake row however it was minted — and matches what
298
+ // migration 363 backfills onto the rows that predate the pin.
299
+ const inferenceProfile =
300
+ params.inferenceProfile ??
301
+ (mode === "wake"
302
+ ? resolveWakeScheduleInferenceProfile(
303
+ getConversation(params.wakeConversationId!) ?? null,
304
+ )
305
+ : resolveDefaultScheduleInferenceProfile());
226
306
  const groupId = params.groupId ?? null;
227
307
  const createdFromConversationId = params.createdFromConversationId ?? null;
228
308
  const description = normalizeDescription(
@@ -268,6 +348,9 @@ async function insertSchedule(
268
348
  groupId,
269
349
  createdFromConversationId,
270
350
  createdBy: params.createdBy ?? "agent",
351
+ sourceKey: params.sourceKey ?? null,
352
+ definitionHash: params.definitionHash ?? null,
353
+ userEnabled: null as boolean | null,
271
354
  mode,
272
355
  routingIntent,
273
356
  routingHintsJson: JSON.stringify(routingHints),
@@ -320,6 +403,15 @@ export async function createSchedule(
320
403
  *
321
404
  * Callers must have established owner authority first; `defer/create` is the
322
405
  * only production caller and is gated accordingly.
406
+ *
407
+ * The pin seeds from the source conversation's durable choice rather than the
408
+ * global default. A defer resumes the very conversation it was created in, and
409
+ * `buildWakeScheduleOptions` forces the row's pin as the woken turn's
410
+ * override, so a defer created inside a conversation the user pinned to a
411
+ * specific profile must fire on that profile. That seeding lives in
412
+ * `insertSchedule`, keyed on `mode: "wake"` rather than on defer provenance,
413
+ * so no wake row can be minted that overrides its target's pin with the global
414
+ * default.
323
415
  */
324
416
  export async function createOwnerDeferredWake(params: {
325
417
  conversationId: string;
@@ -337,9 +429,7 @@ export async function createOwnerDeferredWake(params: {
337
429
  createdBy: OWNER_DEFER_CREATED_BY,
338
430
  nextRunAt: params.fireAt,
339
431
  quiet: true,
340
- ...(params.inferenceProfile !== undefined
341
- ? { inferenceProfile: params.inferenceProfile }
342
- : {}),
432
+ inferenceProfile: params.inferenceProfile ?? null,
343
433
  });
344
434
  }
345
435
 
@@ -390,6 +480,7 @@ export function listSchedules(options?: {
390
480
  mode?: ScheduleMode;
391
481
  createdBy?: string | readonly string[];
392
482
  conversationId?: string;
483
+ inferenceProfile?: string;
393
484
  }): ScheduleJob[] {
394
485
  const db = getDb();
395
486
  const conditions = [];
@@ -418,6 +509,11 @@ export function listSchedules(options?: {
418
509
  eq(scheduleJobs.wakeConversationId, options.conversationId),
419
510
  );
420
511
  }
512
+ if (options?.inferenceProfile) {
513
+ conditions.push(
514
+ eq(scheduleJobs.inferenceProfile, options.inferenceProfile),
515
+ );
516
+ }
421
517
  const where = conditions.length > 0 ? and(...conditions) : undefined;
422
518
  const rows = db
423
519
  .select()
@@ -478,6 +574,16 @@ export async function updateSchedule(
478
574
  return null;
479
575
  }
480
576
 
577
+ // Plugin-sourced rows are read-only here: the plugin's schedule file is the
578
+ // source of truth for every definition column, and the reconciler would
579
+ // overwrite any edit on its next pass. Enable/disable goes through
580
+ // `setUserEnabled`; every other change is an edit to the declaration file.
581
+ if (existing.sourceKey != null) {
582
+ throw new UserError(
583
+ "This schedule is managed by a plugin. Edit the plugin's schedule file instead.",
584
+ );
585
+ }
586
+
481
587
  // Owner-defer provenance certifies that the assistant's owner chose both the
482
588
  // conversation this wake resumes and the text it carries. Rewriting either
483
589
  // would leave the marker standing over content it does not describe, so on
@@ -610,7 +716,12 @@ export async function updateSchedule(
610
716
  set.timeoutMs = updates.timeoutMs;
611
717
  }
612
718
  if (updates.inferenceProfile !== undefined) {
613
- set.inferenceProfile = updates.inferenceProfile;
719
+ // Null re-snapshots the currently resolved default instead of unpinning:
720
+ // "follow whatever my global default happens to be" is not a state a
721
+ // schedule can rest in, since it would let a later default change move the
722
+ // schedule's model and price without the user touching the schedule.
723
+ set.inferenceProfile =
724
+ updates.inferenceProfile ?? resolveDefaultScheduleInferenceProfile();
614
725
  }
615
726
  if (updates.groupId !== undefined) {
616
727
  set.groupId = updates.groupId;
@@ -642,8 +753,27 @@ export async function updateSchedule(
642
753
  return getSchedule(id);
643
754
  }
644
755
 
756
+ /** `source_key` of the row, or null when the row is absent or imperative. */
757
+ function getRowSourceKey(id: string): string | null {
758
+ const row = getDb()
759
+ .select({ sourceKey: scheduleJobs.sourceKey })
760
+ .from(scheduleJobs)
761
+ .where(eq(scheduleJobs.id, id))
762
+ .get();
763
+ return row?.sourceKey ?? null;
764
+ }
765
+
645
766
  export async function deleteSchedule(id: string): Promise<boolean> {
646
767
  const db = getDb();
768
+ // Plugin-sourced rows keep their identity and run history: deleting one
769
+ // would just have the reconciler recreate it from the declaration on its
770
+ // next pass. Disable is the supported action; removal means removing the
771
+ // plugin's schedule file.
772
+ if (getRowSourceKey(id) != null) {
773
+ throw new UserError(
774
+ "This schedule is managed by a plugin and cannot be deleted. Disable it instead, or remove the plugin's schedule file.",
775
+ );
776
+ }
647
777
  // Capture rawChanges() inside the awaited closure: reading it after the
648
778
  // await would race other async DB work on the shared connection.
649
779
  const deleted = await withSqliteRetry(
@@ -659,6 +789,296 @@ export async function deleteSchedule(id: string): Promise<boolean> {
659
789
  return deleted;
660
790
  }
661
791
 
792
+ // ── Plugin-declared schedules ───────────────────────────────────────
793
+ //
794
+ // Rows with a non-null `source_key` mirror a declaration in a plugin's
795
+ // `schedules/` directory. The reconciler owns their definition columns (via
796
+ // the functions below, the only writers of `source_key`, `definition_hash`,
797
+ // and the definition side of `enabled`), the engine owns the runtime columns,
798
+ // and the user owns `user_enabled`. Rows with a null `source_key` are
799
+ // imperative schedules and none of this section touches them.
800
+
801
+ /**
802
+ * Definition of a plugin-declared schedule, as parsed from the plugin's
803
+ * declaration files. `enabled` is the declaration's own value; the stored
804
+ * row's `enabled` is the effective value, `user_enabled ?? enabled`.
805
+ */
806
+ export interface DeclaredScheduleDefinition {
807
+ name: string;
808
+ description?: string;
809
+ syntax: ScheduleSyntax;
810
+ expression: string;
811
+ timezone?: string | null;
812
+ message: string;
813
+ script?: string | null;
814
+ mode: Extract<ScheduleMode, "execute" | "script">;
815
+ maxRetries?: number;
816
+ retryBackoffMs?: number;
817
+ quiet?: boolean;
818
+ inferenceProfile?: string | null;
819
+ timeoutMs?: number | null;
820
+ enabled: boolean;
821
+ definitionHash: string;
822
+ }
823
+
824
+ /** All plugin-sourced schedule rows, including disarmed ones. */
825
+ export function listDeclaredSchedules(): ScheduleJob[] {
826
+ const db = getDb();
827
+ const rows = db
828
+ .select()
829
+ .from(scheduleJobs)
830
+ .where(isNotNull(scheduleJobs.sourceKey))
831
+ .orderBy(asc(scheduleJobs.nextRunAt))
832
+ .all();
833
+ return rows.map(parseJobRow);
834
+ }
835
+
836
+ /**
837
+ * True when the engine has latched the row: a fired or cancelled one-shot, or
838
+ * a recurrence the claim path exhausted. The exhaust path stamps `lastRunAt`
839
+ * as it writes `nextRunAt = 0` and disables the row, so `nextRunAt = 0` with
840
+ * a null `lastRunAt` is a declared schedule inserted disabled, not an engine
841
+ * latch, and stays re-armable.
842
+ */
843
+ function isEngineLatched(row: {
844
+ status: string;
845
+ nextRunAt: number;
846
+ lastRunAt: number | null;
847
+ }): boolean {
848
+ return (
849
+ row.status === "fired" ||
850
+ row.status === "cancelled" ||
851
+ (row.nextRunAt === 0 && row.lastRunAt != null)
852
+ );
853
+ }
854
+
855
+ /**
856
+ * Insert or update the row for a plugin declaration, keyed by `sourceKey`.
857
+ *
858
+ * Effective `enabled` (`user_enabled ?? definition.enabled`) is recomputed on
859
+ * every call, so a row disarmed while its plugin was disabled re-arms once
860
+ * the declaration is back in the desired set. `nextRunAt` is recomputed only
861
+ * when the timing changed or the row transitions to enabled; a matching hash
862
+ * with matching script path and effective `enabled` is a no-op. A script
863
+ * path that moved on a matching hash (the invocation embeds the absolute
864
+ * entrypoint path, which moves with the workspace while the hash covers only
865
+ * schedules/-relative paths and contents) is rewritten without touching
866
+ * timing. Rows the engine has latched are returned untouched.
867
+ */
868
+ export async function upsertDeclaredSchedule(
869
+ sourceKey: string,
870
+ definition: DeclaredScheduleDefinition,
871
+ ): Promise<ScheduleJob> {
872
+ const db = getDb();
873
+ const existing = db
874
+ .select()
875
+ .from(scheduleJobs)
876
+ .where(eq(scheduleJobs.sourceKey, sourceKey))
877
+ .get();
878
+
879
+ if (!existing) {
880
+ return insertSchedule({ ...definition, createdBy: "agent", sourceKey });
881
+ }
882
+
883
+ if (isEngineLatched(existing)) {
884
+ return parseJobRow(existing);
885
+ }
886
+
887
+ // Same timezone defaulting as insert, so change detection compares the
888
+ // value that would actually be stored.
889
+ const timezone = resolveStoredTimezone(
890
+ definition.syntax,
891
+ definition.expression,
892
+ definition.timezone,
893
+ );
894
+
895
+ const definitionChanged =
896
+ existing.definitionHash !== definition.definitionHash;
897
+ const scriptChanged = (definition.script ?? null) !== existing.script;
898
+ const effectiveEnabled = existing.userEnabled ?? definition.enabled;
899
+ if (
900
+ !definitionChanged &&
901
+ !scriptChanged &&
902
+ effectiveEnabled === existing.enabled
903
+ ) {
904
+ return parseJobRow(existing);
905
+ }
906
+
907
+ const spec = {
908
+ syntax: definition.syntax,
909
+ expression: definition.expression,
910
+ timezone,
911
+ };
912
+ if (definitionChanged && !isValidScheduleExpression(spec)) {
913
+ throw new Error(
914
+ `Invalid ${definition.syntax} expression: "${definition.expression}"`,
915
+ );
916
+ }
917
+
918
+ const timingChanged =
919
+ definition.expression !== existing.cronExpression ||
920
+ definition.syntax !== existing.scheduleSyntax ||
921
+ timezone !== existing.timezone;
922
+
923
+ const set: Record<string, unknown> = {
924
+ enabled: effectiveEnabled,
925
+ updatedAt: Date.now(),
926
+ };
927
+ if (definitionChanged) {
928
+ set.name = definition.name;
929
+ set.description = normalizeDescription(
930
+ definition.description,
931
+ definition.name,
932
+ );
933
+ set.cronExpression = definition.expression;
934
+ set.scheduleSyntax = definition.syntax;
935
+ set.timezone = timezone;
936
+ set.message = definition.message;
937
+ set.script = definition.script ?? null;
938
+ set.mode = definition.mode;
939
+ set.maxRetries = definition.maxRetries ?? 3;
940
+ set.retryBackoffMs = definition.retryBackoffMs ?? 60000;
941
+ set.quiet = definition.quiet ?? false;
942
+ set.inferenceProfile = definition.inferenceProfile ?? null;
943
+ set.timeoutMs = definition.timeoutMs ?? null;
944
+ set.definitionHash = definition.definitionHash;
945
+ } else if (scriptChanged) {
946
+ set.script = definition.script ?? null;
947
+ }
948
+ // While disabled, `nextRunAt` is left alone so a stale value never reads as
949
+ // the engine's exhaust latch (`nextRunAt = 0` with `lastRunAt` set).
950
+ if (
951
+ effectiveEnabled &&
952
+ (timingChanged || !existing.enabled || existing.nextRunAt === 0)
953
+ ) {
954
+ set.nextRunAt = computeNextRunAtEngine(spec);
955
+ }
956
+
957
+ await withSqliteRetry(
958
+ () =>
959
+ db
960
+ .update(scheduleJobs)
961
+ .set(set)
962
+ .where(eq(scheduleJobs.id, existing.id))
963
+ .run(),
964
+ { op: "upsertDeclaredSchedule", context: { scheduleId: existing.id } },
965
+ );
966
+ notifySchedulesChanged();
967
+
968
+ const updated = getSchedule(existing.id);
969
+ if (!updated) {
970
+ throw new Error(`Declared schedule ${existing.id} vanished during upsert`);
971
+ }
972
+ return updated;
973
+ }
974
+
975
+ /**
976
+ * Pause a sourced row whose declaration is gone or whose plugin is disabled.
977
+ * The row and its runs are kept so a reinstall re-links by `source_key` and
978
+ * `user_enabled` survives. `nextRunAt` is left alone: a disabled row never
979
+ * fires, and `nextRunAt = 0` is reserved for the engine's exhaust latch.
980
+ *
981
+ * Resolves `true` when this call took an armed row off, and `false` when
982
+ * there was nothing to do because the row is gone or already disabled. A
983
+ * caller reporting the pause to the user needs that distinction: a row the
984
+ * user or the engine already turned off is not something this call paused.
985
+ */
986
+ export async function disarmDeclaredSchedule(id: string): Promise<boolean> {
987
+ const db = getDb();
988
+ const existing = db
989
+ .select()
990
+ .from(scheduleJobs)
991
+ .where(eq(scheduleJobs.id, id))
992
+ .get();
993
+ if (!existing) {
994
+ return false;
995
+ }
996
+ if (existing.sourceKey == null) {
997
+ throw new Error(
998
+ "disarmDeclaredSchedule applies only to plugin-sourced schedules",
999
+ );
1000
+ }
1001
+ if (!existing.enabled) {
1002
+ return false;
1003
+ }
1004
+ await withSqliteRetry(
1005
+ () =>
1006
+ db
1007
+ .update(scheduleJobs)
1008
+ .set({ enabled: false, updatedAt: Date.now() })
1009
+ .where(eq(scheduleJobs.id, id))
1010
+ .run(),
1011
+ { op: "disarmDeclaredSchedule", context: { scheduleId: id } },
1012
+ );
1013
+ notifySchedulesChanged();
1014
+ return true;
1015
+ }
1016
+
1017
+ /**
1018
+ * The user's side of the enabled toggle.
1019
+ *
1020
+ * On an imperative row this is exactly the existing toggle
1021
+ * (`updateSchedule(id, { enabled })`) and `user_enabled` stays null. On a
1022
+ * plugin-sourced row it records the override in `user_enabled` and applies
1023
+ * it to the effective `enabled` on the spot. Engine-latched rows record the
1024
+ * override without being re-armed.
1025
+ */
1026
+ export async function setUserEnabled(
1027
+ id: string,
1028
+ value: boolean,
1029
+ ): Promise<ScheduleJob | null> {
1030
+ const db = getDb();
1031
+ const existing = db
1032
+ .select()
1033
+ .from(scheduleJobs)
1034
+ .where(eq(scheduleJobs.id, id))
1035
+ .get();
1036
+ if (!existing) {
1037
+ return null;
1038
+ }
1039
+ if (existing.sourceKey == null) {
1040
+ return updateSchedule(id, { enabled: value });
1041
+ }
1042
+
1043
+ const set: Record<string, unknown> = {
1044
+ userEnabled: value,
1045
+ updatedAt: Date.now(),
1046
+ };
1047
+ if (value !== existing.enabled && !isEngineLatched(existing)) {
1048
+ // Enabling a row whose declaration is no longer sourceable (plugin
1049
+ // uninstalled or disabled, manifest broken, schedule file removed) would
1050
+ // let it fire an orphaned run before the next reconcile pass disarms it
1051
+ // again. The reconciler is the authority on the declaration set; this
1052
+ // probe only closes that fire-before-next-sweep window. The override
1053
+ // itself is still recorded, so it applies if the declaration returns.
1054
+ if (value && !(await declarationExistsOnDisk(existing.sourceKey))) {
1055
+ logger.info(
1056
+ { scheduleId: id, sourceKey: existing.sourceKey },
1057
+ "Enable override recorded without re-arming: declaration missing on disk",
1058
+ );
1059
+ } else {
1060
+ set.enabled = value;
1061
+ // Re-arming needs a fresh occurrence: the stored one went stale while
1062
+ // the row was disabled. Disabling keeps `nextRunAt`; see the disarm
1063
+ // note.
1064
+ if (value && existing.cronExpression != null) {
1065
+ set.nextRunAt = computeNextRunAtEngine({
1066
+ syntax: existing.scheduleSyntax as ScheduleSyntax,
1067
+ expression: existing.cronExpression,
1068
+ timezone: existing.timezone,
1069
+ });
1070
+ }
1071
+ }
1072
+ }
1073
+
1074
+ await withSqliteRetry(
1075
+ () => db.update(scheduleJobs).set(set).where(eq(scheduleJobs.id, id)).run(),
1076
+ { op: "setUserEnabled", context: { scheduleId: id } },
1077
+ );
1078
+ notifySchedulesChanged();
1079
+ return getSchedule(id);
1080
+ }
1081
+
662
1082
  /**
663
1083
  * Claim due schedules atomically. Handles both recurring and one-shot schedules.
664
1084
  *
@@ -930,6 +1350,15 @@ export async function failOneShotPermanently(id: string): Promise<void> {
930
1350
  */
931
1351
  export async function cancelSchedule(id: string): Promise<boolean> {
932
1352
  const db = getDb();
1353
+ // Cancelled is a permanent latch on a sourced row: the reconciler, the
1354
+ // enabled toggle, and delete all skip it, so cancelling would brick the
1355
+ // plugin's schedule even across reinstalls. Disable is the supported
1356
+ // action, as with update and delete.
1357
+ if (getRowSourceKey(id) != null) {
1358
+ throw new UserError(
1359
+ "This schedule is managed by a plugin and cannot be cancelled. Disable it instead via the enabled toggle.",
1360
+ );
1361
+ }
933
1362
  const now = Date.now();
934
1363
  const cancelled = await withSqliteRetry(
935
1364
  () => {
@@ -1017,10 +1446,15 @@ export async function setScheduleRunConversationId(
1017
1446
  );
1018
1447
  }
1019
1448
 
1020
- export async function completeScheduleRun(
1449
+ /**
1450
+ * Close out a run row, leaving the parent job alone. Returns the job the run
1451
+ * belongs to and the timestamp the row was finished at, or null when the run
1452
+ * no longer exists.
1453
+ */
1454
+ async function finishScheduleRunRow(
1021
1455
  runId: string,
1022
1456
  result: { status: "ok" | "error"; output?: string; error?: string },
1023
- ): Promise<void> {
1457
+ ): Promise<{ jobId: string; now: number } | null> {
1024
1458
  const db = getDb();
1025
1459
  const now = Date.now();
1026
1460
 
@@ -1030,7 +1464,7 @@ export async function completeScheduleRun(
1030
1464
  .where(eq(scheduleRuns.id, runId))
1031
1465
  .get();
1032
1466
  if (!run) {
1033
- return;
1467
+ return null;
1034
1468
  }
1035
1469
 
1036
1470
  const durationMs = now - run.startedAt;
@@ -1048,16 +1482,73 @@ export async function completeScheduleRun(
1048
1482
  })
1049
1483
  .where(eq(scheduleRuns.id, runId))
1050
1484
  .run(),
1051
- { op: "completeScheduleRun.run", context: { runId } },
1485
+ { op: "finishScheduleRunRow", context: { runId } },
1052
1486
  );
1053
1487
 
1488
+ return { jobId: run.jobId, now };
1489
+ }
1490
+
1491
+ /**
1492
+ * Record a run the scheduler declined to start, without charging it to the
1493
+ * schedule's retry budget.
1494
+ *
1495
+ * An administrative skip (the plugin behind a sourced row is disabled,
1496
+ * uninstalled, or broken) is not a failed attempt: nothing ran, so there is
1497
+ * nothing to retry. `retryCount` is therefore left where it is, and no retry
1498
+ * is scheduled, which leaves a recurring row on the next occurrence its claim
1499
+ * advanced it to rather than on retry-backoff cadence. `lastStatus` still
1500
+ * moves to `error` and the run row is still written, so the skip is visible
1501
+ * in the schedule's history.
1502
+ */
1503
+ export async function recordAdministrativeSkipRun(
1504
+ jobId: string,
1505
+ reason: string,
1506
+ ): Promise<void> {
1507
+ const db = getDb();
1508
+ const runId = await createScheduleRun(jobId, null);
1509
+ const finished = await finishScheduleRunRow(runId, {
1510
+ status: "error",
1511
+ error: reason,
1512
+ });
1513
+ if (!finished) {
1514
+ return;
1515
+ }
1516
+ const changed = await withSqliteRetry(
1517
+ () => {
1518
+ db.update(scheduleJobs)
1519
+ .set({ lastStatus: "error", updatedAt: finished.now })
1520
+ .where(eq(scheduleJobs.id, jobId))
1521
+ .run();
1522
+ return rawChanges() > 0;
1523
+ },
1524
+ {
1525
+ op: "recordAdministrativeSkipRun.job",
1526
+ context: { scheduleId: jobId },
1527
+ },
1528
+ );
1529
+ if (changed) {
1530
+ notifySchedulesChanged();
1531
+ }
1532
+ }
1533
+
1534
+ export async function completeScheduleRun(
1535
+ runId: string,
1536
+ result: { status: "ok" | "error"; output?: string; error?: string },
1537
+ ): Promise<void> {
1538
+ const db = getDb();
1539
+ const finished = await finishScheduleRunRow(runId, result);
1540
+ if (!finished) {
1541
+ return;
1542
+ }
1543
+ const { jobId, now } = finished;
1544
+
1054
1545
  // Update the parent job's lastStatus and retryCount
1055
1546
  if (result.status === "error") {
1056
1547
  // Increment retry count
1057
1548
  const job = db
1058
1549
  .select()
1059
1550
  .from(scheduleJobs)
1060
- .where(eq(scheduleJobs.id, run.jobId))
1551
+ .where(eq(scheduleJobs.id, jobId))
1061
1552
  .get();
1062
1553
  if (job) {
1063
1554
  const changed = await withSqliteRetry(
@@ -1068,13 +1559,13 @@ export async function completeScheduleRun(
1068
1559
  retryCount: job.retryCount + 1,
1069
1560
  updatedAt: now,
1070
1561
  })
1071
- .where(eq(scheduleJobs.id, run.jobId))
1562
+ .where(eq(scheduleJobs.id, jobId))
1072
1563
  .run();
1073
1564
  return rawChanges() > 0;
1074
1565
  },
1075
1566
  {
1076
1567
  op: "completeScheduleRun.jobError",
1077
- context: { scheduleId: run.jobId },
1568
+ context: { scheduleId: jobId },
1078
1569
  },
1079
1570
  );
1080
1571
  if (changed) {
@@ -1086,11 +1577,11 @@ export async function completeScheduleRun(
1086
1577
  () => {
1087
1578
  db.update(scheduleJobs)
1088
1579
  .set({ lastStatus: "ok", retryCount: 0, updatedAt: now })
1089
- .where(eq(scheduleJobs.id, run.jobId))
1580
+ .where(eq(scheduleJobs.id, jobId))
1090
1581
  .run();
1091
1582
  return rawChanges() > 0;
1092
1583
  },
1093
- { op: "completeScheduleRun.jobOk", context: { scheduleId: run.jobId } },
1584
+ { op: "completeScheduleRun.jobOk", context: { scheduleId: jobId } },
1094
1585
  );
1095
1586
  if (changed) {
1096
1587
  notifySchedulesChanged();
@@ -1537,6 +2028,9 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
1537
2028
  groupId: row.groupId ?? null,
1538
2029
  createdFromConversationId: row.createdFromConversationId ?? null,
1539
2030
  createdBy: row.createdBy,
2031
+ sourceKey: row.sourceKey ?? null,
2032
+ definitionHash: row.definitionHash ?? null,
2033
+ userEnabled: row.userEnabled ?? null,
1540
2034
  mode: (row.mode ?? "execute") as ScheduleMode,
1541
2035
  routingIntent: (row.routingIntent ?? "all_channels") as RoutingIntent,
1542
2036
  routingHints: safeParseJson(row.routingHintsJson),