@vellumai/assistant 0.11.2 → 0.11.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -38,16 +38,24 @@ import type { Message, TextContent } from "../providers/types.js";
38
38
  import { createAbortReason } from "../util/abort-reasons.js";
39
39
  import { getLogger } from "../util/logger.js";
40
40
  import { getSandboxWorkingDir } from "../util/platform.js";
41
+ import { sleep } from "../util/retry.js";
41
42
  import { injectMessageIntoParent } from "./notify.js";
43
+ import { isSubagentProgressEvent } from "./progress-events.js";
42
44
  import {
45
+ DEFAULT_SUBAGENT_ROLE,
46
+ formatSubagentToolStats,
43
47
  normalizeSubagentLabel,
44
48
  settleUnsupervisedStatus,
45
49
  SUBAGENT_LIMITS,
46
50
  SUBAGENT_ROLE_REGISTRY,
47
51
  type SubagentConfig,
52
+ subagentOutputContractText,
48
53
  type SubagentRole,
54
+ type SubagentSpawnMode,
49
55
  type SubagentState,
50
56
  type SubagentStatus,
57
+ type SubagentToolStatsReading,
58
+ type SubagentToolStatsSummary,
51
59
  TERMINAL_STATUSES,
52
60
  } from "./types.js";
53
61
 
@@ -68,6 +76,21 @@ const SWEEP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
68
76
  */
69
77
  const MAX_REHYDRATED_TERMINAL_RECORDS = 200;
70
78
 
79
+ /**
80
+ * How long {@link SubagentManager.settleQueuedTurns} waits for a queued
81
+ * follow-up turn before reporting the subagent as still moving.
82
+ *
83
+ * Sized for the drain handoff, not for the turn itself: the queue is taken
84
+ * some milliseconds before the processing lock, and a wait shorter than that
85
+ * gap would mistake a turn that is starting for one that already finished. A
86
+ * guidance turn that is genuinely mid-flight outlives any budget worth
87
+ * blocking the parent's tool call for, so it is reported as unfinished instead
88
+ * of waited out.
89
+ */
90
+ const QUEUED_TURN_SETTLE_TIMEOUT_MS = 2_000;
91
+ /** Gap between queue observations while a queued turn is waiting to start. */
92
+ const QUEUED_TURN_POLL_MS = 25;
93
+
71
94
  // ── Durable record → state mapping ─────────────────────────────────────
72
95
 
73
96
  /**
@@ -81,6 +104,11 @@ const MAX_REHYDRATED_TERMINAL_RECORDS = 200;
81
104
  * `SubagentConfig` fields that are not persisted (context, prompts, trust,
82
105
  * profile overrides) are absent, so this shape answers lifecycle questions
83
106
  * only.
107
+ *
108
+ * Tool-call counters are in-memory only and the row carries none, so a state
109
+ * built here never has {@link SubagentState.stats}. What that absence means is
110
+ * the manager's answer to give, not this shape's: see
111
+ * {@link SubagentManager.currentToolStats}.
84
112
  */
