@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
@@ -8,12 +8,18 @@ import {
8
8
  } from "../security/untrusted-content.js";
9
9
  import { getLogger } from "../util/logger.js";
10
10
  import { isLexicalBackfillComplete } from "./checkpoints.js";
11
+ import { unseenAttentionStateConditions } from "./conversation-attention-store.js";
11
12
  import type { ConversationRow } from "./conversation-crud.js";
12
13
  import { parseConversation } from "./conversation-crud.js";
13
14
  import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
14
15
  import { ensureGroupMigration } from "./conversation-group-migration.js";
15
16
  import { searchMessageIdsLexical } from "./conversation-search-lexical.js";
16
- import type { ConversationType } from "./conversation-types.js";
17
+ import {
18
+ type ConversationType,
19
+ NATIVE_ORIGIN_CHANNEL,
20
+ PINNED_GROUP_ID,
21
+ UNGROUPED_GROUP_ID,
22
+ } from "./conversation-types.js";
17
23
  import { getDb } from "./db-connection.js";
18
24
  import { tokenize } from "./embeddings/sparse-tokenize.js";
19
25
  import {
@@ -21,7 +27,11 @@ import {
21
27
  resolveMessageContentBlocks,
22
28
  } from "./message-content-file.js";
23
29
  import { rawAll } from "./raw-query.js";
24
- import { conversations, messages } from "./schema/index.js";
30
+ import {
31
+ conversationAssistantAttentionState,
32
+ conversations,
33
+ messages,
34
+ } from "./schema/index.js";
25
35
 
26
36
  const log = getLogger("conversation-store");
27
37
 
@@ -169,6 +179,26 @@ function surfacedVisibilitySql(alias = "conversations"): string {
169
179
  );
170
180
  }
171
181
 
