@vellumai/assistant 0.11.2 → 0.11.3-staging.1

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 (709) 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-queue.test.ts +265 -1
  72. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  73. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  74. package/src/__tests__/conversation-store.test.ts +26 -10
  75. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  82. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  83. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  84. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  85. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  86. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  87. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  88. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  89. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  90. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  91. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  92. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  93. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  94. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  95. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  96. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  97. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  98. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  99. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  100. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  101. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  102. package/src/__tests__/disk-usage.test.ts +35 -20
  103. package/src/__tests__/document-sync-tags.test.ts +379 -0
  104. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  105. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  106. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  107. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  108. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  109. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  110. package/src/__tests__/file-list-tool.test.ts +12 -10
  111. package/src/__tests__/file-ops-service.test.ts +151 -44
  112. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  113. package/src/__tests__/file-read-tool.test.ts +21 -9
  114. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  115. package/src/__tests__/filesystem-tools.test.ts +82 -66
  116. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  117. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  118. package/src/__tests__/helpers/hub-clients.ts +42 -0
  119. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  120. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  121. package/src/__tests__/history-repair.test.ts +112 -12
  122. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  123. package/src/__tests__/host-bash-routes.test.ts +67 -0
  124. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  125. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  126. package/src/__tests__/host-file-proxy.test.ts +3 -7
  127. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  128. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  129. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  130. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  131. package/src/__tests__/identity-routes.test.ts +13 -13
  132. package/src/__tests__/image-conversion.test.ts +29 -26
  133. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  134. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  135. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  136. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  137. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  138. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  139. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  140. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  141. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  142. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  143. package/src/__tests__/list-messages-queued.test.ts +49 -6
  144. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  145. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  146. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  147. package/src/__tests__/llm-usage-store.test.ts +55 -0
  148. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  149. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  150. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  151. package/src/__tests__/migration-export-http.test.ts +1 -1
  152. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  153. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  154. package/src/__tests__/migration-validate-http.test.ts +1 -1
  155. package/src/__tests__/mtime-cache.test.ts +65 -3
  156. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  157. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  158. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  159. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  160. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  161. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  162. package/src/__tests__/path-policy.test.ts +164 -1
  163. package/src/__tests__/persist-media-references.test.ts +2 -2
  164. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  165. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  166. package/src/__tests__/persona-resolver.test.ts +154 -0
  167. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  168. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  169. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  170. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  171. package/src/__tests__/profiler-routes.test.ts +13 -13
  172. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  173. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  174. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  175. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  176. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  177. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  178. package/src/__tests__/run-due-schedules.test.ts +174 -0
  179. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  180. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  181. package/src/__tests__/schedule-routes.test.ts +730 -8
  182. package/src/__tests__/schedule-store.test.ts +540 -0
  183. package/src/__tests__/schedule-tools.test.ts +103 -0
  184. package/src/__tests__/scheduler-wake.test.ts +5 -0
  185. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  186. package/src/__tests__/secure-keys.test.ts +91 -0
  187. package/src/__tests__/server-history-render.test.ts +97 -7
  188. package/src/__tests__/size-guard.test.ts +8 -8
  189. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  190. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  191. package/src/__tests__/skills-install-staging.test.ts +15 -6
  192. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  193. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  194. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  195. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  196. package/src/__tests__/starter-task-flow.test.ts +5 -4
  197. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  198. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  199. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  200. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  201. package/src/__tests__/subagent-disposal.test.ts +10 -0
  202. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  203. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  204. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  205. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  206. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  207. package/src/__tests__/subagent-persistence.test.ts +141 -0
  208. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  209. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  210. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  211. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  212. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  213. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  214. package/src/__tests__/subagent-tools.test.ts +1901 -108
  215. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  216. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  217. package/src/__tests__/system-prompt.test.ts +82 -7
  218. package/src/__tests__/tool-result-spool.test.ts +74 -18
  219. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  220. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  221. package/src/__tests__/tools-audio-read.test.ts +10 -10
  222. package/src/__tests__/tools-get-route.test.ts +85 -12
  223. package/src/__tests__/tools-list-cli.test.ts +171 -0
  224. package/src/__tests__/top-level-renderer.test.ts +54 -1
  225. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  226. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  227. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  228. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  229. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  230. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  231. package/src/__tests__/usage-routes.test.ts +62 -0
  232. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  233. package/src/__tests__/visible-app-context.test.ts +20 -4
  234. package/src/__tests__/web-search-history.test.ts +96 -0
  235. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  236. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  237. package/src/acp/failure-error.ts +3 -5
  238. package/src/agent/attachments.ts +26 -24
  239. package/src/agent/history-repair/history-repair.ts +27 -26
  240. package/src/agent/image-optimize.ts +6 -6
  241. package/src/agent/loop.ts +40 -22
  242. package/src/agent/message-types.ts +3 -3
  243. package/src/api/constants/document-tools.ts +55 -0
  244. package/src/api/events/question-answered.ts +53 -0
  245. package/src/api/events/tool-result.ts +7 -0
  246. package/src/api/index.ts +13 -0
  247. package/src/api/responses/conversation-message.ts +11 -0
  248. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  249. package/src/api/surfaces.ts +36 -8
  250. package/src/approvals/guardian-channel-delivery.ts +83 -0
  251. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  252. package/src/approvals/guardian-request-resolvers.ts +216 -132
  253. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  254. package/src/calls/media-stream-server.ts +9 -1
  255. package/src/calls/voice-session-bridge.ts +38 -0
  256. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  257. package/src/channels/config.ts +7 -2
  258. package/src/channels/gateway-verification-sessions.ts +7 -2
  259. package/src/channels/types.ts +7 -1
  260. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  261. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  262. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  263. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  264. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  265. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  266. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  267. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  268. package/src/cli/commands/conversations.ts +7 -3
  269. package/src/cli/commands/credentials.help.ts +7 -0
  270. package/src/cli/commands/inference-profiles.ts +9 -3
  271. package/src/cli/commands/inference.help.ts +16 -2
  272. package/src/cli/commands/mcp.help.ts +4 -2
  273. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  274. package/src/cli/commands/notifications.ts +3 -6
  275. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  276. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  277. package/src/cli/commands/oauth/connect.test.ts +26 -1
  278. package/src/cli/commands/oauth/connect.ts +4 -1
  279. package/src/cli/commands/oauth/index.help.ts +3 -1
  280. package/src/cli/commands/oauth/providers.ts +3 -0
  281. package/src/cli/commands/plugins.help.ts +25 -2
  282. package/src/cli/commands/plugins.ts +289 -10
  283. package/src/cli/commands/schedules.help.ts +14 -12
  284. package/src/cli/commands/schedules.ts +19 -5
  285. package/src/cli/commands/tools.help.ts +4 -2
  286. package/src/cli/commands/tools.ts +38 -0
  287. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  288. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  289. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  290. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  291. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  292. package/src/cli/lib/install-from-github.ts +61 -0
  293. package/src/cli/lib/install-from-platform.ts +13 -2
  294. package/src/cli/lib/plugin-surfaces.ts +124 -4
  295. package/src/cli/lib/upgrade-plugin.ts +18 -0
  296. package/src/cli.ts +1 -1
  297. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  298. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  299. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  300. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  301. package/src/config/bundled-skills/AGENTS.md +1 -1
  302. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  303. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  304. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  305. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  306. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  307. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  308. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  309. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  310. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  311. package/src/config/default-profile-catalog.ts +6 -2
  312. package/src/config/feature-flag-registry.json +25 -17
  313. package/src/config/llm-resolver.ts +45 -15
  314. package/src/config/loader.ts +70 -1
  315. package/src/config/profile-tool-support.ts +110 -0
  316. package/src/config/sanitize-for-transfer.ts +11 -0
  317. package/src/config/schema.ts +4 -0
  318. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  319. package/src/config/schemas/channels.ts +13 -0
  320. package/src/config/schemas/memory-retrospective.ts +25 -0
  321. package/src/config/schemas/plugin-updates.ts +55 -0
  322. package/src/config/schemas/services.ts +8 -4
  323. package/src/config/schemas/stt.ts +17 -5
  324. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  325. package/src/contacts/guardian-delivery-reader.ts +29 -2
  326. package/src/context/compactor.ts +4 -4
  327. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  328. package/src/context/strip-injections.ts +42 -0
  329. package/src/context/tool-result-spool.ts +32 -18
  330. package/src/credential-execution/ces-runtime.ts +3 -0
  331. package/src/credential-execution/client.ts +22 -1
  332. package/src/credential-execution/process-manager.test.ts +99 -0
  333. package/src/credential-execution/process-manager.ts +62 -16
  334. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  335. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  336. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  337. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  338. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  339. package/src/daemon/channel-ui-capability.ts +4 -4
  340. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  341. package/src/daemon/conversation-agent-loop.ts +296 -145
  342. package/src/daemon/conversation-attachments.ts +1 -1
  343. package/src/daemon/conversation-error.ts +8 -1
  344. package/src/daemon/conversation-initial-prompt.ts +16 -38
  345. package/src/daemon/conversation-lifecycle.ts +154 -32
  346. package/src/daemon/conversation-messaging.ts +85 -42
  347. package/src/daemon/conversation-process.ts +76 -28
  348. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  349. package/src/daemon/conversation-store.ts +52 -3
  350. package/src/daemon/conversation-surface-state.ts +9 -0
  351. package/src/daemon/conversation-surfaces.ts +403 -210
  352. package/src/daemon/conversation-tool-setup.ts +56 -7
  353. package/src/daemon/conversation-turn-finalize.ts +134 -89
  354. package/src/daemon/conversation.ts +105 -27
  355. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  356. package/src/daemon/disk-pressure-guard.ts +3 -3
  357. package/src/daemon/doordash-steps.ts +5 -5
  358. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  359. package/src/daemon/handlers/config-channels.ts +48 -5
  360. package/src/daemon/handlers/conversation-history.ts +1 -1
  361. package/src/daemon/handlers/conversations.ts +62 -17
  362. package/src/daemon/handlers/shared.ts +12 -0
  363. package/src/daemon/handlers/skills.ts +11 -1
  364. package/src/daemon/identity-helpers.ts +38 -10
  365. package/src/daemon/install-assistant-command.ts +322 -0
  366. package/src/daemon/lifecycle.ts +14 -3
  367. package/src/daemon/message-types/conversations.ts +6 -7
  368. package/src/daemon/message-types/sync.ts +1 -0
  369. package/src/daemon/persist-media-references.ts +23 -14
  370. package/src/daemon/process-message.ts +6 -6
  371. package/src/daemon/tool-setup-types.ts +19 -122
  372. package/src/daemon/tool-side-effects.ts +17 -94
  373. package/src/daemon/trust-context.ts +14 -6
  374. package/src/daemon/turn-tail-chain.ts +73 -0
  375. package/src/daemon/web-search-history.ts +47 -33
  376. package/src/heartbeat/heartbeat-service.ts +6 -6
  377. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  378. package/src/ipc/assistant-server.ts +17 -10
  379. package/src/ipc/cli-client.ts +10 -1
  380. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  381. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  382. package/src/ipc/socket-cleanup.ts +9 -7
  383. package/src/ipc/socket-path.ts +4 -96
  384. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  385. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  386. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  387. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  388. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  389. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  390. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  391. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  392. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  393. package/src/live-voice/live-voice-archive.ts +13 -13
  394. package/src/live-voice/live-voice-connection.ts +7 -1
  395. package/src/live-voice/live-voice-photo.ts +169 -0
  396. package/src/live-voice/live-voice-session.ts +108 -4
  397. package/src/live-voice/protocol.ts +89 -1
  398. package/src/mcp/client.ts +10 -0
  399. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  400. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  401. package/src/messaging/providers/callback-routing.ts +1 -0
  402. package/src/messaging/providers/discord/api.ts +312 -0
  403. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  404. package/src/messaging/providers/discord/render.test.ts +204 -0
  405. package/src/messaging/providers/discord/render.ts +195 -0
  406. package/src/messaging/providers/discord/send.test.ts +179 -0
  407. package/src/messaging/providers/discord/send.ts +212 -0
  408. package/src/messaging/providers/discord/transport.ts +81 -0
  409. package/src/messaging/providers/index.ts +3 -1
  410. package/src/messaging/providers/retry-policy.test.ts +247 -0
  411. package/src/messaging/providers/retry-policy.ts +206 -0
  412. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  413. package/src/messaging/providers/slack/mention-source.ts +287 -0
  414. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  415. package/src/messaging/providers/whatsapp/api.ts +47 -136
  416. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  417. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  418. package/src/monitoring/db-integrity-sample.ts +6 -1
  419. package/src/monitoring/plugin-auto-update.ts +359 -0
  420. package/src/monitoring/resource-sampler.ts +21 -5
  421. package/src/monitoring/worker.ts +13 -0
  422. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  423. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  424. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  425. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  426. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  427. package/src/notifications/adapters/platform.ts +31 -2
  428. package/src/notifications/assistant-reply-producer.ts +79 -32
  429. package/src/notifications/broadcaster.ts +30 -10
  430. package/src/notifications/copy-composer.ts +69 -0
  431. package/src/notifications/emit-signal.ts +82 -15
  432. package/src/notifications/events-store.ts +10 -2
  433. package/src/notifications/home-feed-side-effect.ts +6 -3
  434. package/src/notifications/notification-utils.ts +173 -5
  435. package/src/notifications/signal.ts +12 -0
  436. package/src/oauth/byo-connection.ts +6 -1
  437. package/src/oauth/credential-token-resolver.ts +3 -10
  438. package/src/oauth/manual-token-connection.ts +29 -64
  439. package/src/oauth/manual-token-providers.ts +62 -0
  440. package/src/oauth/provider-serializer.ts +13 -0
  441. package/src/oauth/seed-providers.ts +56 -8
  442. package/src/onboarding/onboarding-events-store.ts +91 -1
  443. package/src/permissions/question-prompter.test.ts +4 -0
  444. package/src/permissions/question-prompter.ts +21 -4
  445. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  446. package/src/persistence/attachments-store.ts +15 -15
  447. package/src/persistence/conversation-attention-store.ts +30 -10
  448. package/src/persistence/conversation-bootstrap.ts +17 -0
  449. package/src/persistence/conversation-crud.ts +363 -97
  450. package/src/persistence/conversation-group-migration.ts +1 -1
  451. package/src/persistence/conversation-key-store.ts +38 -10
  452. package/src/persistence/conversation-lineage.ts +226 -0
  453. package/src/persistence/conversation-plugin-facade.ts +28 -3
  454. package/src/persistence/conversation-queries.ts +207 -33
  455. package/src/persistence/conversation-types.ts +107 -1
  456. package/src/persistence/jobs-store.ts +32 -8
  457. package/src/persistence/llm-usage-store.ts +22 -0
  458. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  459. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  460. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  461. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  462. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  463. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  464. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  465. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  466. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  467. package/src/persistence/schema/conversations.ts +32 -0
  468. package/src/persistence/schema/infrastructure.ts +3 -0
  469. package/src/persistence/steps.ts +30 -0
  470. package/src/persistence/subagent-store.ts +148 -6
  471. package/src/platform/managed-speech.test.ts +20 -0
  472. package/src/platform/managed-speech.ts +9 -0
  473. package/src/plugin-api/index.ts +8 -0
  474. package/src/plugin-api/webhook-url.ts +151 -0
  475. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  476. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  477. package/src/plugins/collect-source-versions.ts +4 -27
  478. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  479. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  480. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  481. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  482. package/src/plugins/defaults/index.ts +1 -1
  483. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  484. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  485. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  486. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  487. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  488. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  489. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  490. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  491. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  492. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  493. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  494. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  495. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  496. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  497. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  498. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  499. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  500. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  501. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  502. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  503. package/src/plugins/external-plugin-loader.ts +17 -8
  504. package/src/plugins/installed-plugin-dirs.ts +101 -0
  505. package/src/plugins/mtime-cache.ts +34 -22
  506. package/src/plugins/types.ts +69 -8
  507. package/src/prompts/persona-resolver.ts +6 -2
  508. package/src/prompts/system-prompt.ts +72 -17
  509. package/src/providers/__tests__/inference.test.ts +40 -0
  510. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  511. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  512. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  513. package/src/providers/anthropic/client.ts +112 -95
  514. package/src/providers/connection-resolution.ts +35 -1
  515. package/src/providers/gemini/client.ts +5 -5
  516. package/src/providers/inference/adapter-factory.ts +16 -10
  517. package/src/providers/inference/auth.ts +16 -0
  518. package/src/providers/inference/connection-availability.ts +164 -12
  519. package/src/providers/inference/connections.ts +25 -1
  520. package/src/providers/media-resolve.ts +21 -11
  521. package/src/providers/model-catalog.ts +15 -0
  522. package/src/providers/openai/chat-completions-provider.ts +4 -4
  523. package/src/providers/openai/responses-provider.ts +12 -25
  524. package/src/providers/registry.ts +9 -4
  525. package/src/providers/retry.ts +25 -0
  526. package/src/providers/server-tool-pairing.ts +134 -0
  527. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  528. package/src/providers/speech-to-text/deepgram.ts +1 -0
  529. package/src/providers/speech-to-text/resolve.ts +80 -10
  530. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  531. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  532. package/src/providers/types.ts +26 -6
  533. package/src/providers/vellum-model-routing.ts +6 -7
  534. package/src/runtime/AGENTS.md +2 -0
  535. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  536. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  537. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  538. package/src/runtime/agent-wake.ts +63 -24
  539. package/src/runtime/assistant-event-hub.ts +87 -12
  540. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  541. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  542. package/src/runtime/auth/same-actor.ts +56 -8
  543. package/src/runtime/auth/scopes.ts +12 -0
  544. package/src/runtime/auth/types.ts +2 -0
  545. package/src/runtime/background-job-runner.ts +5 -27
  546. package/src/runtime/channel-readiness-service.ts +59 -3
  547. package/src/runtime/channel-readiness-types.ts +18 -0
  548. package/src/runtime/desktop-presence.ts +69 -0
  549. package/src/runtime/http-server.ts +12 -0
  550. package/src/runtime/local-actor-identity.ts +11 -9
  551. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  552. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  553. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  554. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  555. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  556. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  557. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  558. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  559. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  560. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  561. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  562. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  563. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  564. package/src/runtime/routes/archive-utils.ts +16 -12
  565. package/src/runtime/routes/attachment-routes.ts +13 -6
  566. package/src/runtime/routes/canned-reply-release.ts +54 -0
  567. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  568. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  569. package/src/runtime/routes/client-routes.ts +114 -2
  570. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  571. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  572. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  573. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  574. package/src/runtime/routes/conversation-routes.ts +350 -293
  575. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  576. package/src/runtime/routes/debug-routes.ts +4 -1
  577. package/src/runtime/routes/default-provider-routes.ts +7 -11
  578. package/src/runtime/routes/documents-routes.ts +44 -1
  579. package/src/runtime/routes/events-routes.ts +28 -1
  580. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  581. package/src/runtime/routes/host-bash-routes.ts +2 -0
  582. package/src/runtime/routes/host-browser-routes.ts +2 -0
  583. package/src/runtime/routes/host-cu-routes.ts +2 -0
  584. package/src/runtime/routes/host-file-routes.ts +2 -0
  585. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  586. package/src/runtime/routes/identity-routes.ts +6 -6
  587. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  588. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  589. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  590. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  591. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  592. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  593. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  594. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  595. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  596. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  597. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  598. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  599. package/src/runtime/routes/log-export-routes.ts +1 -1
  600. package/src/runtime/routes/notification-routes.ts +0 -14
  601. package/src/runtime/routes/oauth-providers.ts +1 -0
  602. package/src/runtime/routes/plugins-routes.ts +106 -1
  603. package/src/runtime/routes/profiler-routes.ts +4 -2
  604. package/src/runtime/routes/question-routes.ts +28 -10
  605. package/src/runtime/routes/retrospective-routes.ts +9 -3
  606. package/src/runtime/routes/schedule-routes.ts +210 -12
  607. package/src/runtime/routes/secret-routes.ts +40 -23
  608. package/src/runtime/routes/settings-routes.ts +41 -13
  609. package/src/runtime/routes/skills-routes.ts +79 -0
  610. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  611. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  612. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  613. package/src/runtime/services/conversation-serializer.ts +24 -9
  614. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  615. package/src/runtime/sync/resource-sync-events.ts +79 -1
  616. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  617. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  618. package/src/runtime/verification-outbound-actions.ts +250 -222
  619. package/src/runtime/verification-templates.ts +41 -30
  620. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  621. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  622. package/src/schedule/inference-profile.ts +64 -3
  623. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  624. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  625. package/src/schedule/plugin-schedules-gate.ts +23 -0
  626. package/src/schedule/schedule-store.ts +520 -26
  627. package/src/schedule/scheduler.ts +38 -0
  628. package/src/security/credential-backend.ts +51 -4
  629. package/src/security/encrypted-store.ts +64 -42
  630. package/src/security/secure-keys.ts +55 -7
  631. package/src/skills/catalog-install.ts +20 -7
  632. package/src/skills/inline-command-runner.ts +2 -7
  633. package/src/skills/skill-history.test.ts +269 -0
  634. package/src/skills/skill-history.ts +250 -0
  635. package/src/skills/skillssh-registry.ts +1 -1
  636. package/src/stt/daemon-batch-transcriber.ts +8 -6
  637. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  638. package/src/subagent/consult-prompt.ts +3 -1
  639. package/src/subagent/consult-transcript.ts +1 -1
  640. package/src/subagent/manager.ts +295 -42
  641. package/src/subagent/progress-events.ts +35 -0
  642. package/src/subagent/role-resolution.ts +88 -0
  643. package/src/subagent/types.ts +302 -67
  644. package/src/subagent/validate-allowlists.ts +3 -4
  645. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  646. package/src/telegram/webhook-health.ts +101 -3
  647. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  648. package/src/telemetry/activation-funnel.ts +6 -1
  649. package/src/telemetry/live-voice-funnel.ts +75 -0
  650. package/src/telemetry/telemetry-event-sources.ts +6 -0
  651. package/src/telemetry/telemetry-wire-source.json +1 -1
  652. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  653. package/src/telemetry/types.ts +23 -4
  654. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  655. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  656. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  657. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  658. package/src/tools/browser/browser-execution.ts +1 -1
  659. package/src/tools/calls/call-start.ts +8 -5
  660. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  661. package/src/tools/document/document-tool.ts +11 -4
  662. package/src/tools/executor.ts +7 -6
  663. package/src/tools/filesystem/edit.ts +1 -1
  664. package/src/tools/filesystem/list.ts +1 -1
  665. package/src/tools/filesystem/read.ts +7 -13
  666. package/src/tools/filesystem/search.ts +23 -21
  667. package/src/tools/filesystem/write.ts +4 -1
  668. package/src/tools/host-filesystem/edit.ts +7 -2
  669. package/src/tools/host-filesystem/read.ts +21 -8
  670. package/src/tools/host-filesystem/write.ts +10 -2
  671. package/src/tools/host-terminal/host-shell.ts +2 -2
  672. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  673. package/src/tools/permission-checker.ts +8 -3
  674. package/src/tools/schedule/delete.ts +13 -1
  675. package/src/tools/schedule/list.ts +14 -3
  676. package/src/tools/schedule/update.ts +56 -33
  677. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  678. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  679. package/src/tools/shared/filesystem/image-read.ts +12 -12
  680. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  681. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  682. package/src/tools/shared/input-misuse.ts +72 -0
  683. package/src/tools/skills/scaffold-managed.ts +82 -0
  684. package/src/tools/skills/skill-tool-factory.ts +14 -1
  685. package/src/tools/subagent/consult-deadline.ts +12 -7
  686. package/src/tools/subagent/message.ts +3 -1
  687. package/src/tools/subagent/read.ts +66 -4
  688. package/src/tools/subagent/spawn.ts +500 -78
  689. package/src/tools/types.ts +22 -3
  690. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  691. package/src/tools/ui-surface/visual-validation.ts +2 -0
  692. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  693. package/src/usage/subagent-attribution.ts +108 -0
  694. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  695. package/src/util/abort-reasons.ts +16 -0
  696. package/src/util/ansi.ts +44 -0
  697. package/src/util/disk-usage.ts +17 -13
  698. package/src/util/image-conversion.ts +20 -13
  699. package/src/util/schedule-source-key.ts +23 -0
  700. package/src/util/worker-process.ts +7 -1
  701. package/src/workspace/git-service.ts +33 -9
  702. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  703. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  704. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  705. package/src/workspace/migrations/registry.ts +4 -0
  706. package/src/workspace/top-level-renderer.ts +15 -2
  707. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  708. package/src/daemon/install-symlink.ts +0 -207
  709. 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
  },
