@vellumai/assistant 0.12.3-staging.1 → 0.12.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 (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -206,19 +206,26 @@ describe("spawnScheduleWorkerProcess", () => {
206
206
  }
207
207
  });
208
208
 
209
- test("reuses an already-running worker without spawning", async () => {
209
+ test("spawns when the PID file names an unrelated live process", async () => {
210
210
  writeFileSync(pidPath, String(process.pid));
211
211
  let spawned = false;
212
212
  const restore = stubBunSpawn(() => {
213
213
  spawned = true;
214
- return { unref: () => {}, kill: () => {}, pid: 1, exited: neverExits() };
214
+ writeFileSync(pidPath, "4242");
215
+ return {
216
+ unref: () => {},
217
+ kill: () => {},
218
+ pid: 4242,
219
+ exited: neverExits(),
220
+ };
215
221
  });
216
222
  try {
217
223
  const result = await spawnScheduleWorkerProcess({
218
- pidWaitTimeoutMs: 100,
224
+ pidWaitTimeoutMs: 1_000,
225
+ pidPollIntervalMs: 10,
219
226
  });
220
- expect(result).toEqual({ pid: process.pid, alreadyRunning: true });
221
- expect(spawned).toBe(false);
227
+ expect(result).toEqual({ pid: 4242, alreadyRunning: false });
228
+ expect(spawned).toBe(true);
222
229
  } finally {
223
230
  restore();
224
231
  }
@@ -240,6 +247,11 @@ describe("probeScheduleWorker", () => {
240
247
  }
241
248
  });
242
249
 
250
+ test("reports not_running when the PID file names a live process that is not this worker", () => {
251
+ writeFileSync(pidPath, String(process.pid));
252
+ expect(probeScheduleWorker()).toEqual({ status: "not_running" });
253
+ });
254
+
243
255
  test("reports running (not throws) when the process exists but is not signalable (EPERM)", () => {
244
256
  writeFileSync(pidPath, "4321");
245
257
  const restore = stubProcessKill(new Set(), new Set([4321]));
@@ -256,6 +268,25 @@ describe("stopScheduleWorkerProcess", () => {
256
268
  expect(stopScheduleWorkerProcess()).toEqual({ status: "not_running" });
257
269
  });
258
270
 
271
+ test("does not signal a recycled PID that is not this worker", () => {
272
+ writeFileSync(pidPath, String(process.pid));
273
+ const signalled: Array<[number, string | number | undefined]> = [];
274
+ const original = process.kill.bind(process);
275
+ process.kill = ((pid: number, signal?: string | number) => {
276
+ if ((signal ?? 0) === 0) {
277
+ return original(pid, signal);
278
+ }
279
+ signalled.push([pid, signal]);
280
+ return true;
281
+ }) as typeof process.kill;
282
+ try {
283
+ expect(stopScheduleWorkerProcess()).toEqual({ status: "not_running" });
284
+ expect(signalled).toEqual([]);
285
+ } finally {
286
+ process.kill = original;
287
+ }
288
+ });
289
+
259
290
  test("signals a running worker and reports its prior state", () => {
260
291
  writeFileSync(pidPath, "4321");
261
292
  const signalled: Array<[number, string | number | undefined]> = [];
@@ -19,6 +19,8 @@ import {
19
19
  type WorkerProcessStatus,
20
20
  } from "../util/worker-process.js";
21
21
 
22
+ const SCHEDULE_WORKER_ENTRY = new URL("./worker.ts", import.meta.url);
23
+
22
24
  const log = getLogger("schedule-worker-control");
23
25
 
24
26
  /**
@@ -42,11 +44,15 @@ export function setScheduleWorkerAdministrativelyStopped(value: boolean): void {
42
44
 
43
45
  /**
44
46
  * Inspect the PID file to determine whether the schedule worker process is
45
- * alive. A stale PID file (pointing at a dead process) is cleaned up and
46
- * reported as not_running.
47
+ * alive. A stale PID file (pointing at a dead process, or at a live process
48
+ * that is not this worker) is cleaned up and reported as not_running.
47
49
  */
48
50
  export function probeScheduleWorker(): WorkerProcessStatus {
49
- return probeWorkerPidFile(getScheduleWorkerPidPath());
51
+ return probeWorkerPidFile(
52
+ getScheduleWorkerPidPath(),
53
+ SCHEDULE_WORKER_ENTRY,
54
+ "schedule",
55
+ );
50
56
  }
51
57
 
52
58
  export class ScheduleWorkerSpawnError extends WorkerProcessSpawnError {}
@@ -95,7 +101,7 @@ async function spawnScheduleWorkerProcessUncoalesced(
95
101
  try {
96
102
  return await spawnWorkerProcess({
97
103
  pidPath: getScheduleWorkerPidPath(),
98
- entry: new URL("./worker.ts", import.meta.url),
104
+ entry: SCHEDULE_WORKER_ENTRY,
99
105
  packagedEntry: "schedule",
100
106
  workerLabel: "Schedule worker",
101
107
  options: opts,
@@ -114,7 +120,11 @@ async function spawnScheduleWorkerProcessUncoalesced(
114
120
  * `process.kill` itself fails (e.g. EPERM) — a not-running worker is a no-op.
115
121
  */
116
122
  export function stopScheduleWorkerProcess(): WorkerProcessStatus {
117
- return stopWorkerProcess(getScheduleWorkerPidPath());
123
+ return stopWorkerProcess(
124
+ getScheduleWorkerPidPath(),
125
+ SCHEDULE_WORKER_ENTRY,
126
+ "schedule",
127
+ );
118
128
  }
119
129
 
120
130
  /**
@@ -1,22 +1,18 @@
1
1
  /**
2
- * Unified secure key storage — single-backend routing through CredentialBackend
2
+ * Unified secure key storage: single-backend routing through CredentialBackend
3
3
  * adapters.
4
4
  *
5
5
  * Backend selection (`resolveBackendAsync`) is the single async decision point:
6
- * 1. CES RPC (primary) - injected via `setCesClient()`: delegates credential
7
- * operations to the CES process over Unix socket RPC. This is the default
8
- * path for the assistant (which calls startCes() at boot) and for
9
- * child processes that lazily connect to the CES socket (see below).
10
- * 2. Lazy CES RPC connect - non-assistant processes (workers, CLI
11
- * subprocesses) that never call startCes(). On first credential
12
- * resolution they discover the CES bootstrap socket
13
- * (`CES_BOOTSTRAP_SOCKET_DIR`) and cache
14
- * the connection.
15
- * 3. CES HTTP - containerized failover when IPC is unavailable
6
+ * 1. CES RPC (primary) - connect to the CES bootstrap socket and talk RPC.
7
+ * `openCesRpcSession` is the one client constructor. The assistant
8
+ * claims session ownership at boot (`startCes`) and hands CES the
9
+ * assistant API key. Child processes open a session on first credential
10
+ * read only when this process has no client and no reconnect owner.
11
+ * 2. CES HTTP - containerized failover when IPC is unavailable
16
12
  * (`IS_CONTAINERIZED` + `CES_CREDENTIAL_URL`). Used if the assistant's
17
13
  * bootstrap RPC transport is down, or if a process with HTTP env could
18
14
  * not open the socket.
19
- * 4. Encrypted file store (fallback) - used when CES is unavailable locally.
15
+ * 3. Encrypted file store (fallback) - used when CES is unavailable locally.
20
16
  *
21
17
  * All operations (reads, writes, lists, deletes) go to exactly one backend.
22
18
  * There are no cross-store fallbacks or merges. The only transport failover is
@@ -35,14 +31,11 @@ import type {
35
31
 
36
32
  import { getIsContainerized } from "../config/env-registry.js";
37
33
  import {
38
- type CesClient,
39
- createCesClient,
40
- } from "../credential-execution/client.js";
34
+ openCesRpcSession,
35
+ reconnectCesRpcSession,
36
+ } from "../credential-execution/ces-connect.js";
37
+ import { type CesClient } from "../credential-execution/client.js";
41
38
  import { discoverCes } from "../credential-execution/executable-discovery.js";
42
- import {
43
- CesUnavailableError,
44
- createCesProcessManager,
45
- } from "../credential-execution/process-manager.js";
46
39
  import { getAnyProviderEnvVar } from "../providers/provider-env-vars.js";
47
40
  import { getLogger } from "../util/logger.js";
48
41
  import { getProtectedDir } from "../util/platform.js";
@@ -85,11 +78,11 @@ let _resolvedBackend: CredentialBackend | undefined;
85
78
  let _resolvePromise: Promise<CredentialBackend> | undefined;
86
79
 
87
80
  /**
88
- * In-flight lazy CES connection promise for non-daemon processes.
81
+ * In-flight CES RPC session promise for processes that did not call startCes().
89
82
  *
90
83
  * Workers and CLI subprocesses never call startCes(). When they hit
91
84
  * resolveBackendAsync() with no _cesClient and no _cesReconnect
92
- * (daemon-only), this promise memoizes a direct CES connection attempt so
85
+ * (assistant-boot only), this promise memoizes `openCesRpcSession` so
93
86
  * concurrent credential reads in the same process share a single
94
87
  * connect+handshake rather than racing.
95
88
  */
@@ -206,10 +199,9 @@ function getEncryptedStoreBackend(): CredentialBackend {
206
199
  * Resolve the primary credential backend for this process (async).
207
200
  *
208
201
  * Priority:
209
- * 1. CES RPC client: primary path in every environment.
210
- * 2. Lazy CES RPC connect: child processes discover the CES socket.
211
- * 3. Containerized + CES_CREDENTIAL_URL: CES HTTP, only if IPC is down.
212
- * 4. Encrypted file store: local fallback when CES is unavailable.
202
+ * 1. CES RPC: live client, or open one via `openCesRpcSession`.
203
+ * 2. Containerized + CES_CREDENTIAL_URL: CES HTTP, only if IPC is down.
204
+ * 3. Encrypted file store: local fallback when CES is unavailable.
213
205
  *
214
206
  * Once resolved, the backend is cached. If it becomes unavailable (e.g. the
215
207
  * CES transport dies), we attempt to reconnect via `_cesReconnect` rather
@@ -415,19 +407,19 @@ export async function attemptCesReconnection(
415
407
  }
416
408
 
417
409
  /**
418
- * Lazily connect to a CES sibling socket from a non-daemon process.
410
+ * Open a CES RPC session from a process that did not call startCes().
419
411
  *
420
412
  * Workers and CLI subprocesses never call startCes(). This function
421
- * establishes a direct CES connection on first credential resolution,
413
+ * opens the same `openCesRpcSession` path the assistant uses at boot,
422
414
  * memoizing the in-flight promise so concurrent callers share a single
423
415
  * connect+handshake. Discovery uses the shared CES bootstrap socket; a
424
416
  * missing socket fails immediately so callers can fall through without
425
417
  * polling.
426
418
  *
427
419
  * On success, the client is injected via setCesClient() so subsequent
428
- * resolveBackendAsync() calls take the fast CES RPC path (step 1). A
429
- * reconnect callback is also registered so the lazy connection can heal
430
- * if the transport drops mid-process.
420
+ * resolveBackendAsync() calls take the live CES RPC path. A reconnect
421
+ * callback is also registered so the session can heal if the transport
422
+ * drops mid-process.
431
423
  *
432
424
  * Returns undefined on any failure (socket not found, handshake rejected,
433
425
  * timeout) so the caller falls through to the encrypted file store.
@@ -438,70 +430,30 @@ async function tryLazyCesConnect(): Promise<CesClient | undefined> {
438
430
  }
439
431
 
440
432
  _lazyConnectPromise = (async () => {
441
- try {
442
- const discovery = discoverCes();
443
- if (discovery.mode === "unavailable") {
444
- log.info(
445
- { reason: discovery.reason },
446
- "CES socket not reachable for lazy connect, falling back to encrypted file store",
447
- );
448
- return undefined;
449
- }
450
- const pm = createCesProcessManager({});
451
- const transport = await pm.start();
452
- const client = createCesClient(transport);
453
- const { accepted, reason } = await client.handshake();
454
- if (!accepted) {
455
- log.warn(
456
- { reason },
457
- "Lazy CES connection handshake rejected — falling back to encrypted file store",
458
- );
459
- client.close();
460
- await pm.stop().catch(() => {});
461
- return undefined;
462
- }
433
+ const discovery = discoverCes();
434
+ if (discovery.mode === "unavailable") {
463
435
  log.info(
464
- "Lazy CES connection established — credential operations routed through CES RPC",
436
+ { reason: discovery.reason },
437
+ "CES socket not reachable for lazy connect, falling back to encrypted file store",
465
438
  );
466
- setCesClient(client);
467
- // Register a reconnect callback so the lazy connection self-heals
468
- // if the transport drops, mirroring the daemon's proactive reconnect.
469
- setCesReconnect(async () => {
470
- try {
471
- await pm.stop();
472
- const newTransport = await pm.start();
473
- const newClient = createCesClient(newTransport);
474
- const { accepted: ok } = await newClient.handshake();
475
- if (ok) {
476
- log.info("Lazy CES reconnection successful");
477
- return newClient;
478
- }
479
- newClient.close();
480
- await pm.stop().catch(() => {});
481
- return undefined;
482
- } catch (err) {
483
- log.warn(
484
- { error: err instanceof Error ? err.message : String(err) },
485
- "Lazy CES reconnection failed",
486
- );
487
- return undefined;
488
- }
489
- });
490
- return client;
491
- } catch (err) {
492
- if (err instanceof CesUnavailableError) {
493
- log.info(
494
- { reason: err.message },
495
- "CES socket not reachable for lazy connect — falling back to encrypted file store",
496
- );
497
- } else {
498
- log.warn(
499
- { error: err instanceof Error ? err.message : String(err) },
500
- "Lazy CES connection failed — falling back to encrypted file store",
501
- );
502
- }
503
439
  return undefined;
504
440
  }
441
+ const session = await openCesRpcSession();
442
+ if (!session) {
443
+ return undefined;
444
+ }
445
+ log.info(
446
+ "CES RPC session established; credential operations route through CES RPC",
447
+ );
448
+ setCesClient(session.client);
449
+ setCesReconnect(async () => {
450
+ const client = await reconnectCesRpcSession(session.processManager);
451
+ if (client) {
452
+ log.info("CES RPC reconnection successful");
453
+ }
454
+ return client;
455
+ });
456
+ return session.client;
505
457
  })();
506
458
 
507
459
  try {
@@ -512,7 +464,10 @@ async function tryLazyCesConnect(): Promise<CesClient | undefined> {
512
464
  }
513
465
 
514
466
  async function doResolveBackend(): Promise<CredentialBackend> {
515
- // 1. CES RPC. Primary credential backend in every environment.
467
+ // 1. CES RPC. Primary credential backend in every environment. Boot
468
+ // claims reconnect ownership before it reads handshake identity, so
469
+ // that read cannot open a second session. Children open here only
470
+ // when this process has no client and no reconnect owner.
516
471
  if (_cesClient) {
517
472
  const cesRpc = new CesRpcCredentialBackend(_cesClient);
518
473
  if (cesRpc.isAvailable()) {
@@ -525,24 +480,19 @@ async function doResolveBackend(): Promise<CredentialBackend> {
525
480
  );
526
481
  }
527
482
 
528
- // 2. Lazy CES RPC connect. Child processes never call startCes(). When
529
- // the assistant's setCesReconnect() is NOT registered, attempt a
530
- // direct connection to the CES bootstrap socket. On success, inject
531
- // the client via setCesClient()
532
- // and re-resolve through the CES RPC path. On failure, fall through.
533
483
  if (!_cesClient && !_cesReconnect) {
534
- const lazyClient = await tryLazyCesConnect();
535
- if (lazyClient) {
536
- const cesRpc = new CesRpcCredentialBackend(lazyClient);
484
+ const client = await tryLazyCesConnect();
485
+ if (client) {
486
+ const cesRpc = new CesRpcCredentialBackend(client);
537
487
  if (cesRpc.isAvailable()) {
538
488
  _resolvedBackend = cesRpc;
539
- log.info("Resolved credential backend: ces-rpc (lazy connect)");
489
+ log.info("Resolved credential backend: ces-rpc");
540
490
  return cesRpc;
541
491
  }
542
492
  }
543
493
  }
544
494
 
545
- // 3. CES HTTP. Managed failover when IPC is unavailable.
495
+ // 2. CES HTTP. Managed failover when IPC is unavailable.
546
496
  if (getIsContainerized() && process.env.CES_CREDENTIAL_URL) {
547
497
  const ces = createCesCredentialBackend();
548
498
  if (ces.isAvailable()) {
@@ -556,7 +506,7 @@ async function doResolveBackend(): Promise<CredentialBackend> {
556
506
  );
557
507
  }
558
508
 
559
- // 4. On a containerized pod the local encrypted store does not exist and CES
509
+ // 3. On a containerized pod the local encrypted store does not exist and CES
560
510
  // owns credentials. Never resolve to the encrypted store here; it would
561
511
  // report a provisioned credential as absent. Return an unreachable backend
562
512
  // (presence indeterminate, which callers retry) WITHOUT caching it, so the
@@ -568,7 +518,7 @@ async function doResolveBackend(): Promise<CredentialBackend> {
568
518
  return createUnavailableBackend();
569
519
  }
570
520
 
571
- // 5. Encrypted file store: the legitimate backend for local / self-hosted
521
+ // 4. Encrypted file store: the legitimate backend for local / self-hosted
572
522
  // mode when CES is unavailable.
573
523
  _resolvedBackend = getEncryptedStoreBackend();
574
524
  log.info("Resolved credential backend: encrypted-store (local mode)");
@@ -11,11 +11,6 @@ import {
11
11
 
12
12
  const log = getLogger("clawhub");
13
13
 
14
- // Managed skills directory — where installed skill folders live
15
- function getManagedSkillsDir(): string {
16
- return getWorkspaceSkillsDir();
17
- }
18
-
19
14
  // ClaWHub project root — clawhub creates a `skills/` subdir inside its cwd,
20
15
  // so we use the parent of the managed skills dir as the project root.
21
16
  function getClawhubProjectRoot(): string {
@@ -39,7 +34,7 @@ interface IntegrityRecord {
39
34
  type IntegrityManifest = Record<string, IntegrityRecord>;
40
35
 
41
36
  function getIntegrityPath(): string {
42
- return join(getManagedSkillsDir(), ".integrity.json");
37
+ return join(getWorkspaceSkillsDir(), ".integrity.json");
43
38
  }
44
39
 
45
40
  function loadIntegrityManifest(): IntegrityManifest {
@@ -66,7 +61,7 @@ function loadIntegrityManifest(): IntegrityManifest {
66
61
  * from skills that haven't been migrated yet.
67
62
  */
68
63
  export function verifyAndRecordSkillHash(slug: string): void {
69
- const skillDir = join(getManagedSkillsDir(), slug);
64
+ const skillDir = join(getWorkspaceSkillsDir(), slug);
70
65
  const hash = computeSkillHash(skillDir);
71
66
  if (!hash) {
72
67
  log.warn({ slug }, "Could not compute content hash for installed skill");
@@ -232,7 +227,7 @@ function getSkillNameFromSlug(slug: string): string {
232
227
  }
233
228
 
234
229
  function getClawhubSkillsDir(projectRoot?: string): string {
235
- return projectRoot ? join(projectRoot, "skills") : getManagedSkillsDir();
230
+ return projectRoot ? join(projectRoot, "skills") : getWorkspaceSkillsDir();
236
231
  }
237
232
 
238
233
  function hasRootSkillFile(skillDir: string): boolean {
@@ -44,13 +44,9 @@ export function validateManagedSkillId(id: string): string | null {
44
44
 
45
45
  // ─── Path helpers ────────────────────────────────────────────────────────────
46
46
 
47
- function getManagedSkillsDir(): string {
48
- return getWorkspaceSkillsDir();
49
- }
50
-
51
47
  /** Absolute path of a managed skill's directory (whether or not it exists). */
52
48
  export function getManagedSkillDir(id: string): string {
53
- return join(getManagedSkillsDir(), id);
49
+ return join(getWorkspaceSkillsDir(), id);
54
50
  }
55
51
 
56
52
  interface ResolvedCompanionPath {
package/src/stt/types.ts CHANGED
@@ -436,6 +436,10 @@ export interface SttStreamServerTurnEndEvent {
436
436
  * provider does not surface a score.
437
437
  */
438
438
  readonly confidence?: number;
439
+ /** Provider-reported cause of the turn boundary, when available. */
440
+ readonly trigger?: "model" | "manual" | "timeout";
441
+ /** End of the decoded audio window, in seconds from stream start. */
442
+ readonly audioWindowEndSeconds?: number;
439
443
  /**
440
444
  * Provider's index for the turn being closed, matching the
441
445
  * {@link SttStreamServerTurnStartEvent} that opened it. A consumer can
@@ -8,16 +8,20 @@
8
8
  * manager out of the import graph lets tool modules import these helpers without
9
9
  * pulling in the conversation/agent-loop core.
10
10
  *
11
- * Delivery targets the parent's in-process `Conversation` via the conversation
12
- * registry: a notification is injected only when the parent is live here. A
13
- * stale idle parent is rebuilt through `getOrCreateConversation` first so the
14
- * injected turn uses the current provider, prompt, and credentials.
11
+ * An active live-voice session owns delivery for its conversation. Otherwise,
12
+ * delivery targets the parent's in-process `Conversation` via the registry.
13
+ * A stale idle parent is rebuilt through `getOrCreateConversation` first so
14
+ * the injected turn uses the current provider, prompt, and credentials.
15
15
  */
16
16
 
17
+ import { getConfig } from "../config/loader.js";
18
+ import { resolveTurnCommitWaitMs } from "../daemon/abort-watchdog.js";
17
19
  import {
18
20
  findConversation,
19
21
  findConversationOrSubagent,
20
22
  } from "../daemon/conversation-registry.js";
23
+ import { startAfterTurnFinalization } from "../daemon/turn-finalization.js";
24
+ import { deliverSubagentNotificationToLiveVoice } from "../live-voice/live-voice-manager.js";
21
25
  import { getSubagentRecordByConversationId } from "../persistence/subagent-store.js";
22
26
  import { getLogger } from "../util/logger.js";
23
27
  import { type SubagentStatus, TERMINAL_STATUSES } from "./types.js";
@@ -25,9 +29,9 @@ import { type SubagentStatus, TERMINAL_STATUSES } from "./types.js";
25
29
  const log = getLogger("subagent-notify");
26
30
 
27
31
  /**
28
- * Enqueue (or persist + run) `message` as a user-role turn in the parent
29
- * conversation. No-op with a warning when the parent conversation is not live
30
- * in this process.
32
+ * Deliver task updates through the parent's live voice session when present,
33
+ * otherwise enqueue a parent turn. No-op with a warning
34
+ * when neither the session nor the parent conversation is live here.
31
35
  *
32
36
  * Shared by the child-triggered {@link notifyParentFromChild} and the
33
37
  * manager's terminal/abort injections so every subagent → parent turn lands
@@ -37,8 +41,30 @@ export function injectMessageIntoParent(
37
41
  parentConversationId: string,
38
42
  message: string,
39
43
  metadata?: Record<string, unknown>,
40
- opts?: { cronRunId?: string | null },
44
+ opts?: { cronRunId?: string | null; bypassLiveVoice?: boolean },
41
45
  ): void {
46
+ const notification = metadata?.subagentNotification;
47
+ // The live child's conversation ID is stable even if its cosmetic record changes.
48
+ if (
49
+ !opts?.bypassLiveVoice &&
50
+ notification !== null &&
51
+ typeof notification === "object" &&
52
+ "subagentId" in notification &&
53
+ typeof notification.subagentId === "string" &&
54
+ metadata !== undefined &&
55
+ deliverSubagentNotificationToLiveVoice(parentConversationId, {
56
+ taskId:
57
+ "conversationId" in notification &&
58
+ typeof notification.conversationId === "string"
59
+ ? notification.conversationId
60
+ : notification.subagentId,
61
+ message,
62
+ metadata,
63
+ cronRunId: opts?.cronRunId,
64
+ })
65
+ ) {
66
+ return;
67
+ }
42
68
  const existing = findConversation(parentConversationId);
43
69
  if (!existing) {
44
70
  log.warn(
@@ -76,17 +102,10 @@ function deliverToParent(
76
102
  content: string;
77
103
  metadata?: Record<string, unknown>;
78
104
  isInteractive: boolean;
105
+ queueWhenIdle: boolean;
79
106
  cronRunId?: string | null;
80
107
  }) => { queued: boolean; rejected?: boolean };
81
- persistUserMessage: (options: {
82
- content: string;
83
- metadata?: Record<string, unknown>;
84
- }) => Promise<{ id: string }>;
85
- runAgentLoop: (
86
- message: string,
87
- messageId: string,
88
- options: { isInteractive: boolean; cronRunId?: string | null },
89
- ) => Promise<unknown>;
108
+ kickDrainQueue: (reason: "loop_complete", origin: string) => Promise<void>;
90
109
  },
91
110
  parentConversationId: string,
92
111
  message: string,
@@ -95,33 +114,34 @@ function deliverToParent(
95
114
  ): void {
96
115
  // The continuation this notification starts is still the scheduled firing's
97
116
  // work, so it carries the same run id as the child whose result triggered it.
98
- // Both delivery paths need it: the queued one drains after the enqueuing turn
99
- // has ended, so the id has to travel on the message.
117
+ // The queue drains after the enqueuing turn, so the id travels on the message.
100
118
  const cronRunId = opts?.cronRunId ?? null;
101
119
  // Machine-injected with no human asserted present, so the notification
102
120
  // turn runs non-interactive; it still streams to whoever is watching
103
121
  // through the parent's sink.
104
122
  const enqueueResult = parentConversation.enqueueMessage({
105
123
  content: message,
106
- metadata,
124
+ metadata: { ...metadata, scripted: true },
107
125
  isInteractive: false,
126
+ queueWhenIdle: true,
108
127
  ...(cronRunId ? { cronRunId } : {}),
109
128
  });
110
- if (!enqueueResult.queued && !enqueueResult.rejected) {
111
- parentConversation
112
- .persistUserMessage({ content: message, metadata })
113
- .then(({ id: messageId }) =>
114
- parentConversation.runAgentLoop(message, messageId, {
115
- isInteractive: false,
116
- ...(cronRunId ? { cronRunId } : {}),
117
- }),
118
- )
119
- .catch((err) => {
120
- log.error(
121
- { parentConversationId, err },
122
- "Failed to process subagent notification in parent",
129
+ if (enqueueResult.queued) {
130
+ startAfterTurnFinalization(
131
+ parentConversationId,
132
+ resolveTurnCommitWaitMs(getConfig().workspaceGit?.turnCommitMaxWaitMs),
133
+ () => {
134
+ void parentConversation.kickDrainQueue(
135
+ "loop_complete",
136
+ "subagent_notification",
123
137
  );
124
- });
138
+ },
139
+ );
140
+ } else {
141
+ log.error(
142
+ { parentConversationId },
143
+ "Parent queue rejected subagent notification",
144
+ );
125
145
  }
126
146
  }
127
147
 
@@ -0,0 +1,6 @@
1
+ export interface SubagentParentNotification {
2
+ taskId: string;
3
+ message: string;
4
+ metadata: Record<string, unknown>;
5
+ cronRunId?: string | null;
6
+ }
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ import {
4
+ PHONE_CALL_STEPS,
5
+ phoneCallEndScreen,
6
+ phoneCallSilenceReason,
7
+ phoneCallStartScreen,
8
+ } from "../phone-call-funnel.js";
9
+
10
+ describe("phoneCallStartScreen", () => {
11
+ it("stamps the direction and the call mode", () => {
12
+ expect(phoneCallStartScreen("inbound", "normal")).toBe(
13
+ "started_inbound:normal",
14
+ );
15
+ expect(phoneCallStartScreen("outbound", "verification")).toBe(
16
+ "started_outbound:verification",
17
+ );
18
+ });
19
+
20
+ it("reads an unwritten mode as the ordinary call it is", () => {
21
+ // Only verification and invite calls write a mode, so a null there is a
22
+ // normal conversation rather than a dimension nobody filled in.
23
+ expect(phoneCallStartScreen("inbound", null)).toBe(
24
+ "started_inbound:normal",
25
+ );
26
+ expect(phoneCallStartScreen("inbound")).toBe("started_inbound:normal");
27
+ });
28
+
29
+ it("keeps the longest stamp inside the wire field's 64-char bound", () => {
30
+ const longest = phoneCallStartScreen("outbound", "verification");
31
+ expect(longest.length).toBeLessThanOrEqual(64);
32
+ });
33
+ });
34
+
35
+ describe("phoneCallSilenceReason", () => {
36
+ it("blames the connection when the call never reached the caller", () => {
37
+ // Nothing that happens on a live call can be held against one that never
38
+ // connected: nobody was there to speak.
39
+ expect(phoneCallSilenceReason({ connected: false })).toBe("no_connect");
40
+ });
41
+
42
+ it("reports a connected call that took no turn as silence on the line", () => {
43
+ expect(phoneCallSilenceReason({ connected: true })).toBe("no_turn");
44
+ });
45
+ });
46
+
47
+ describe("phoneCallEndScreen", () => {
48
+ it("stamps the terminal status on a call that took a turn", () => {
49
+ expect(phoneCallEndScreen("completed")).toBe("ended_completed");
50
+ expect(phoneCallEndScreen("failed", null)).toBe("ended_failed");
51
+ });
52
+
53
+ it("carries the silence classification when no turn was taken", () => {
54
+ expect(phoneCallEndScreen("failed", "no_connect")).toBe(
55
+ "ended_failed:silent_no_connect",
56
+ );
57
+ expect(phoneCallEndScreen("completed", "no_turn")).toBe(
58
+ "ended_completed:silent_no_turn",
59
+ );
60
+ });
61
+ });
62
+
63
+ describe("PHONE_CALL_STEPS", () => {
64
+ it("orders the pair the warehouse subtracts", () => {
65
+ // Duration is the gap between these two rows, so their order is part of
66
+ // the contract rather than a label.
67
+ expect(PHONE_CALL_STEPS.callStarted.stepIndex).toBe(0);
68
+ expect(PHONE_CALL_STEPS.callEnded.stepIndex).toBe(1);
69
+ expect(PHONE_CALL_STEPS.callStarted.stepName).toBe("phone_call_started");
70
+ expect(PHONE_CALL_STEPS.callEnded.stepName).toBe("phone_call_ended");
71
+ });
72
+ });