182
+ /**
183
+ * Raw SQL predicate for "not an automated background/scheduled row".
184
+ *
185
+ * A background or scheduled row counts as foreground only once it has been
186
+ * explicitly surfaced. {@link standardListingVisibilitySql} already admits
187
+ * such rows through its surfaced and custom-group arms, so a caller that
188
+ * must distinguish "visible in the listing" from "user-facing foreground"
189
+ * (unread counting) applies this on top.
190
+ *
191
+ * The SQL twin of `isBackgroundConversation` in the web client's
192
+ * `utils/conversation-predicates.ts`.
193
+ */
194
+ function notBackgroundVisibilitySql(alias = "conversations"): string {
195
+ return (
196
+ `NOT ((${alias}.conversation_type IN ('background', 'scheduled')` +
197
+ ` OR COALESCE(${alias}.group_id, 'system:all') IN ('system:background', 'system:scheduled'))` +
198
+ ` AND ${alias}.surfaced_at IS NULL)`
199
+ );
200
+ }
201
+
172
202
  /**
173
203
  * SQL predicate selecting which bucket {@link listConversations} and
174
204
  * {@link countConversations} return, keyed by the canonical
@@ -211,34 +241,133 @@ function conversationTypeClause(type: ConversationType) {
211
241
  }
212
242
  }
213
243
 
244
+ /**
245
+ * The predicates that select which conversations a listing covers, shared by
246
+ * {@link listConversations} and {@link countConversations} so a list and its
247
+ * count can never disagree about what they are describing.
248
+ *
249
+ * Named rather than positional on purpose: `originChannel` and `groupId` are
250
+ * both optional strings, and as adjacent positional arguments they were a
251
+ * transposition waiting to happen.
252
+ */
253
+ export interface ConversationListFilter {
254
+ conversationType?: ConversationType;
255
+ archiveStatus?: ArchiveStatusFilter;
256
+ originChannel?: string;
257
+ /**
258
+ * Restrict to one group. {@link UNGROUPED_GROUP_ID} selects rows in no
259
+ * group (the sidebar's flat list), {@link PINNED_GROUP_ID} the Pinned
260
+ * section, and a UUID one custom group. Omit to span every group.
261
+ */
262
+ groupId?: string;
263
+ }
264
+
265
+ export interface ConversationListQuery extends ConversationListFilter {
266
+ limit?: number;
267
+ offset?: number;
268
+ }
269
+
270
+ /**
271
+ * SQL predicate for {@link ConversationListFilter.groupId}.
272
+ *
273
+ * The ungrouped case has to match NULL as well as the literal sentinel:
274
+ * `group_id` is only written when a conversation is filed somewhere, so the
275
+ * overwhelming majority of rows carry NULL rather than `'system:all'`.
276
+ *
277
+ * `group_id` is referenced as raw SQL because it is not a Drizzle column:
278
+ * it, `display_order`, and `is_pinned` are added by the lazy
279
+ * `ensureDisplayOrderMigration` / `ensureGroupMigration` steps rather than
280
+ * declared in `schema/conversations.ts`.
281
+ */
282
+ function groupIdClause(groupId: string) {
283
+ if (groupId === UNGROUPED_GROUP_ID) {
284
+ // "Ungrouped" means every row the sidebar's flat list shows: not pinned
285
+ // and not filed in a custom group. It deliberately admits the system
286
+ // buckets, because a surfaced background or scheduled conversation keeps
287
+ // its `system:background` / `system:scheduled` group id (surfacing writes
288
+ // only `surfaced_at`) while the standard listing renders it in Recents.
289
+ // Whether such a row is actually visible stays with
290
+ // `conversationTypeClause`, which admits the surfaced ones and excludes
291
+ // the rest; matching on group id alone here would drop them.
292
+ return sql`(group_id IS NULL OR (group_id LIKE 'system:%' AND group_id != ${PINNED_GROUP_ID}))`;
293
+ }
294
+ // `group_id` is single-valued and authoritative, so a row belongs to
295
+ // exactly one section by construction. `is_pinned` is a derived duplicate
296
+ // that reads do not consult: `ensureGroupMigration` backfills
297
+ // `group_id = 'system:pinned'` for every legacy `is_pinned` row before any
298
+ // query runs, and pinning writes both together thereafter.
299
+ return sql`group_id = ${groupId}`;
300
+ }
301
+
302
+ /**
303
+ * SQL predicate for {@link ConversationListFilter.originChannel}.
304
+ *
305
+ * `'vellum'` additionally matches NULL. `origin_channel` is left NULL at
306
+ * insert precisely so an inbound message can still claim the conversation for
307
+ * its channel (`setConversationOriginChannelIfUnset` is guarded on `isNull`),
308
+ * and migration 288 settles whatever was never claimed to `'vellum'` at daemon
309
+ * startup. NULL is therefore "not yet attributed", and it is what the
310
+ * overwhelming majority of rows carry between one boot and the next.
311
+ *
312
+ * A strict equality would put those rows in no section at all: not native, not
313
+ * any channel. Reading NULL as native is the self-correcting error of the two,
314
+ * since the only rows it can misplace are ones whose inbound message has not
315
+ * arrived yet, and that message moves them the moment it does.
316
+ *
317
+ * Every other channel matches exactly. A row is claimed for a channel only by
318
+ * that channel, so there is no ambiguity to be tolerant about.
319
+ */
320
+ function originChannelClause(originChannel: string) {
321
+ if (originChannel === NATIVE_ORIGIN_CHANNEL) {
322
+ return sql`(origin_channel IS NULL OR origin_channel = ${NATIVE_ORIGIN_CHANNEL})`;
323
+ }
324
+ return eq(conversations.originChannel, originChannel);
325
+ }
326
+
327
+ function conversationListWhere(filter: ConversationListFilter) {
328
+ const {
329
+ conversationType = "standard",
330
+ archiveStatus = "active",
331
+ originChannel,
332
+ groupId,
333
+ } = filter;
334
+ return and(
335
+ conversationTypeClause(conversationType),
336
+ archiveStatusClause(archiveStatus) ?? undefined,
337
+ originChannel ? originChannelClause(originChannel) : undefined,
338
+ groupId ? groupIdClause(groupId) : undefined,
339
+ );
340
+ }
341
+
214
342
  export function listConversations(
215
- limit?: number,
216
- conversationType: ConversationType = "standard",
217
- offset = 0,
218
- archiveStatus: ArchiveStatusFilter = "active",
219
- originChannel?: string,
343
+ query: ConversationListQuery = {},
220
344
  ): ConversationRow[] {
221
345
  ensureDisplayOrderMigration();
222
346
  ensureGroupMigration();
223
347
  const db = getDb();
224
- const typeCond = conversationTypeClause(conversationType);
225
- const archiveCond = archiveStatusClause(archiveStatus);
226
- const channelCond = originChannel
227
- ? eq(conversations.originChannel, originChannel)
228
- : undefined;
229
- const where = and(typeCond, archiveCond ?? undefined, channelCond);
230
- const query = db
348
+ const { limit, offset = 0, ...filter } = query;
349
+ const recency = desc(
350
+ sql`COALESCE(${conversations.lastMessageAt}, ${conversations.updatedAt})`,
351
+ );
352
+ // `id` closes the ordering so the sort is a total order. Rows tied on every
353
+ // preceding key would otherwise have no defined relative order, which
354
+ // becomes duplicated and skipped rows once a cursor pages across a tie
355
+ // boundary.
356
+ //
357
+ // Deliberately untested here: SQLite returns a stable arrangement for
358
+ // identical queries over identical data, so no assertion at this layer can
359
+ // distinguish a total order from a lucky one. The guarantee becomes
360
+ // observable, and gets its test, with the keyset pagination work.
361
+ const tiebreak = desc(conversations.id);
362
+ return db
231
363
  .select()
232
364
  .from(conversations)
233
- .where(where)
234
- .orderBy(
235
- desc(
236
- sql`COALESCE(${conversations.lastMessageAt}, ${conversations.updatedAt})`,
237
- ),
238
- )
365
+ .where(conversationListWhere(filter))
366
+ .orderBy(recency, tiebreak)
239
367
  .limit(limit ?? 100)
240
- .offset(offset);
241
- return query.all().map(parseConversation);
368
+ .offset(offset)
369
+ .all()
370
+ .map(parseConversation);
242
371
  }