@@ -645,18 +733,18 @@ export class SubagentManager {
645
733
  conversation.hasSystemPromptOverride = true;
646
734
  }
647
735
 
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.
736
+ // Apply the role's tool allowlist when one is defined. `builder` defines
737
+ // none, so it keeps the full surface its conversation projects; the scoped
738
+ // roles are filtered to their own list whether or not this is a fork.
652
739
  if (roleConfig.allowedTools) {
653
740
  conversation.setSubagentAllowedTools(new Set(roleConfig.allowedTools));
654
741
  }
655
742
 
656
743
  // 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) {
744
+ // class; the executor gate rejects any such dispatch and they are kept off
745
+ // the model's tool surface. The role carries the gate so a caller cannot
746
+ // spawn a role past it, and a spawn can still ask for it on top.
747
+ if (config.denySideEffectTools || roleConfig.denySideEffects) {
660
748
  conversation.setSubagentDenySideEffects(true);
661
749
  }
662
750
 
@@ -745,12 +833,20 @@ export class SubagentManager {
745
833
  async spawnAndAwait(
746
834
  config: Omit<SubagentConfig, "id">,
747
835
  parentSendToClient: (msg: AssistantEvent) => void,
748
- opts?: { signal?: AbortSignal; onText?: (chunk: string) => void },
836
+ opts?: {
837
+ signal?: AbortSignal;
838
+ onText?: (chunk: string) => void;
839
+ onProgress?: () => void;
840
+ },
749
841
  ): Promise<string> {
750
842
  const { subagentId, managed } = await this.setUpSubagent(
751
843
  config,
752
844
  parentSendToClient,
753
- { synchronous: true, ...(opts?.onText ? { onText: opts.onText } : {}) },
845
+ {
846
+ synchronous: true,
847
+ ...(opts?.onText ? { onText: opts.onText } : {}),
848
+ ...(opts?.onProgress ? { onProgress: opts.onProgress } : {}),
849
+ },
754
850
  );
755
851
 
756
852
  // Wire the external signal to abort the child conversation. If the signal
@@ -863,13 +959,24 @@ export class SubagentManager {
863
959
  // the generic "complete this task and return your findings" wrapper would
864
960
  // fight that framing. The advisor's objective is already the bare advice
865
961
  // request (`advisorRequestText()`), so it is sent uncontested.
962
+ //
963
+ // A fork's persona and output contract ride in this framing rather than
964
+ // the system prompt: the prompt is the parent's, inherited verbatim to
965
+ // keep the KV cache aligned, so the task message is the only place a
966
+ // fork-specific instruction can land.
866
967
  const useForkFraming =
867
968
  managed.state.isFork && managed.state.config.role !== "advisor";
969
+ const forkPersona = managed.state.config.persona;
970
+ const forkContract = subagentOutputContractText(
971
+ managed.state.config.outputContract,
972
+ );
868
973
  const message = useForkFraming
869
974
  ? [
870
975
  "⎯⎯⎯ FORK TASK ⎯⎯⎯",
871
976
  "You have been forked from the parent conversation to execute a specific task.",
872
977
  "The conversation above is context — do NOT continue it. Do NOT spawn sub-agents.",
978
+ ...(forkPersona ? [`Act as ${forkPersona} for this task.`] : []),
979
+ ...(forkContract ? [`Output contract: ${forkContract}`] : []),
873
980
  "Complete this task directly and return only your findings:",
874
981
  "",
875
982
  objective,
@@ -887,6 +994,11 @@ export class SubagentManager {
887
994
  ...(managed.state.config.forceOverrideProfile
888
995
  ? { forceOverrideProfile: true }
889
996
  : {}),
997
+ // Stamp the child's usage with the firing that spawned it so schedule
998
+ // cost reporting sees delegated spend.
999
+ ...(managed.state.config.cronRunId
1000
+ ? { cronRunId: managed.state.config.cronRunId }
1001
+ : {}),
890
1002
  });
891
1003
 
892
1004
  // Agent loop completed successfully.
@@ -898,6 +1010,12 @@ export class SubagentManager {
898
1010
  const deniedTools = [...conversation.subagentDeniedToolNames];
899
1011
  // Copy usage stats from the conversation before sending status (which includes usage).
900
1012
  managed.state.usage = { ...conversation.usageStats };
1013
+ // Same window for the tool-call counts: the terminal notification below
1014
+ // reads them off the state. This is the first reading, not necessarily
1015
+ // the last, since a follow-up turn queued during the run drains after
1016
+ // this returns and keeps counting into the same conversation. Later
1017
+ // readers go through `currentToolStats` for the settled numbers.
1018
+ managed.state.stats = snapshotToolStats(conversation);
901
1019
  // Only update state + notify if still non-terminal (guards against abort race).
902
1020
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
903
1021
  managed.state.completedAt = Date.now();
@@ -925,6 +1043,7 @@ export class SubagentManager {
925
1043
  // Copy usage from the captured conversation reference — managed.conversation
926
1044
  // may have been nulled by an external dispose() before catch runs.
927
1045
  managed.state.usage = { ...conversation.usageStats };
1046
+ managed.state.stats = snapshotToolStats(conversation);
928
1047
 
929
1048
  // Only update status if not already terminal (e.g. aborted).
930
1049
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
@@ -1167,9 +1286,19 @@ export class SubagentManager {
1167
1286
 
1168
1287
  // ── Send message to subagent ──────────────────────────────────────────
1169
1288
 
1289
+ /**
1290
+ * Deliver a follow-up message to a live subagent.
1291
+ *
1292
+ * `opts.cronRunId` is the firing that produced THIS message, not the one the
1293
+ * subagent was spawned under: a continuation turn's spend belongs to the
1294
+ * firing that asked for it. Only the immediately-processed turn carries it,
1295
+ * since a queued message drains through the conversation's own queue, which
1296
+ * holds no per-message run options.
1297
+ */
1170
1298
  async sendMessage(
1171
1299
  subagentId: string,
1172
1300
  content: string,
1301
+ opts?: { cronRunId?: string | null },
1173
1302
  ): Promise<"sent" | "empty" | "not_found" | "terminal"> {
1174
1303
  const trimmed = content?.trim();
1175
1304
  if (!trimmed) {
@@ -1208,6 +1337,7 @@ export class SubagentManager {
1208
1337
  ...(managed.state.config.forceOverrideProfile
1209
1338
  ? { forceOverrideProfile: true }
1210
1339
  : {}),
1340
+ ...(opts?.cronRunId ? { cronRunId: opts.cronRunId } : {}),
1211
1341
  })
1212
1342
  .catch((err) => {
1213
1343
  log.error({ subagentId, err }, "Subagent message processing failed");
@@ -1222,6 +1352,116 @@ export class SubagentManager {
1222
1352
  return this.subagents.get(subagentId)?.state;
1223
1353
  }
1224
1354
 
1355
+ /**
1356
+ * The subagent's tool-call counters, brought up to date first, or why there
1357
+ * are none (see {@link SubagentToolStatsReading}).
1358
+ *
1359
+ * `runSubagent` harvests when its awaited agent loop returns, but that is not
1360
+ * the end of the child's work: guidance queued during the run drains
1361
+ * afterwards, on the same conversation, and those calls land in the same
1362
+ * counters. So any read taken later re-reads them while the conversation is
1363
+ * still retained (see {@link refreshToolStats}), and the release freezes the
1364
+ * settled numbers. Readers that need the queued turn's calls included wait
1365
+ * for it first, via {@link settleQueuedTurns}.
1366
+ *
1367
+ * An id the manager does not hold is `unrecoverable` rather than unknown:
1368
+ * counters exist nowhere else, so no caller can ever obtain them, and the
1369
+ * only state a caller can be holding for such an id came from the durable
1370
+ * row.
1371
+ */
1372
+ currentToolStats(subagentId: string): SubagentToolStatsReading {
1373
+ const managed = this.subagents.get(subagentId);
1374
+ if (!managed) {
1375
+ return { kind: "unrecoverable" };
1376
+ }
1377
+ this.refreshToolStats(managed);
1378
+ if (managed.state.stats) {
1379
+ return { kind: "counted", stats: managed.state.stats };
1380
+ }
1381
+ return managed.rehydrated
1382
+ ? { kind: "unrecoverable" }
1383
+ : { kind: "unmeasured" };
1384
+ }
1385
+
1386
+ /**
1387
+ * Wait for a follow-up turn queued during the subagent's run to finish.
1388
+ *
1389
+ * `runSubagent` marks the subagent terminal as soon as its own agent loop
1390
+ * returns, and the parent is told to read from there. Guidance queued during
1391
+ * that run drains afterwards though, on the same retained conversation, so a
1392
+ * read taken in that window sees the transcript and the counters from before
1393
+ * the guidance landed and never comes back for the rest. Waiting here closes
1394
+ * the window.
1395
+ *
1396
+ * Resolves `true` once the retained conversation is idle with an empty
1397
+ * queue, and `false` when `timeoutMs` elapses first, so the reader always
1398
+ * gets an answer within a bound and can say the subagent is still moving
1399
+ * rather than pass a partial result off as final.
1400
+ *
1401
+ * `true` comes back immediately when nothing can still be running: no
1402
+ * manager entry, no retained conversation (a released one has its transcript
1403
+ * and counters frozen), or a run that never had anything queued.
1404
+ *
1405
+ * Idle is confirmed across two observations a poll apart. The drain takes
1406
+ * the queue before it takes the processing lock (`drainQueue` shifts the
1407
+ * message, then `drainSingleMessage` awaits slash resolution and the
1408
+ * user-message persist before `runAgentLoop` sets processing), so a single
1409
+ * look into that gap finds an empty queue and an unlocked conversation while
1410
+ * the turn is in fact starting.
1411
+ */
1412
+ async settleQueuedTurns(
1413
+ subagentId: string,
1414
+ timeoutMs: number = QUEUED_TURN_SETTLE_TIMEOUT_MS,
1415
+ ): Promise<boolean> {
1416
+ const managed = this.subagents.get(subagentId);
1417
+ const conversation = managed?.conversation;
1418
+ if (!managed || !conversation || managed.hadEnqueuedMessages !== true) {
1419
+ return true;
1420
+ }
1421
+ const deadline = Date.now() + timeoutMs;
1422
+ let idleObservations = 0;
1423
+ for (;;) {
1424
+ // A release during the wait (the TTL sweep, or a dispose) freezes
1425
+ // everything a reader can see, so there is nothing left to wait for.
1426
+ if (managed.conversation !== conversation) {
1427
+ return true;
1428
+ }
1429
+ const remainingMs = deadline - Date.now();
1430
+ if (remainingMs <= 0) {
1431
+ return false;
1432
+ }
1433
+ if (conversation.isProcessing()) {
1434
+ idleObservations = 0;
1435
+ await conversation.waitForIdle({ timeoutMs: remainingMs });
1436
+ continue;
1437
+ }
1438
+ if (conversation.hasQueuedMessages()) {
1439
+ idleObservations = 0;
1440
+ await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
1441
+ continue;
1442
+ }
1443
+ idleObservations += 1;
1444
+ if (idleObservations >= 2) {
1445
+ return true;
1446
+ }
1447
+ await sleep(Math.min(QUEUED_TURN_POLL_MS, remainingMs));
1448
+ }
1449
+ }
1450
+
1451
+ /**
1452
+ * Re-read the child conversation's live counters into its state.
1453
+ *
1454
+ * Only ever updates a harvest that already happened: a run that never
1455
+ * reached its harvest (aborted before the first turn, or still going) has
1456
+ * nothing measured to report, and a zero taken mid-flight would read as
1457
+ * "this subagent used no tools" rather than "not measured yet".
1458
+ */
1459
+ private refreshToolStats(managed: ManagedSubagent): void {
1460
+ if (managed.conversation && managed.state.stats) {
1461
+ managed.state.stats = snapshotToolStats(managed.conversation);
1462
+ }
1463
+ }
1464
+
1225
1465
  getByLabel(
1226
1466
  label: string,
1227
1467
  parentConversationId: string,
@@ -1296,6 +1536,10 @@ export class SubagentManager {
1296
1536
  if (!managed.conversation) {
1297
1537
  return;
1298
1538
  }
1539
+ // Last chance at the counters: on the deferred path this release happens
1540
+ // after the queued follow-up turn drained, so this is the reading that
1541
+ // includes it. Everything read after this point is this snapshot.
1542
+ this.refreshToolStats(managed);
1299
1543
  const conversation = managed.conversation;
1300
1544
  removeSubagentConversation(conversation.conversationId, conversation);
1301
1545
  conversation.dispose();
@@ -1388,7 +1632,7 @@ export class SubagentManager {
1388
1632
  conversationId: state.conversationId,
1389
1633
  label: state.config.label,
1390
1634
  objective: state.config.objective,
1391
- role: state.config.role ?? "general",
1635
+ role: state.config.role ?? DEFAULT_SUBAGENT_ROLE,
1392
1636
  isFork: state.isFork,
1393
1637
  sendResultToUser: state.config.sendResultToUser ?? null,
1394
1638
  parentToolUseId: state.config.parentToolUseId ?? null,
@@ -1454,6 +1698,7 @@ export class SubagentManager {
1454
1698
  state,
1455
1699
  parentSendToClient: () => {},
1456
1700
  retainedUntil: now + TERMINAL_RETENTION_MS,
1701
+ rehydrated: true,
1457
1702
  };
1458
1703
  this.subagents.set(rec.id, managed);
1459
1704
 
@@ -1609,6 +1854,10 @@ export class SubagentManager {
1609
1854
  ? config.sendResultToUser !== true
1610
1855
  : config.sendResultToUser === false;
1611
1856
 
1857
+ // A queued follow-up turn means the snapshot we hold is stale; defer to a
1858
+ // read pointer so the parent picks up the queued turn's output instead.
1859
+ const deferred = managed.hadEnqueuedMessages === true;
1860
+
1612
1861
  const message = buildSubagentTerminalMessage({
1613
1862
  label: config.label,
1614
1863
  subagentId: config.id,
@@ -1617,10 +1866,14 @@ export class SubagentManager {
1617
1866
  silent,
1618
1867
  finalText,
1619
1868
  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,
1869
+ deferred,
1623
1870
  deniedTools,
1871
+ // Same staleness applies to the counters, and worse: the queued turn has
1872
+ // not run yet at this point, so any number quoted here would under-report
1873
+ // it, permanently, in a message that is never rewritten. The deferred
1874
+ // message sends the parent to `subagent_read`, whose footer re-reads the
1875
+ // counters once the queued turn has actually landed.
1876
+ ...(deferred ? {} : { stats: managed.state.stats }),
1624
1877
  });
1625
1878
 
1626
1879
  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
+ }