85
113
  export function subagentStateFromRecord(rec: SubagentRecord): SubagentState {
86
114
  return {
@@ -169,6 +197,18 @@ function extractFinalAssistantText(messages: Message[]): string {
169
197
  return "";
170
198
  }
171
199
 
200
+ /**
201
+ * Snapshot the child conversation's live tool-call counters before the
202
+ * conversation is released. The `filesWritten` Set collapses to its size so
203
+ * nothing keeps a reference into the released conversation.
204
+ */
205
+ function snapshotToolStats(
206
+ conversation: Conversation,
207
+ ): SubagentToolStatsSummary {
208
+ const { calls, succeeded, filesWritten } = conversation.subagentToolStats;
209
+ return { calls, succeeded, filesWritten: filesWritten.size };
210
+ }
211
+
172
212
  /**
173
213
  * Pull the user-visible text out of a streaming delta event, or null for any
174
214
  * other event type. Used by the synchronous `onText` tap to forward
@@ -191,12 +231,15 @@ export function buildSubagentSystemPrompt(
191
231
  role: SubagentRole,
192
232
  ): string {
193
233
  const roleConfig = SUBAGENT_ROLE_REGISTRY[role];
194
- const sections: string[] = [
195
- roleConfig.systemPromptPreamble,
196
- "",
197
- "## Your Task",
198
- config.objective,
199
- ];
234
+ const sections: string[] = [roleConfig.systemPromptPreamble];
235
+ if (config.persona) {
236
+ sections.push(`- Persona: act as ${config.persona} for this task.`);
237
+ }
238
+ const contractText = subagentOutputContractText(config.outputContract);
239
+ if (contractText) {
240
+ sections.push(`- Output contract: ${contractText}`);
241
+ }
242
+ sections.push("", "## Your Task", config.objective);
200
243
  if (config.context) {
201
244
  sections.push("", "## Context from Parent", config.context);
202
245
  }
@@ -207,6 +250,7 @@ export function buildSubagentSystemPrompt(
207
250
  "- You cannot spawn nested subagents.",
208
251
  "- Use notify_parent to report important findings, or if you are blocked.",
209
252
  '- If the objective needs a capability your role\'s tools do not provide (for example, writing or editing a file, or running a command, with a read-only role), do NOT fabricate a completed result — call notify_parent with urgency "blocked", name the capability you lack (e.g. file_write), and stop.',
253
+ "- If a tool call fails, or a tool you expected is unavailable, report the failure verbatim and stop that line of work. Never simulate, reconstruct, or invent tool output you did not actually receive.",
210
254
  );
211
255
  return sections.join("\n");
212
256
  }
@@ -234,6 +278,8 @@ export function buildSubagentTerminalMessage(opts: {
234
278
  deferred?: boolean;
235
279
  /** Tools the subagent attempted but that its role allowlist denied. */
236
280
  deniedTools?: string[];
281
+ /** What the subagent actually ran, harvested when the run ended. */
282
+ stats?: SubagentToolStatsSummary;
237
283
  }): string {
238
284
  const {
239
285
  label,
@@ -245,6 +291,7 @@ export function buildSubagentTerminalMessage(opts: {
245
291
  error,
246
292
  deferred,
247
293
  deniedTools,
294
+ stats,
248
295
  } = opts;
249
296
  const prefix = isFork ? "Fork" : "Subagent";
250
297
 
@@ -254,9 +301,14 @@ export function buildSubagentTerminalMessage(opts: {
254
301
  const many = (deniedTools?.length ?? 0) > 1;
255
302
  const deniedNote =
256
303
  deniedTools && deniedTools.length > 0
257
- ? `\n\nNote: this ${prefix.toLowerCase()} attempted ${deniedTools.join(", ")} but its role does not permit ${many ? "them" : "it"}. If the objective requires ${many ? "those" : "that"}, re-spawn with a role that includes ${many ? "them" : "it"} (e.g. \`coder\`).`
304
+ ? `\n\nNote: this ${prefix.toLowerCase()} attempted ${deniedTools.join(", ")} but its role does not permit ${many ? "them" : "it"}. If the objective requires ${many ? "those" : "that"}, re-spawn with a role that includes ${many ? "them" : "it"} (e.g. \`builder\`).`
258
305
  : "";
259
306
 
307
+ // Machine truth envelope: the counts the parent can check the synthesis
308
+ // above against. Absent when the run never reached the harvest, and on the
309
+ // deferred path, where the counters are still moving (see the caller).
310
+ const statsNote = stats ? `\n\n${formatSubagentToolStats(stats)}` : "";
311
+
260
312
  if (outcome === "failed") {
261
313
  return (
262
314
  `[${prefix} "${label}" failed]\n\n` +
@@ -273,7 +325,8 @@ export function buildSubagentTerminalMessage(opts: {
273
325
  (silent
274
326
  ? `(Use these findings internally; do not relay the raw ${prefix.toLowerCase()} output to the user.)`
275
327
  : `(Incorporate this into your reply to the user as appropriate.)`) +
276
- deniedNote
328
+ deniedNote +
329
+ statsNote
277
330
  );
278
331
  }
279
332
 
@@ -288,7 +341,8 @@ export function buildSubagentTerminalMessage(opts: {
288
341
  `[${prefix} "${label}" completed]\n\n` +
289
342
  `${reason}. Use subagent_read with subagent_id "${subagentId}"${lastN} for the latest output.` +
290
343
  (silent ? ` Keep the result internal.` : ``) +
291
- deniedNote
344
+ deniedNote +
345
+ statsNote
292
346
  );
293
347
  }
294
348
 
@@ -302,6 +356,14 @@ interface ManagedSubagent {
302
356
  parentSendToClient: (msg: AssistantEvent) => void;
303
357
  /** Epoch ms after which this terminal entry can be removed by the TTL sweep. */
304
358
  retainedUntil?: number;
359
+ /**
360
+ * True for an entry the startup rehydration rebuilt from a durable row
361
+ * rather than a run this process executed. Such an entry sits in the manager
362
+ * exactly like a live one but can never have tool-call counters, so
363
+ * {@link SubagentManager.currentToolStats} reads this to tell "never
364
+ * measured here" apart from "not measured yet".
365
+ */
366
+ rehydrated?: boolean;
305
367
  /**
306
368
  * Sticky monotonic flag: set to true when sendMessage enqueues a follow-up
307
369
  * message while a run is in progress, and never cleared. Needed because the
@@ -331,6 +393,15 @@ interface ManagedSubagent {
331
393
  * this callback IN ADDITION to the normal `subagent_event` envelope.
332
394
  */
333
395
  onText?: (chunk: string) => void;
396
+ /**
397
+ * Optional liveness tap for the synchronous path. When set,
398
+ * `wrappedSendToClient` fires it once per event that shows the child is still
399
+ * moving (see {@link isSubagentProgressEvent}), which is a superset of the
400
+ * `onText` chunks: a subagent executing a tool streams no token but is not
401
+ * stalled. Callers that bound a synchronous run by an idle window re-arm it
402
+ * here.
403
+ */
404
+ onProgress?: () => void;
334
405
  }
335
406
 
336
407
  export interface SubagentNotificationInfo {
@@ -413,7 +484,11 @@ export class SubagentManager {
413
484
  private async setUpSubagent(
414
485
  config: Omit<SubagentConfig, "id">,
415
486
  parentSendToClient: (msg: AssistantEvent) => void,
416
- opts?: { synchronous?: boolean; onText?: (chunk: string) => void },
487
+ opts?: {
488
+ synchronous?: boolean;
489
+ onText?: (chunk: string) => void;
490
+ onProgress?: () => void;
491
+ },
417
492
  ): Promise<{ subagentId: string; managed: ManagedSubagent }> {
418
493
  // ── Limit checks ────────────────────────────────────────────────
419
494
 
@@ -428,22 +503,13 @@ export class SubagentManager {
428
503
  }
429
504
 
430
505
  // ── Resolve role ─────────────────────────────────────────────────
506
+ // A role is one of the three types, or absent for an internal caller that
507
+ // states no shape and runs on the default. The `subagent_spawn` tool
508
+ // resolves whatever the model wrote into a type before it gets here (see
509
+ // subagent/role-resolution.ts), so an unregistered value at this point is
510
+ // an internal caller's bug and is worth throwing over.
431
511
  const isFork = config.fork === true;
432
- const role: SubagentRole = (config.role as SubagentRole) ?? "general";
433
- if (isFork && role !== "general") {
434
- // A context-inheriting subagent normally keeps the parent's `general`
435
- // role so its KV cache stays aligned with the parent conversation. An
436
- // explicit non-general role opts out of that alignment on purpose
437
- // (e.g. the advisor role running on a stronger profile), so honor it.
438
- log.warn(
439
- {
440
- requestedRole: role,
441
- parentConversationId: config.parentConversationId,
442
- label: config.label,
443
- },
444
- "Fork requested with non-general role — caller opted out of parent KV-cache alignment",
445
- );
446
- }
512
+ const role: SubagentRole = config.role ?? DEFAULT_SUBAGENT_ROLE;
447
513
  if (!SUBAGENT_ROLE_REGISTRY[role]) {
448
514
  throw new Error(
449
515
  `Invalid subagent role "${config.role}". Must be one of: ${Object.keys(SUBAGENT_ROLE_REGISTRY).join(", ")}`,
@@ -451,14 +517,29 @@ export class SubagentManager {
451
517
  }
452
518
  const roleConfig = SUBAGENT_ROLE_REGISTRY[role];
453
519
 
520
+ // ── Resolve spawn mode ───────────────────────────────────────────
521
+ // The spawning call site is the only layer that can tell an advisor
522
+ // consult or a live-voice continuation apart from a plain fork, so it
523
+ // declares its mode. The fallback is mechanical rather than NULL: a
524
+ // future call site that forgets still records honest context-inheritance
525
+ // shape instead of dropping out of the telemetry breakdown entirely.
526
+ const spawnMode: SubagentSpawnMode =
527
+ config.spawnMode ?? (isFork ? "fork" : "regular");
528
+
454
529
  // ── Create conversation ─────────────────────────────────────────
455
530
  const subagentId = uuid();
531
+ // `subagentRole` / `subagentSpawnMode` are stamped on the conversation
532
+ // row, not just the `subagents` row, because `subagents` rows are deleted
533
+ // on dispose while usage telemetry flushes on a watermark that can trail
534
+ // far behind. See migration 362.
456
535
  const conversationRecord = await bootstrapConversation({
457
536
  conversationType: "background",
458
537
  source: "subagent",
459
538
  origin: "subagent",
460
539
  systemHint: `Subagent: ${config.label}`,
461
540
  parentConversationId: config.parentConversationId,
541
+ subagentRole: role,
542
+ subagentSpawnMode: spawnMode,
462
543
  });
463
544
 
464
545
  // ── Build conversation dependencies ─────────────────────────────
@@ -555,6 +636,7 @@ export class SubagentManager {
555
636
  parentSendToClient,
556
637
  ...(opts?.synchronous ? { synchronous: true } : {}),
557
638
  ...(opts?.onText ? { onText: opts.onText } : {}),
639
+ ...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
558
640
  };
559
641
 
560
642
  // Wrap sendToClient to envelope all events with the subagent ID.
@@ -569,6 +651,11 @@ export class SubagentManager {
569
651
  managed.onText(text);
570
652
  }
571
653
  }
654
+ // Liveness tap, separate from the text tap because tool activity is
655
+ // progress the caller must see and is not a chunk it can forward.
656
+ if (managed.onProgress && isSubagentProgressEvent(msg)) {
657
+ managed.onProgress();
658
+ }
572
659
  managed.parentSendToClient({
573
660
  type: "subagent_event",
574
661
  subagentId,
@@ -589,10 +676,11 @@ export class SubagentManager {
589
676
  // Records the parent at construction; drives isSubagent and notify
590
677
  // routing from non-writable in-process state.
591
678
  parentConversationId: config.parentConversationId,
592
- // The advisor consult runs tool-less for CLIENT tools but should ground
593
- // its guidance with provider-native web search when the resolved
594
- // provider supports it. This is a server tool the provider runs itself,
595
- // so it stays one-shot — no client tool surfaced, allowlist unchanged.
679
+ // The advisor consult is scoped to read-only CLIENT tools and should
680
+ // also ground its guidance with provider-native web search when the
681
+ // resolved provider supports it. This is a server tool the provider
682
+ // runs itself, so no client tool is surfaced and the allowlist is
683
+ // unchanged.
596
684
  // Other roles keep the default (no native search appended).
597
685
  ...(role === "advisor" ? { enableNativeWebSearch: true } : {}),
598
686
  },
@@ -613,10 +701,17 @@ export class SubagentManager {
613
701
  // wins over parent inheritance: a parent that stamps trust per-turn (the
614
702
  // live-voice bridge) has already cleared it by the time a detached spawn
615
703
  // reads it, so its spawner resolves trust itself.
704
+ //
705
+ // Inherit the parent's *turn* trust ahead of its conversation-level slot:
706
+ // the slot holds whichever actor sent most recently, so a spawn during one
707
+ // actor's turn would otherwise run under another's privileges.
708
+ const parentTurnTrust =
709
+ parentConversation?.currentTurnTrustContext ??
710
+ parentConversation?.trustContext;
616
711
  if (config.trustContext) {
617
712
  conversation.setTrustContext({ ...config.trustContext });
618
- } else if (parentConversation?.trustContext) {
619
- conversation.setTrustContext({ ...parentConversation.trustContext });
713
+ } else if (parentTurnTrust) {
714
+ conversation.setTrustContext({ ...parentTurnTrust });
620
715
  }
621
716
  const parentAuthContext = parentConversation?.getAuthContext();
622
717
  if (parentAuthContext) {
@@ -645,18 +740,18 @@ export class SubagentManager {
645
740
  conversation.hasSystemPromptOverride = true;
646
741
  }
647
742
 
648
- // Apply the role's tool allowlist when one is defined. The `general` role
649
- // has `allowedTools: undefined`, so default forks (which keep the general
650
- // role) are unaffected; a fork carrying an explicit role gets its
651
- // allowlist applied like any other subagent.
743
+ // Apply the role's tool allowlist when one is defined. `builder` defines
744
+ // none, so it keeps the full surface its conversation projects; the scoped
745
+ // roles are filtered to their own list whether or not this is a fork.
652
746
  if (roleConfig.allowedTools) {
653
747
  conversation.setSubagentAllowedTools(new Set(roleConfig.allowedTools));
654
748
  }
655
749
 
656
750
  // A read-only subagent refuses side-effecting tools regardless of trust
657
- // class; the executor gate rejects
658
- // any such dispatch and they are kept off the model's tool surface.
659
- if (config.denySideEffectTools) {
751
+ // class; the executor gate rejects any such dispatch and they are kept off
752
+ // the model's tool surface. The role carries the gate so a caller cannot
753
+ // spawn a role past it, and a spawn can still ask for it on top.
754
+ if (config.denySideEffectTools || roleConfig.denySideEffects) {
660
755
  conversation.setSubagentDenySideEffects(true);
661
756
  }
662
757
 
@@ -745,12 +840,20 @@ export class SubagentManager {
745
840
  async spawnAndAwait(
746
841
  config: Omit<SubagentConfig, "id">,
747
842
  parentSendToClient: (msg: AssistantEvent) => void,
748
- opts?: { signal?: AbortSignal; onText?: (chunk: string) => void },
843
+ opts?: {
844
+ signal?: AbortSignal;
845
+ onText?: (chunk: string) => void;
846
+ onProgress?: () => void;
847
+ },
749
848
  ): Promise<string> {
750
849
  const { subagentId, managed } = await this.setUpSubagent(
751
850
  config,
752
851
  parentSendToClient,
753
- { synchronous: true, ...(opts?.onText ? { onText: opts.onText } : {}) },
852
+ {
853
+ synchronous: true,
854
+ ...(opts?.onText ? { onText: opts.onText } : {}),
855
+ ...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
856
+ },
754
857
  );
755
858
 
756
859
  // Wire the external signal to abort the child conversation. If the signal
@@ -863,13 +966,24 @@ export class SubagentManager {
863
966
  // the generic "complete this task and return your findings" wrapper would
864
967
  // fight that framing. The advisor's objective is already the bare advice
865
968
  // request (`advisorRequestText()`), so it is sent uncontested.
969
+ //
970
+ // A fork's persona and output contract ride in this framing rather than
971
+ // the system prompt: the prompt is the parent's, inherited verbatim to
972
+ // keep the KV cache aligned, so the task message is the only place a
973
+ // fork-specific instruction can land.
866
974
  const useForkFraming =
867
975
  managed.state.isFork && managed.state.config.role !== "advisor";
976
+ const forkPersona = managed.state.config.persona;
977
+ const forkContract = subagentOutputContractText(
978
+ managed.state.config.outputContract,
979
+ );
868
980
  const message = useForkFraming
869
981
  ? [
870
982
  "⎯⎯⎯ FORK TASK ⎯⎯⎯",
871
983
  "You have been forked from the parent conversation to execute a specific task.",
872
984
  "The conversation above is context — do NOT continue it. Do NOT spawn sub-agents.",
985
+ ...(forkPersona ? [`Act as ${forkPersona} for this task.`] : []),
986
+ ...(forkContract ? [`Output contract: ${forkContract}`] : []),
873
987
  "Complete this task directly and return only your findings:",
874
988
  "",
875
989
  objective,
@@ -887,6 +1001,11 @@ export class SubagentManager {
887
1001
  ...(managed.state.config.forceOverrideProfile
888
1002
  ? { forceOverrideProfile: true }
889
1003
  : {}),
1004
+ // Stamp the child's usage with the firing that spawned it so schedule
1005
+ // cost reporting sees delegated spend.
1006
+ ...(managed.state.config.cronRunId
1007
+ ? { cronRunId: managed.state.config.cronRunId }
1008
+ : {}),
890
1009
  });
891
1010
 
892
1011
  // Agent loop completed successfully.
@@ -898,6 +1017,12 @@ export class SubagentManager {
898
1017
  const deniedTools = [...conversation.subagentDeniedToolNames];
899
1018
  // Copy usage stats from the conversation before sending status (which includes usage).
900
1019
  managed.state.usage = { ...conversation.usageStats };
1020
+ // Same window for the tool-call counts: the terminal notification below
1021
+ // reads them off the state. This is the first reading, not necessarily
1022
+ // the last, since a follow-up turn queued during the run drains after
1023
+ // this returns and keeps counting into the same conversation. Later
1024
+ // readers go through `currentToolStats` for the settled numbers.
1025
+ managed.state.stats = snapshotToolStats(conversation);
901
1026
  // Only update state + notify if still non-terminal (guards against abort race).
902
1027
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
903
1028
  managed.state.completedAt = Date.now();
@@ -925,6 +1050,7 @@ export class SubagentManager {
925
1050
  // Copy usage from the captured conversation reference — managed.conversation
926
1051
  // may have been nulled by an external dispose() before catch runs.
927
1052
  managed.state.usage = { ...conversation.usageStats };
1053
+ managed.state.stats = snapshotToolStats(conversation);
928
1054
 
929
1055
  // Only update status if not already terminal (e.g. aborted).
930
1056
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
@@ -1167,9 +1293,19 @@ export class SubagentManager {
1167
1293
 
1168
1294
  // ── Send message to subagent ──────────────────────────────────────────
1169
1295
 
1296
+ /**
1297
+ * Deliver a follow-up message to a live subagent.
1298
+ *
1299
+ * `opts.cronRunId` is the firing that produced THIS message, not the one the
1300
+ * subagent was spawned under: a continuation turn's spend belongs to the
1301
+ * firing that asked for it. Only the immediately-processed turn carries it,
1302
+ * since a queued message drains through the conversation's own queue, which
1303
+ * holds no per-message run options.
1304
+ */
1170
1305
  async sendMessage(
1171
1306
  subagentId: string,
1172
1307
  content: string,
1308
+ opts?: { cronRunId?: string | null },
1173
1309
  ): Promise<"sent" | "empty" | "not_found" | "terminal"> {
1174
1310
  const trimmed = content?.trim();
1175
1311
  if (!trimmed) {
@@ -1208,6 +1344,7 @@ export class SubagentManager {
1208
1344
  ...(managed.state.config.forceOverrideProfile
1209
1345
  ? { forceOverrideProfile: true }
1210
1346
  : {}),
1347
+ ...(opts?.cronRunId ? { cronRunId: opts.cronRunId } : {}),
1211
1348
  })
1212
1349
  .catch((err) => {
1213
1350
  log.error({ subagentId, err }, "Subagent message processing failed");
@@ -1222,6 +1359,116 @@ export class SubagentManager {
1222
1359
  return this.subagents.get(subagentId)?.state;
1223
1360
  }
1224
1361
 
1362
+ /**
1363
+ * The subagent's tool-call counters, brought up to date first, or why there
1364
+ * are none (see {@link SubagentToolStatsReading}).
1365
+ *
1366
+ * `runSubagent` harvests when its awaited agent loop returns, but that is not
1367
+ * the end of the child's work: guidance queued during the run drains
1368
+ * afterwards, on the same conversation, and those calls land in the same
1369
+ * counters. So any read taken later re-reads them while the conversation is
1370
+ * still retained (see {@link refreshToolStats}), and the release freezes the
1371
+ * settled numbers. Readers that need the queued turn's calls included wait
1372
+ * for it first, via {@link settleQueuedTurns}.
1373
+ *
1374
+ * An id the manager does not hold is `unrecoverable` rather than unknown:
1375
+ * counters exist nowhere else, so no caller can ever obtain them, and the
1376
+ * only state a caller can be holding for such an id came from the durable
1377
+ * row.
1378
+ */
1379
+ currentToolStats(subagentId: string): SubagentToolStatsReading {
1380
+ const managed = this.subagents.get(subagentId);
1381
+ if (!managed) {
1382
+ return { kind: "unrecoverable" };
1383
+ }
1384
+ this.refreshToolStats(managed);
1385
+ if (managed.state.stats) {
1386
+ return { kind: "counted", stats: managed.state.stats };
1387
+ }
1388
+ return managed.rehydrated
1389
+ ? { kind: "unrecoverable" }
1390
+ : { kind: "unmeasured" };
1391
+ }
1392
+
1393
+ /**
1394
+ * Wait for a follow-up turn queued during the subagent's run to finish.
1395
+ *
1396
+ * `runSubagent` marks the subagent terminal as soon as its own agent loop
1397
+ * returns, and the parent is told to read from there. Guidance queued during
1398
+ * that run drains afterwards though, on the same retained conversation, so a
1399
+ * read taken in that window sees the transcript and the counters from before
1400
+ * the guidance landed and never comes back for the rest. Waiting here closes
1401
+ * the window.
1402
+ *
1403
+ * Resolves `true` once the retained conversation is idle with an empty
1404
+ * queue, and `false` when `timeoutMs` elapses first, so the reader always
1405
+ * gets an answer within a bound and can say the subagent is still moving
1406
+ * rather than pass a partial result off as final.
1407
+ *
1408
+ * `true` comes back immediately when nothing can still be running: no
1409
+ * manager entry, no retained conversation (a released one has its transcript
1410
+ * and counters frozen), or a run that never had anything queued.
1411
+ *
1412
+ * Idle is confirmed across two observations a poll apart. The drain takes
1413
+ * the queue before it takes the processing lock (`drainQueue` shifts the
1414
+ * message, then `drainSingleMessage` awaits slash resolution and the
1415
+ * user-message persist before `runAgentLoop` sets processing), so a single
1416
+ * look into that gap finds an empty queue and an unlocked conversation while
1417
+ * the turn is in fact starting.
1418
+ */
1419
+ async settleQueuedTurns(
1420
+ subagentId: string,
1421
+ timeoutMs: number = QUEUED_TURN_SETTLE_TIMEOUT_MS,
1422
+ ): Promise<boolean> {
1423
+ const managed = this.subagents.get(subagentId);
1424
+ const conversation = managed?.conversation;
1425
+ if (!managed || !conversation || managed.hadEnqueuedMessages !== true) {
1426
+ return true;
1427
+ }
1428
+ const deadline = Date.now() + timeoutMs;
1429
+ let idleObservations = 0;
1430
+ for (;;) {
1431
+ // A release during the wait (the TTL sweep, or a dispose) freezes
1432
+ // everything a reader can see, so there is nothing left to wait for.
1433
+ if (managed.conversation !== conversation) {
1434
+ return true;
1435
+ }
1436
+ const remainingMs = deadline - Date.now();
1437
+ if (remainingMs <= 0) {
1438
+ return false;
1439
+ }
1440
+ if (conversation.isProcessing()) {
1441
+ idleObservations = 0;
1442
+ await conversation.waitForIdle({ timeoutMs: remainingMs });
1443
+ continue;
1444
+ }
1445
+ if (conversation.hasQueuedMessages()) {
1446
+ idleObservations = 0;
1447
+ await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
1448
+ continue;
1449
+ }
1450
+ idleObservations += 1;
1451
+ if (idleObservations >= 2) {
1452
+ return true;
1453
+ }
1454
+ await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
1455
+ }
1456
+ }
1457
+
1458
+ /**
1459
+ * Re-read the child conversation's live counters into its state.
1460
+ *
1461
+ * Only ever updates a harvest that already happened: a run that never
1462
+ * reached its harvest (aborted before the first turn, or still going) has
1463
+ * nothing measured to report, and a zero taken mid-flight would read as
1464
+ * "this subagent used no tools" rather than "not measured yet".
1465
+ */
1466
+ private refreshToolStats(managed: ManagedSubagent): void {
1467
+ if (managed.conversation && managed.state.stats) {
1468
+ managed.state.stats = snapshotToolStats(managed.conversation);
1469
+ }
1470
+ }
1471
+
1225
1472
  getByLabel(
1226
1473
  label: string,
1227
1474
  parentConversationId: string,
@@ -1296,6 +1543,10 @@ export class SubagentManager {
1296
1543
  if (!managed.conversation) {
1297
1544
  return;
1298
1545
  }
1546
+ // Last chance at the counters: on the deferred path this release happens
1547
+ // after the queued follow-up turn drained, so this is the reading that
1548
+ // includes it. Everything read after this point is this snapshot.
1549
+ this.refreshToolStats(managed);
1299
1550
  const conversation = managed.conversation;
1300
1551
  removeSubagentConversation(conversation.conversationId, conversation);
1301
1552
  conversation.dispose();
@@ -1388,7 +1639,7 @@ export class SubagentManager {
1388
1639
  conversationId: state.conversationId,
1389
1640
  label: state.config.label,
1390
1641
  objective: state.config.objective,
1391
- role: state.config.role ?? "general",
1642
+ role: state.config.role ?? DEFAULT_SUBAGENT_ROLE,
1392
1643
  isFork: state.isFork,
1393
1644
  sendResultToUser: state.config.sendResultToUser ?? null,
1394
1645
  parentToolUseId: state.config.parentToolUseId ?? null,
@@ -1454,6 +1705,7 @@ export class SubagentManager {
1454
1705
  state,
1455
1706
  parentSendToClient: () => {},
1456
1707
  retainedUntil: now + TERMINAL_RETENTION_MS,
1708
+ rehydrated: true,
1457
1709
  };
1458
1710
  this.subagents.set(rec.id, managed);
1459
1711
 
@@ -1609,6 +1861,10 @@ export class SubagentManager {
1609
1861
  ? config.sendResultToUser !== true
1610
1862
  : config.sendResultToUser === false;
1611
1863
 
1864
+ // A queued follow-up turn means the snapshot we hold is stale; defer to a
1865
+ // read pointer so the parent picks up the queued turn's output instead.
1866
+ const deferred = managed.hadEnqueuedMessages === true;
1867
+
1612
1868
  const message = buildSubagentTerminalMessage({
1613
1869
  label: config.label,
1614
1870
  subagentId: config.id,
@@ -1617,10 +1873,14 @@ export class SubagentManager {
1617
1873
  silent,
1618
1874
  finalText,
1619
1875
  error: managed.state.error,
1620
- // A queued follow-up turn means the snapshot we hold is stale; defer to a
1621
- // read pointer so the parent picks up the queued turn's output instead.
1622
- deferred: managed.hadEnqueuedMessages === true,
1876
+ deferred,
1623
1877
  deniedTools,
1878
+ // Same staleness applies to the counters, and worse: the queued turn has
1879
+ // not run yet at this point, so any number quoted here would under-report
1880
+ // it, permanently, in a message that is never rewritten. The deferred
1881
+ // message sends the parent to `subagent_read`, whose footer re-reads the
1882
+ // counters once the queued turn has actually landed.
1883
+ ...(deferred ? {} : { stats: managed.state.stats }),
1624
1884
  });
1625
1885
 
1626
1886
  const notification: SubagentNotificationInfo = {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Classify subagent events as evidence that a child run is still moving.
3
+ *
4
+ * A synchronous caller bounds its wait with an idle window, and the only thing
5
+ * that resets that window is a progress signal. Streamed tokens are the obvious
6
+ * one, but a subagent that calls a tool goes silent for the whole execution
7
+ * while producing no delta, so tool activity counts as well: the call being
8
+ * recognized, its input streaming in, its output streaming out, and its result
9
+ * landing. Without it, a healthy child reading a large file looks identical to
10
+ * a stalled one and is aborted before the model ever sees its tool result.
11
+ *
12
+ * Its own module so the classification can be unit-tested (and imported by a
13
+ * deadline test) without pulling in the manager and its Conversation graph.
14
+ */
15
+
16
+ import type { AssistantEvent } from "../api/index.js";
17
+
18
+ const PROGRESS_EVENT_TYPES: ReadonlySet<string> = new Set([
19
+ "assistant_text_delta",
20
+ "assistant_thinking_delta",
21
+ "tool_use_preview_start",
22
+ "tool_use_start",
23
+ "tool_input_delta",
24
+ "tool_output_chunk",
25
+ "tool_result",
26
+ ]);
27
+
28
+ /**
29
+ * Whether an event is evidence of forward progress in a child run. Pure, and
30
+ * deliberately an allowlist: a lifecycle or status event says nothing about
31
+ * whether work is happening, so an unknown event type never extends a deadline.
32
+ */
33
+ export function isSubagentProgressEvent(msg: AssistantEvent): boolean {
34
+ return PROGRESS_EVENT_TYPES.has(msg.type);
35
+ }