243
372
 
244
373
  /**
@@ -360,7 +489,6 @@ export function listPinnedConversations(
360
489
  ),
361
490
  )
362
491
  .orderBy(
363
- sql`COALESCE(display_order, 999999) ASC`,
364
492
  desc(
365
493
  sql`COALESCE(${conversations.lastMessageAt}, ${conversations.updatedAt})`,
366
494
  ),
@@ -422,23 +550,69 @@ export function listConversationsByTitlePrefix(
422
550
  }));
423
551
  }
424
552
 
553
+ /**
554
+ * How many conversations {@link listConversations} would return for the same
555
+ * filter, ignoring `limit` / `offset`. Both read through
556
+ * {@link conversationListWhere}, so a page and its total always describe the
557
+ * same set.
558
+ */
425
559
  export function countConversations(
426
- conversationType: ConversationType = "standard",
427
- archiveStatus: ArchiveStatusFilter = "active",
428
- originChannel?: string,
560
+ filter: ConversationListFilter = {},
429
561
  ): number {
430
562
  ensureGroupMigration();
431
563
  const db = getDb();
432
- const typeCond = conversationTypeClause(conversationType);
433
- const archiveCond = archiveStatusClause(archiveStatus);
434
- const channelCond = originChannel
435
- ? eq(conversations.originChannel, originChannel)
436
- : undefined;
437
- const where = and(typeCond, archiveCond ?? undefined, channelCond);
438
564
  const [{ total }] = db
439
565
  .select({ total: count() })
440
566
  .from(conversations)
441
- .where(where)
567
+ .where(conversationListWhere(filter))
568
+ .all();
569
+ return total;
570
+ }
571
+
572
+ /**
573
+ * Count of foreground conversations whose latest assistant message is unseen.
574
+ *
575
+ * The server-side twin of the web client's `contributesToUnreadCount`
576
+ * predicate (`clients/web/src/utils/conversation-predicates.ts`), so the
577
+ * count returned by `GET /v1/conversations/unread-count` always matches what
578
+ * the sidebar's unread indicators would sum to over the full list.
579
+ *
580
+ * **Two definitions of one rule.** Changing what counts here means changing
581
+ * the client predicate too. Both sides assert the same named scenario matrix
582
+ * ("unread-count contract") so a one-sided change is visible in review: see
583
+ * `__tests__/conversation-list-routes.test.ts` here and
584
+ * `conversation-predicates.test.ts` there. The duplication exists only to
585
+ * keep a fallback for assistants without this route, and goes away with it.
586
+ *
587
+ * The rules:
588
+ *
589
+ * - visible in the standard (Recents) listing ({@link conversationTypeClause}),
590
+ * - not archived,
591
+ * - not an unsurfaced background/scheduled row
592
+ * ({@link notBackgroundVisibilitySql}), which excludes automated rows the
593
+ * listing admits through its custom-group arm,
594
+ * - unseen per the attention projection
595
+ * ({@link unseenAttentionStateConditions}); conversations without a
596
+ * projection row read as seen, hence the inner join.
597
+ */
598
+ export function countUnreadConversations(): number {
599
+ ensureGroupMigration();
600
+ const db = getDb();
601
+ const [{ total }] = db
602
+ .select({ total: count() })
603
+ .from(conversations)
604
+ .innerJoin(
605
+ conversationAssistantAttentionState,
606
+ eq(conversationAssistantAttentionState.conversationId, conversations.id),
607
+ )
608
+ .where(
609
+ and(
610
+ conversationTypeClause("standard"),
611
+ isNull(conversations.archivedAt),
612
+ sql.raw(notBackgroundVisibilitySql()),
613
+ ...unseenAttentionStateConditions(),
614
+ ),
615
+ )
442
616
  .all();
443
617
  return total;
444
618
  }
@@ -7,7 +7,32 @@
7
7
  // `resolveConversationKind` classifier, and the pure predicates over a
8
8
  // persisted message's `metadata` record.
9
9
 
10
- import { parseChannelId } from "../channels/types.js";
10
+ import {
11
+ type ChannelId,
12
+ parseChannelId,
13
+ parseClientOs,
14
+ } from "../channels/types.js";
15
+
16
+ /**
17
+ * Where a conversation came from, stated by whoever creates it.
18
+ *
19
+ * `conversations.origin_channel` records this. It is the caller's job to
20
+ * supply it, because the caller is the only party that knows: an inbound
21
+ * Slack message creates a conversation *because* it is Slack, and nothing
22
+ * downstream can recover that fact once the moment has passed.
23
+ *
24
+ * A bare {@link ChannelId} covers the cases where the origin is known
25
+ * outright, including `"vellum"` for a conversation started in the app.
26
+ * `inheritFrom` covers the derived ones, where the conversation belongs to
27
+ * whatever surface its parent belongs to: forks, subagent spawns, and
28
+ * scheduled wakes.
29
+ *
30
+ * Deliberately closed, and deliberately not optional. An optional string
31
+ * would let a caller decline to answer, which is how the column came to
32
+ * carry a third "not yet attributed" state that different readers resolve
33
+ * in contradictory directions. See JARVIS-1466.
34
+ */
35
+ export type ConversationOrigin = ChannelId | { readonly inheritFrom: string };
11
36
 
12
37
  /**
13
38
  * Canonical `conversations.source` string for background memory v2
@@ -24,6 +49,15 @@ export type ConversationCreateType = "standard" | "background" | "scheduled";
24
49
  /** Read-side alias of {@link ConversationCreateType}. */
25
50
  export type ConversationType = ConversationCreateType;
26
51
 
52
+ /**
53
+ * Conversation types minted outside the schedule pipeline. Scheduled rows are
54
+ * owned by it and created through `createConversation`.
55
+ */
56
+ export type NonScheduledConversationType = Exclude<
57
+ ConversationCreateType,
58
+ "scheduled"
59
+ >;
60
+
27
61
  /**
28
62
  * Conversation types created by background machinery (heartbeat runs,
29
63
  * scheduled runs, retrospective forks) rather than by a person. Exported as a
@@ -128,6 +162,29 @@ export function isEchoSuppressedUserMessage(
128
162
  );
129
163
  }
130
164
 
165
+ /**
166
+ * True when a queued message has no client-visible queued row, so every event
167
+ * and snapshot that describes the queue to clients must skip it.
168
+ *
169
+ * Same set as {@link isEchoSuppressedUserMessage}: a row that never renders as
170
+ * a user bubble must not render as a queued one either. A subagent's
171
+ * completion notification injected into a busy parent (`subagent/notify.ts`)
172
+ * is the common case — the user sees that run through its inline card, and a
173
+ * `[Subagent "…" — …]` row in the queue drawer is daemon scaffolding leaking
174
+ * into the transcript's place.
175
+ *
176
+ * Named separately from the echo predicate for the queue sites that must
177
+ * agree: the `message_queued` ack and its visible-position count, the
178
+ * corrective `message_requeued` position, the terminal
179
+ * `message_queued_deleted` on user delete and on abort discard, and the
180
+ * list-messages queued snapshot a cold reload reads.
181
+ */
182
+ export function isSuppressedQueuedMessage(
183
+ metadata: Record<string, unknown> | undefined,
184
+ ): boolean {
185
+ return isEchoSuppressedUserMessage(metadata);
186
+ }
187
+
131
188
  /**
132
189
  * True when a role-`"user"` row was persisted by the voice bridge for a live
133
190
  * phone call or in-app voice session (every row `startVoiceTurn` persists, see
@@ -148,6 +205,34 @@ export function isVoiceSessionUserMessage(
148
205
  return metadata?.voiceSessionTurn === true;
149
206
  }
150
207
 
208
+ /**
209
+ * True when the row that opened the turn was sent from the macOS app, on that
210
+ * row's own evidence.
211
+ *
212
+ * Two markers, both required. The `client` bag's `os` entry is the only
213
+ * per-platform attribution on a message: `userMessageInterface` is `"web"` for
214
+ * the macOS app, the iOS app, and a desktop browser alike. `clientOsFromRequest`
215
+ * says that `os` was reported by this row's request or transport rather than
216
+ * inherited from the conversation's live client state, which names the surface
217
+ * of an earlier turn: a button tapped on the phone against a conversation last
218
+ * sent to from the Mac persists `os: "macos"` with no marker.
219
+ *
220
+ * Origin a row did not report itself is origin unknown. Callers gate
221
+ * suppression on this, so unknown has to read as not-macOS.
222
+ */
223
+ export function isMacOriginatedUserMessage(
224
+ metadata: Record<string, unknown> | undefined,
225
+ ): boolean {
226
+ if (metadata?.clientOsFromRequest !== true) {
227
+ return false;
228
+ }
229
+ const client = metadata.client;
230
+ if (typeof client !== "object" || client === null) {
231
+ return false;
232
+ }
233
+ return parseClientOs((client as Record<string, unknown>).os) === "macos";
234
+ }
235
+
151
236
  /**
152
237
  * True when the row that opened the turn arrived over an external messaging
153
238
  * surface (Slack, Telegram, WhatsApp, email, a phone call) rather than the
@@ -220,3 +305,24 @@ export function isReplyPushIneligibleUserMessage(
220
305
  (!options?.replyDeliveredInAppOnly && isReplyDeliveredOffApp(metadata))
221
306
  );
222
307
  }
308
+
309
+ /**
310
+ * The reserved `group_id` values the sidebar's system sections use.
311
+ *
312
+ * `group_id` is nullable, and a conversation that has never been filed
313
+ * carries NULL rather than {@link UNGROUPED_GROUP_ID}, so predicates over the
314
+ * column read it through `COALESCE(group_id, 'system:all')`.
315
+ */
316
+ export const UNGROUPED_GROUP_ID = "system:all";
317
+ export const PINNED_GROUP_ID = "system:pinned";
318
+
319
+ /**
320
+ * The `origin_channel` value for a conversation started in Vellum itself
321
+ * rather than arriving from an external channel.
322
+ *
323
+ * Named because reads have to treat it as "vellum or not yet attributed":
324
+ * `origin_channel` is deliberately NULL at insert so an inbound message can
325
+ * claim the conversation for its own channel, and migration 288 settles the
326
+ * unclaimed ones to this value at daemon startup.
327
+ */
328
+ export const NATIVE_ORIGIN_CHANNEL = "vellum";
@@ -794,10 +794,19 @@ export function claimMemoryJobs(
794
794
 
795
795
  export function completeMemoryJob(id: string): void {
796
796
  const db = memoryDb();
797
+ // Guarded on `running` so a late return from an abandoned execution cannot
798
+ // overwrite a terminal state another writer already recorded (the stalled-job
799
+ // watchdog's `failed`, or a successor worker's re-claimed row).
797
800
  db.update(memoryJobs)
798
801
  .set({ status: "completed", updatedAt: Date.now(), lastError: null })
799
- .where(eq(memoryJobs.id, id))
802
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
800
803
  .run();
804
+ if (rawMemoryChanges() === 0) {
805
+ log.warn(
806
+ { jobId: id },
807
+ "completeMemoryJob: row was not running; late completion suppressed",
808
+ );
809
+ }
801
810
  }
802
811
 
803
812
  /** Max times a job can be deferred before it is marked as failed. */
@@ -820,7 +829,17 @@ const DEFER_MAX_DELAY_MS = 5 * 60 * 1000;
820
829
  * Returns `'deferred'` if the job was put back, or `'failed'` if max deferrals
821
830
  * were exceeded and the job was marked as failed.
822
831
  */
823
- export function deferMemoryJob(id: string): "deferred" | "failed" {
832
+ export function deferMemoryJob(
833
+ id: string,
834
+ options?: {
835
+ /**
836
+ * Terminal `last_error` recorded when the deferral budget is exhausted.
837
+ * Defaults to the backend-unavailable wording used by the worker's
838
+ * infrastructure deferrals.
839
+ */
840
+ exhaustedMessage?: string;
841
+ },
842
+ ): "deferred" | "failed" {
824
843
  const db = memoryDb();
825
844
  const row = db.select().from(memoryJobs).where(eq(memoryJobs.id, id)).get();
826
845
  if (!row) {
@@ -840,9 +859,11 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
840
859
  status: "failed",
841
860
  deferrals,
842
861
  updatedAt: now,
843
- lastError: `Backend unavailable after ${deferrals} deferrals`,
862
+ lastError:
863
+ options?.exhaustedMessage ??
864
+ `Backend unavailable after ${deferrals} deferrals`,
844
865
  })
845
- .where(eq(memoryJobs.id, id))
866
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
846
867
  .run();
847
868
  return "failed";
848
869
  }
@@ -868,7 +889,7 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
868
889
  runAfter: now + delay,
869
890
  updatedAt: now,
870
891
  })
871
- .where(eq(memoryJobs.id, id))
892
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
872
893
  .run();
873
894
  return "deferred";
874
895
  }
@@ -886,7 +907,7 @@ export function rescheduleMemoryJob(id: string, delayMs: number): void {
886
907
  memoryDb()
887
908
  .update(memoryJobs)
888
909
  .set({ status: "pending", runAfter: now + delayMs, updatedAt: now })
889
- .where(eq(memoryJobs.id, id))
910
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
890
911
  .run();
891
912
  }
892
913
 
@@ -904,6 +925,9 @@ export function failMemoryJob(
904
925
  }
905
926
  const attempts = row.attempts + 1;
906
927
  const now = Date.now();
928
+ // Both transitions are guarded on `running` for the same reason as
929
+ // `completeMemoryJob`: a late writer must not overwrite a terminal state
930
+ // recorded by the stalled-job watchdog or a successor worker.
907
931
  if (attempts >= maxAttempts) {
908
932
  db.update(memoryJobs)
909
933
  .set({
@@ -912,7 +936,7 @@ export function failMemoryJob(
912
936
  updatedAt: now,
913
937
  lastError: truncate(error, 2000, ""),
914
938
  })
915
- .where(eq(memoryJobs.id, id))
939
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
916
940
  .run();
917
941
  return;
918
942
  }
@@ -924,7 +948,7 @@ export function failMemoryJob(
924
948
  updatedAt: now,
925
949
  lastError: truncate(error, 2000, ""),
926
950
  })
927
- .where(eq(memoryJobs.id, id))
951
+ .where(and(eq(memoryJobs.id, id), eq(memoryJobs.status, "running")))
928
952
  .run();
929
953
  }
930
954
 
@@ -258,6 +258,21 @@ export interface UnreportedUsageEvent extends UsageEvent {
258
258
  * Null when there's no parent conversation.
259
259
  */
260
260
  parentTurnIndex: number | null;
261
+ /**
262
+ * Role of the subagent that owns this LLM call's conversation, from
263
+ * `SUBAGENT_ROLE_REGISTRY`. Null for every LLM call that did not run inside
264
+ * a subagent conversation, and for subagent conversations created before
265
+ * migration 362 whose `subagents` row had already been disposed.
266
+ */
267
+ subagentRole: string | null;
268
+ /**
269
+ * How that subagent was spawned, one of the modes described on
270
+ * `SubagentSpawnMode` in `subagent/types.ts`. Orthogonal to `subagentRole`:
271
+ * the role selects the child's capabilities, the spawn mode selects its
272
+ * context inheritance and lifecycle. Null under the same conditions as
273
+ * `subagentRole`.
274
+ */
275
+ subagentSpawnMode: string | null;
261
276
  }
262
277
 
263
278
  export function queryUnreportedUsageEvents(
@@ -357,6 +372,11 @@ export function queryUnreportedUsageEvents(
357
372
  // the cutoff (child creation for subagent spawns, fork boundary
358
373
  // message for retrospective forks). Same eligibility filter as
359
374
  // `turnIndex` above.
375
+ // Stamped on the conversation row at spawn (migration 362) rather than
376
+ // joined from `subagents`, whose rows are deleted on dispose while this
377
+ // watermark query can trail far behind after an ingest outage.
378
+ subagentRole: conversations.subagentRole,
379
+ subagentSpawnMode: conversations.subagentSpawnMode,
360
380
  parentTurnIndex: sql<number | null>`(
361
381
  CASE WHEN ${parentIdSql} IS NULL THEN NULL
362
382
  ELSE (
@@ -399,6 +419,8 @@ export function queryUnreportedUsageEvents(
399
419
  parentConversationId: row.parentConversationId,
400
420
  parentTurnIndex:
401
421
  row.parentTurnIndex === null ? null : Number(row.parentTurnIndex),
422
+ subagentRole: row.subagentRole,
423
+ subagentSpawnMode: row.subagentSpawnMode,
402
424
  }));
403
425
  }
404
426