@vellumai/assistant 0.4.46 → 0.4.49

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 (382) hide show
  1. package/ARCHITECTURE.md +7 -7
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/security.md +5 -5
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/anthropic-provider.test.ts +156 -0
  12. package/src/__tests__/approval-cascade.test.ts +810 -0
  13. package/src/__tests__/approval-primitive.test.ts +0 -1
  14. package/src/__tests__/approval-routes-http.test.ts +2 -0
  15. package/src/__tests__/assistant-attachments.test.ts +12 -34
  16. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  17. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  18. package/src/__tests__/browser-fill-credential.test.ts +5 -2
  19. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  20. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +2 -1
  21. package/src/__tests__/channel-guardian.test.ts +0 -2
  22. package/src/__tests__/channel-readiness-routes.test.ts +35 -25
  23. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  24. package/src/__tests__/checker.test.ts +9 -29
  25. package/src/__tests__/cli.test.ts +23 -0
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-watcher.test.ts +0 -1
  29. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  30. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  31. package/src/__tests__/context-token-estimator.test.ts +196 -13
  32. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  33. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  34. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  36. package/src/__tests__/credential-broker-browser-fill.test.ts +23 -22
  37. package/src/__tests__/credential-broker-server-use.test.ts +22 -21
  38. package/src/__tests__/credential-broker.test.ts +2 -1
  39. package/src/__tests__/credential-metadata-store.test.ts +239 -26
  40. package/src/__tests__/credential-resolve.test.ts +5 -4
  41. package/src/__tests__/credential-security-e2e.test.ts +8 -8
  42. package/src/__tests__/credential-security-invariants.test.ts +111 -7
  43. package/src/__tests__/credential-vault-unit.test.ts +287 -54
  44. package/src/__tests__/credential-vault.test.ts +406 -12
  45. package/src/__tests__/credentials-cli.test.ts +82 -6
  46. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  47. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  48. package/src/__tests__/gateway-only-enforcement.test.ts +4 -2
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/gemini-image-service.test.ts +75 -45
  51. package/src/__tests__/gemini-provider.test.ts +9 -6
  52. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -33
  53. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -20
  54. package/src/__tests__/guardian-action-followup-executor.test.ts +1 -28
  55. package/src/__tests__/guardian-action-followup-store.test.ts +1 -1
  56. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  57. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  58. package/src/__tests__/guardian-grant-minting.test.ts +35 -0
  59. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  60. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  61. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  62. package/src/__tests__/heartbeat-service.test.ts +0 -1
  63. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  64. package/src/__tests__/host-shell-tool.test.ts +27 -15
  65. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  66. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  67. package/src/__tests__/integration-status.test.ts +38 -25
  68. package/src/__tests__/intent-routing.test.ts +0 -1
  69. package/src/__tests__/invite-routes-http.test.ts +10 -9
  70. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  71. package/src/__tests__/managed-proxy-context.test.ts +5 -3
  72. package/src/__tests__/media-generate-image.test.ts +63 -2
  73. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -3
  74. package/src/__tests__/messaging-send-tool.test.ts +4 -6
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +373 -14
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +756 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-fail-open-selection.test.ts +3 -1
  83. package/src/__tests__/provider-managed-proxy-integration.test.ts +70 -6
  84. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  85. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  86. package/src/__tests__/recording-handler.test.ts +3 -4
  87. package/src/__tests__/registry.test.ts +2 -2
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  91. package/src/__tests__/schema-transforms.test.ts +226 -0
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-injection-runtime.test.ts +23 -13
  95. package/src/__tests__/script-proxy-policy-runtime.test.ts +1 -1
  96. package/src/__tests__/script-proxy-session-manager.test.ts +1 -1
  97. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  98. package/src/__tests__/secret-onetime-send.test.ts +5 -3
  99. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  100. package/src/__tests__/sequence-store.test.ts +0 -1
  101. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  102. package/src/__tests__/session-messaging-secret-redirect.test.ts +5 -4
  103. package/src/__tests__/skill-include-graph.test.ts +66 -0
  104. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  105. package/src/__tests__/skill-load-tool.test.ts +149 -1
  106. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  107. package/src/__tests__/skills-uninstall.test.ts +3 -3
  108. package/src/__tests__/skills.test.ts +3 -12
  109. package/src/__tests__/slack-channel-config.test.ts +76 -11
  110. package/src/__tests__/slack-share-routes.test.ts +17 -14
  111. package/src/__tests__/system-prompt.test.ts +0 -1
  112. package/src/__tests__/telegram-bot-username-resolution.test.ts +3 -0
  113. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  114. package/src/__tests__/terminal-tools.test.ts +4 -3
  115. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  116. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  117. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  118. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  119. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  120. package/src/__tests__/tool-executor.test.ts +0 -1
  121. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  122. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  123. package/src/__tests__/trust-store.test.ts +1 -22
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  126. package/src/__tests__/twilio-config.test.ts +2 -1
  127. package/src/__tests__/twilio-provider.test.ts +4 -2
  128. package/src/__tests__/twilio-routes.test.ts +5 -20
  129. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  130. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  131. package/src/agent/ax-tree-compaction.test.ts +235 -0
  132. package/src/agent/loop.ts +76 -130
  133. package/src/calls/call-domain.ts +8 -10
  134. package/src/calls/relay-server.ts +9 -13
  135. package/src/calls/twilio-config.ts +4 -8
  136. package/src/calls/twilio-provider.ts +2 -1
  137. package/src/calls/twilio-rest.ts +2 -1
  138. package/src/calls/twilio-routes.ts +1 -2
  139. package/src/calls/voice-ingress-preflight.ts +1 -1
  140. package/src/cli/commands/browser-relay.ts +46 -15
  141. package/src/cli/commands/completions.ts +0 -3
  142. package/src/cli/commands/credentials.ts +110 -23
  143. package/src/cli/commands/oauth/apps.ts +255 -0
  144. package/src/cli/commands/oauth/connections.ts +299 -0
  145. package/src/cli/commands/oauth/index.ts +52 -0
  146. package/src/cli/commands/oauth/providers.ts +242 -0
  147. package/src/cli/commands/skills.ts +4 -338
  148. package/src/cli/program.ts +1 -5
  149. package/src/cli/reference.ts +1 -3
  150. package/src/cli.ts +3 -2
  151. package/src/config/assistant-feature-flags.ts +0 -3
  152. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  153. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -4
  154. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  155. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  156. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +29 -32
  157. package/src/config/bundled-skills/gmail/SKILL.md +4 -4
  158. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +54 -61
  159. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +25 -28
  160. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +14 -17
  161. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +39 -44
  162. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +61 -58
  163. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +50 -49
  164. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +11 -13
  165. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +148 -146
  166. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +4 -7
  167. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +175 -173
  168. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +4 -7
  169. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +71 -76
  170. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +32 -38
  171. package/src/config/bundled-skills/google-calendar/SKILL.md +2 -2
  172. package/src/config/bundled-skills/google-calendar/calendar-client.ts +90 -44
  173. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +9 -10
  174. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +5 -6
  175. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +4 -5
  176. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +14 -15
  177. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +37 -37
  178. package/src/config/bundled-skills/google-calendar/tools/shared.ts +4 -9
  179. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +24 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +6 -6
  181. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +62 -63
  182. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +15 -16
  183. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +4 -5
  184. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +6 -7
  185. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +4 -5
  186. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +14 -15
  187. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +4 -5
  188. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +128 -128
  189. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +33 -34
  190. package/src/config/bundled-skills/messaging/tools/shared.ts +12 -15
  191. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  192. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  193. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  194. package/src/config/bundled-skills/slack/tools/shared.ts +4 -10
  195. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +4 -5
  196. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +15 -16
  197. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +4 -5
  198. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +4 -5
  199. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +4 -5
  200. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +95 -92
  201. package/src/config/env-registry.ts +14 -83
  202. package/src/config/env.ts +11 -50
  203. package/src/config/feature-flag-registry.json +16 -16
  204. package/src/config/schema.ts +3 -1
  205. package/src/config/skills.ts +21 -2
  206. package/src/context/image-dimensions.ts +229 -0
  207. package/src/context/token-estimator.ts +75 -12
  208. package/src/context/window-manager.ts +49 -10
  209. package/src/daemon/assistant-attachments.ts +1 -13
  210. package/src/daemon/guardian-action-generators.ts +4 -5
  211. package/src/daemon/handlers/config-ingress.ts +8 -33
  212. package/src/daemon/handlers/config-slack-channel.ts +76 -56
  213. package/src/daemon/handlers/config-telegram.ts +53 -24
  214. package/src/daemon/handlers/sessions.ts +10 -24
  215. package/src/daemon/handlers/shared.ts +0 -130
  216. package/src/daemon/host-cu-proxy.ts +401 -0
  217. package/src/daemon/lifecycle.ts +39 -63
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/computer-use.ts +2 -119
  220. package/src/daemon/message-types/host-cu.ts +19 -0
  221. package/src/daemon/message-types/integrations.ts +1 -0
  222. package/src/daemon/message-types/messages.ts +3 -0
  223. package/src/daemon/server.ts +14 -21
  224. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  225. package/src/daemon/session-attachments.ts +1 -2
  226. package/src/daemon/session-messaging.ts +3 -1
  227. package/src/daemon/session-slash.ts +1 -1
  228. package/src/daemon/session-surfaces.ts +40 -28
  229. package/src/daemon/session-tool-setup.ts +20 -11
  230. package/src/daemon/session.ts +139 -16
  231. package/src/daemon/tool-side-effects.ts +2 -8
  232. package/src/daemon/watch-handler.ts +2 -2
  233. package/src/email/providers/index.ts +2 -1
  234. package/src/events/tool-metrics-listener.ts +2 -2
  235. package/src/hooks/manager.ts +1 -4
  236. package/src/inbound/public-ingress-urls.ts +7 -7
  237. package/src/instrument.ts +15 -1
  238. package/src/logfire.ts +16 -5
  239. package/src/media/app-icon-generator.ts +30 -4
  240. package/src/media/avatar-router.ts +26 -3
  241. package/src/media/gemini-image-service.ts +28 -2
  242. package/src/memory/conversation-key-store.ts +21 -0
  243. package/src/memory/db-init.ts +4 -0
  244. package/src/memory/guardian-action-store.ts +1 -1
  245. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  246. package/src/memory/migrations/index.ts +1 -0
  247. package/src/memory/schema/guardian.ts +1 -1
  248. package/src/memory/schema/index.ts +1 -0
  249. package/src/memory/schema/oauth.ts +65 -0
  250. package/src/messaging/provider.ts +19 -13
  251. package/src/messaging/providers/gmail/adapter.ts +40 -23
  252. package/src/messaging/providers/gmail/client.ts +283 -122
  253. package/src/messaging/providers/gmail/people-client.ts +32 -24
  254. package/src/messaging/providers/slack/adapter.ts +29 -19
  255. package/src/messaging/providers/slack/client.ts +265 -78
  256. package/src/messaging/providers/telegram-bot/adapter.ts +19 -18
  257. package/src/messaging/providers/whatsapp/adapter.ts +17 -11
  258. package/src/messaging/registry.ts +2 -31
  259. package/src/notifications/copy-composer.ts +0 -5
  260. package/src/notifications/signal.ts +4 -5
  261. package/src/oauth/byo-connection.test.ts +537 -0
  262. package/src/oauth/byo-connection.ts +128 -0
  263. package/src/oauth/connect-orchestrator.ts +139 -56
  264. package/src/oauth/connect-types.ts +17 -23
  265. package/src/oauth/connection-resolver.ts +58 -0
  266. package/src/oauth/connection.ts +38 -0
  267. package/src/oauth/manual-token-connection.ts +104 -0
  268. package/src/oauth/oauth-store.ts +496 -0
  269. package/src/oauth/platform-connection.test.ts +192 -0
  270. package/src/oauth/platform-connection.ts +111 -0
  271. package/src/oauth/provider-behaviors.ts +124 -0
  272. package/src/oauth/scope-policy.ts +9 -2
  273. package/src/oauth/seed-providers.ts +161 -0
  274. package/src/oauth/token-persistence.ts +74 -78
  275. package/src/permissions/checker.ts +8 -4
  276. package/src/permissions/defaults.ts +0 -1
  277. package/src/permissions/prompter.ts +10 -1
  278. package/src/permissions/trust-store.ts +13 -0
  279. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  280. package/src/prompts/system-prompt.ts +70 -45
  281. package/src/providers/anthropic/client.ts +133 -24
  282. package/src/providers/gemini/client.ts +15 -6
  283. package/src/providers/managed-proxy/constants.ts +2 -2
  284. package/src/providers/managed-proxy/context.ts +5 -1
  285. package/src/providers/ratelimit.ts +17 -0
  286. package/src/providers/registry.ts +2 -2
  287. package/src/providers/retry.ts +1 -27
  288. package/src/runtime/AGENTS.md +17 -0
  289. package/src/runtime/auth/route-policy.ts +0 -3
  290. package/src/runtime/channel-invite-transports/telegram.ts +2 -1
  291. package/src/runtime/channel-readiness-service.ts +168 -195
  292. package/src/runtime/channel-readiness-types.ts +4 -0
  293. package/src/runtime/channel-reply-delivery.ts +0 -40
  294. package/src/runtime/gateway-client.ts +0 -7
  295. package/src/runtime/guardian-action-conversation-turn.ts +1 -3
  296. package/src/runtime/guardian-action-followup-executor.ts +1 -1
  297. package/src/runtime/guardian-action-message-composer.ts +3 -23
  298. package/src/runtime/http-server.ts +17 -10
  299. package/src/runtime/http-types.ts +2 -3
  300. package/src/runtime/middleware/rate-limiter.ts +74 -20
  301. package/src/runtime/middleware/twilio-validation.ts +1 -11
  302. package/src/runtime/pending-interactions.ts +14 -12
  303. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  304. package/src/runtime/routes/channel-readiness-routes.ts +2 -0
  305. package/src/runtime/routes/conversation-routes.ts +73 -19
  306. package/src/runtime/routes/diagnostics-routes.ts +11 -9
  307. package/src/runtime/routes/events-routes.ts +21 -11
  308. package/src/runtime/routes/guardian-approval-interception.ts +20 -5
  309. package/src/runtime/routes/host-cu-routes.ts +97 -0
  310. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  311. package/src/runtime/routes/integrations/slack/share.ts +6 -6
  312. package/src/runtime/routes/integrations/twilio.ts +6 -5
  313. package/src/runtime/routes/log-export-routes.ts +126 -8
  314. package/src/runtime/routes/secret-routes.ts +3 -2
  315. package/src/runtime/routes/settings-routes.ts +113 -48
  316. package/src/runtime/routes/surface-action-routes.ts +1 -1
  317. package/src/runtime/routes/watch-routes.ts +128 -0
  318. package/src/schedule/integration-status.ts +10 -8
  319. package/src/security/credential-key.ts +14 -0
  320. package/src/security/keychain-broker-client.ts +5 -6
  321. package/src/security/oauth2.ts +1 -1
  322. package/src/security/token-manager.ts +145 -43
  323. package/src/skills/catalog-install.ts +358 -0
  324. package/src/skills/include-graph.ts +32 -0
  325. package/src/telegram/bot-username.ts +2 -3
  326. package/src/tools/apps/definitions.ts +0 -5
  327. package/src/tools/assets/materialize.ts +0 -5
  328. package/src/tools/assets/search.ts +0 -5
  329. package/src/tools/browser/headless-browser.ts +1 -67
  330. package/src/tools/browser/network-recorder.ts +1 -1
  331. package/src/tools/browser/network-recording-types.ts +1 -1
  332. package/src/tools/claude-code/claude-code.ts +0 -5
  333. package/src/tools/computer-use/definitions.ts +46 -11
  334. package/src/tools/computer-use/registry.ts +4 -5
  335. package/src/tools/credentials/broker.ts +5 -4
  336. package/src/tools/credentials/metadata-store.ts +22 -74
  337. package/src/tools/credentials/resolve.ts +2 -1
  338. package/src/tools/credentials/vault.ts +139 -151
  339. package/src/tools/filesystem/edit.ts +1 -6
  340. package/src/tools/filesystem/read.ts +0 -5
  341. package/src/tools/filesystem/write.ts +1 -6
  342. package/src/tools/host-filesystem/edit.ts +1 -6
  343. package/src/tools/host-filesystem/read.ts +1 -6
  344. package/src/tools/host-filesystem/write.ts +1 -6
  345. package/src/tools/mcp/mcp-tool-factory.ts +18 -1
  346. package/src/tools/memory/definitions.ts +0 -5
  347. package/src/tools/network/web-fetch.ts +0 -5
  348. package/src/tools/network/web-search.ts +0 -5
  349. package/src/tools/registry.ts +2 -7
  350. package/src/tools/schema-transforms.ts +99 -0
  351. package/src/tools/skills/load.ts +62 -8
  352. package/src/tools/swarm/delegate.ts +0 -5
  353. package/src/tools/system/avatar-generator.ts +0 -5
  354. package/src/tools/ui-surface/definitions.ts +0 -15
  355. package/src/tools/watch/screen-watch.ts +0 -5
  356. package/src/tools/watch/watch-state.ts +0 -12
  357. package/src/util/logger.ts +7 -41
  358. package/src/util/platform.ts +9 -28
  359. package/src/version.ts +10 -0
  360. package/src/watcher/providers/github.ts +51 -52
  361. package/src/watcher/providers/gmail.ts +88 -80
  362. package/src/watcher/providers/google-calendar.ts +94 -86
  363. package/src/watcher/providers/linear.ts +87 -93
  364. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  365. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  366. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  367. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  368. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  369. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  370. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  371. package/src/cli/commands/dev.ts +0 -129
  372. package/src/cli/commands/map.ts +0 -391
  373. package/src/cli/commands/oauth.ts +0 -77
  374. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  375. package/src/daemon/computer-use-session.ts +0 -1020
  376. package/src/daemon/ride-shotgun-handler.ts +0 -567
  377. package/src/oauth/provider-profiles.ts +0 -192
  378. package/src/prompts/computer-use-prompt.ts +0 -98
  379. package/src/runtime/routes/computer-use-routes.ts +0 -641
  380. package/src/runtime/telegram-streaming-delivery.test.ts +0 -597
  381. package/src/runtime/telegram-streaming-delivery.ts +0 -383
  382. package/src/tools/computer-use/request-computer-control.ts +0 -61
@@ -31,8 +31,6 @@ export interface ChannelReplyPayload {
31
31
  ephemeral?: boolean;
32
32
  /** Slack user ID — required when `ephemeral` is true. */
33
33
  user?: string;
34
- /** Telegram message_id for editing an existing message instead of sending a new one. */
35
- messageId?: number;
36
34
  /** When provided, instructs the delivery endpoint to update an existing message instead of posting a new one. */
37
35
  messageTs?: string;
38
36
  /** When true, auto-generate Block Kit blocks from text via textToBlocks(). */
@@ -45,8 +43,6 @@ export interface ChannelDeliveryResult {
45
43
  ok: boolean;
46
44
  /** The message timestamp returned by the delivery endpoint (e.g. Slack message ts). */
47
45
  ts?: string;
48
- /** The Telegram message_id returned when a new message was sent. */
49
- messageId?: number;
50
46
  }
51
47
 
52
48
  interface ManagedOutboundCallbackContext {
@@ -100,9 +96,6 @@ export async function deliverChannelReply(
100
96
  if (typeof responseBody.ts === "string") {
101
97
  result.ts = responseBody.ts;
102
98
  }
103
- if (typeof responseBody.messageId === "number") {
104
- result.messageId = responseBody.messageId;
105
- }
106
99
  } catch {
107
100
  // Response may not be JSON for non-Slack channels; that's fine.
108
101
  }
@@ -2,12 +2,11 @@
2
2
  * Guardian follow-up conversation engine.
3
3
  *
4
4
  * When a guardian replies to a post-timeout follow-up prompt (e.g. "would you
5
- * like to call them back or send a message?"), this engine classifies the
5
+ * like to call them back?"), this engine classifies the
6
6
  * guardian's intent into a structured disposition and produces a natural reply.
7
7
  *
8
8
  * Dispositions:
9
9
  * - call_back: Guardian wants to call the original caller back
10
- * - message_back: Guardian wants to send a text/message to the caller
11
10
  * - decline: Guardian declines to follow up ("never mind", "no thanks")
12
11
  * - keep_pending: Intent is ambiguous — ask for clarification
13
12
  *
@@ -37,7 +36,6 @@ const FALLBACK_RETRY_TEXT = getGuardianActionFallbackMessage({
37
36
 
38
37
  const VALID_DISPOSITIONS: ReadonlySet<string> = new Set([
39
38
  "call_back",
40
- "message_back",
41
39
  "decline",
42
40
  "keep_pending",
43
41
  ]);
@@ -173,7 +173,7 @@ async function executeCallBack(
173
173
 
174
174
  /**
175
175
  * Execute a follow-up action after the conversation engine has classified
176
- * the guardian's intent as call_back or message_back and the follow-up
176
+ * the guardian's intent as call_back and the follow-up
177
177
  * state has been transitioned to `dispatching`.
178
178
  *
179
179
  * On success: finalizes the follow-up to `completed` and returns
@@ -36,8 +36,6 @@ export type GuardianActionMessageScenario =
36
36
  | "guardian_superseded_remap"
37
37
  | "guardian_unknown_code"
38
38
  | "guardian_auto_matched"
39
- | "outbound_message_copy"
40
- | "followup_message_sent"
41
39
  | "followup_call_started"
42
40
  | "followup_action_failed"
43
41
  | "guardian_answer_delivery_failed";
@@ -183,8 +181,8 @@ export function getGuardianActionFallbackMessage(
183
181
 
184
182
  case "guardian_late_answer_followup":
185
183
  return context.callerIdentifier
186
- ? `${context.callerIdentifier} called earlier with a question, but I wasn't able to connect them. Would you like to call them back or send them a message?`
187
- : "Someone called earlier with a question, but I wasn't able to connect them. Would you like to call them back or send them a message?";
184
+ ? `${context.callerIdentifier} called earlier with a question, but I wasn't able to connect them. Would you like to call them back?`
185
+ : "Someone called earlier with a question, but I wasn't able to connect them. Would you like to call them back?";
188
186
 
189
187
  case "guardian_followup_dispatching":
190
188
  return context.followupAction
@@ -205,7 +203,7 @@ export function getGuardianActionFallbackMessage(
205
203
  return "No problem. Let me know if you change your mind or need anything else.";
206
204
 
207
205
  case "guardian_followup_clarification":
208
- return "Sorry, I didn't quite catch that. Would you like to call them back, send them a message, or skip it for now?";
206
+ return "Sorry, I didn't quite catch that. Would you like to call them back or skip it for now?";
209
207
 
210
208
  case "guardian_pending_disambiguation":
211
209
  return listedCodes
@@ -247,24 +245,6 @@ export function getGuardianActionFallbackMessage(
247
245
  ? `Got it! Your answer has been applied to the current active request: "${context.questionText}"`
248
246
  : "Got it! Your answer has been applied to the current active request on the call.";
249
247
 
250
- case "outbound_message_copy":
251
- // This message is sent TO the original caller relaying the guardian's answer.
252
- // When lateAnswerText is available, include it — that's the whole point of message_back.
253
- if (context.lateAnswerText && context.questionText) {
254
- return `Hi! You asked "${context.questionText}" earlier. Here's the answer: ${context.lateAnswerText}`;
255
- }
256
- if (context.lateAnswerText) {
257
- return `Hi! Regarding your earlier question — here's the answer: ${context.lateAnswerText}`;
258
- }
259
- return context.questionText
260
- ? `Hi! You asked "${context.questionText}" earlier. We'll get back to you with an answer soon.`
261
- : "Hi! Thanks for calling earlier. We'll get back to you soon.";
262
-
263
- case "followup_message_sent":
264
- return context.counterpartyPhone
265
- ? `Done! I've sent a text message to ${context.counterpartyPhone} with your answer.`
266
- : "Done! I've sent them a text message with your answer.";
267
-
268
248
  case "followup_call_started":
269
249
  return context.counterpartyPhone
270
250
  ? `Got it! I'm calling ${context.counterpartyPhone} back now to relay your answer.`
@@ -110,7 +110,6 @@ import {
110
110
  stopGuardianExpirySweep,
111
111
  } from "./routes/channel-routes.js";
112
112
  import { channelVerificationRouteDefinitions } from "./routes/channel-verification-routes.js";
113
- import { computerUseRouteDefinitions } from "./routes/computer-use-routes.js";
114
113
  import {
115
114
  contactCatchAllRouteDefinitions,
116
115
  contactRouteDefinitions,
@@ -126,6 +125,7 @@ import { guardianActionRouteDefinitions } from "./routes/guardian-action-routes.
126
125
  import { handleGuardianBootstrap } from "./routes/guardian-bootstrap-routes.js";
127
126
  import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
128
127
  import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
128
+ import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
129
129
  import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
130
130
  import { handleHealth } from "./routes/identity-routes.js";
131
131
  import { identityRouteDefinitions } from "./routes/identity-routes.js";
@@ -155,6 +155,7 @@ import { surfaceActionRouteDefinitions } from "./routes/surface-action-routes.js
155
155
  import { surfaceContentRouteDefinitions } from "./routes/surface-content-routes.js";
156
156
  import { trustRulesRouteDefinitions } from "./routes/trust-rules-routes.js";
157
157
  import { usageRouteDefinitions } from "./routes/usage-routes.js";
158
+ import { watchRouteDefinitions } from "./routes/watch-routes.js";
158
159
  import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
159
160
  import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
160
161
 
@@ -216,7 +217,7 @@ export class RuntimeHttpServer {
216
217
  private getSkillContext?: RuntimeHttpServerOptions["getSkillContext"];
217
218
  private sessionManagementDeps?: RuntimeHttpServerOptions["sessionManagementDeps"];
218
219
  private getModelSetContext?: RuntimeHttpServerOptions["getModelSetContext"];
219
- private getComputerUseDeps?: RuntimeHttpServerOptions["getComputerUseDeps"];
220
+ private getWatchDeps?: RuntimeHttpServerOptions["getWatchDeps"];
220
221
  private getRecordingDeps?: RuntimeHttpServerOptions["getRecordingDeps"];
221
222
  private router: HttpRouter;
222
223
 
@@ -237,7 +238,7 @@ export class RuntimeHttpServer {
237
238
  this.getSkillContext = options.getSkillContext;
238
239
  this.sessionManagementDeps = options.sessionManagementDeps;
239
240
  this.getModelSetContext = options.getModelSetContext;
240
- this.getComputerUseDeps = options.getComputerUseDeps;
241
+ this.getWatchDeps = options.getWatchDeps;
241
242
  this.getRecordingDeps = options.getRecordingDeps;
242
243
  this.router = new HttpRouter(this.buildRouteTable());
243
244
  }
@@ -532,11 +533,16 @@ export class RuntimeHttpServer {
532
533
  if (!isHttpAuthDisabled()) {
533
534
  const clientIp = extractClientIp(req, server);
534
535
  const token = extractBearerToken(req);
535
- const result = token
536
- ? apiRateLimiter.check(clientIp)
537
- : ipRateLimiter.check(clientIp);
536
+ const limiter = token ? apiRateLimiter : ipRateLimiter;
537
+ const limiterKind = token ? "authenticated" : "unauthenticated";
538
+ const result = limiter.check(clientIp, path);
538
539
  if (!result.allowed) {
539
- return rateLimitResponse(result);
540
+ return rateLimitResponse(result, {
541
+ clientIp,
542
+ deniedPath: path,
543
+ limiterKind: limiterKind as "authenticated" | "unauthenticated",
544
+ pathCounts: limiter.getRecentPathCounts(clientIp),
545
+ });
540
546
  }
541
547
  const routerResponse = await this.router.dispatch(
542
548
  endpoint,
@@ -941,6 +947,7 @@ export class RuntimeHttpServer {
941
947
  ...globalSearchRouteDefinitions(),
942
948
  ...approvalRouteDefinitions(),
943
949
  ...hostBashRouteDefinitions(),
950
+ ...hostCuRouteDefinitions(),
944
951
  ...hostFileRouteDefinitions(),
945
952
  ...(this.getSkillContext
946
953
  ? skillRouteDefinitions({
@@ -968,9 +975,9 @@ export class RuntimeHttpServer {
968
975
  ...channelReadinessRouteDefinitions(),
969
976
  ...attachmentRouteDefinitions(),
970
977
 
971
- ...(this.getComputerUseDeps
972
- ? computerUseRouteDefinitions({
973
- getComputerUseDeps: this.getComputerUseDeps,
978
+ ...(this.getWatchDeps
979
+ ? watchRouteDefinitions({
980
+ getWatchDeps: this.getWatchDeps,
974
981
  })
975
982
  : []),
976
983
  ...(this.getRecordingDeps
@@ -83,7 +83,6 @@ export type GuardianActionCopyGenerator = (
83
83
  /** The disposition returned by the guardian follow-up conversation engine. */
84
84
  export type GuardianFollowUpDisposition =
85
85
  | "call_back"
86
- | "message_back"
87
86
  | "decline"
88
87
  | "keep_pending";
89
88
 
@@ -220,8 +219,8 @@ export interface RuntimeHttpServerOptions {
220
219
  sessionManagementDeps?: SessionManagementDeps;
221
220
  /** Lazy factory for model config set context (session eviction, config reload suppression). */
222
221
  getModelSetContext?: () => import("../daemon/handlers/config-model.js").ModelSetContext;
223
- /** Provider for computer-use session dependencies (CU routes). */
224
- getComputerUseDeps?: () => import("./routes/computer-use-routes.js").ComputerUseDeps;
222
+ /** Provider for watch observation dependencies (watch routes). */
223
+ getWatchDeps?: () => import("./routes/watch-routes.js").WatchDeps;
225
224
  /** Provider for recording dependencies (recording routes). */
226
225
  getRecordingDeps?: () => import("./routes/recording-routes.js").RecordingDeps;
227
226
  }
@@ -2,10 +2,13 @@
2
2
  // Tracks request counts per key and returns 429 when the limit is exceeded.
3
3
  // Follows the same sliding-window pattern as gateway/src/auth-rate-limiter.ts.
4
4
 
5
+ import { getLogger } from "../../util/logger.js";
5
6
  import type { HttpErrorResponse } from "../http-errors.js";
6
7
  import { isPrivateAddress } from "./auth.js";
7
8
 
8
- const DEFAULT_MAX_REQUESTS = 60;
9
+ const log = getLogger("rate-limiter");
10
+
11
+ const DEFAULT_MAX_REQUESTS = 300;
9
12
  const DEFAULT_WINDOW_MS = 60_000; // 60 seconds
10
13
  const MAX_TRACKED_TOKENS = 10_000;
11
14
 
@@ -14,8 +17,13 @@ const DEFAULT_IP_MAX_REQUESTS = 20;
14
17
  const DEFAULT_IP_WINDOW_MS = 60_000;
15
18
  const MAX_TRACKED_IPS = 50_000;
16
19
 
20
+ interface RequestEntry {
21
+ timestamp: number;
22
+ path: string;
23
+ }
24
+
17
25
  export class TokenRateLimiter {
18
- private requests = new Map<string, number[]>();
26
+ private requests = new Map<string, RequestEntry[]>();
19
27
  private readonly maxRequests: number;
20
28
  private readonly windowMs: number;
21
29
  private readonly maxTrackedKeys: number;
@@ -34,11 +42,11 @@ export class TokenRateLimiter {
34
42
  * Check whether the request should be allowed and record it.
35
43
  * Returns rate limit metadata for response headers.
36
44
  */
37
- check(key: string): RateLimitResult {
45
+ check(key: string, path?: string): RateLimitResult {
38
46
  const now = Date.now();
39
- let timestamps = this.requests.get(key);
47
+ let entries = this.requests.get(key);
40
48
 
41
- if (!timestamps) {
49
+ if (!entries) {
42
50
  if (this.requests.size >= this.maxTrackedKeys) {
43
51
  this.evictStale(now);
44
52
  if (this.requests.size >= this.maxTrackedKeys) {
@@ -46,24 +54,24 @@ export class TokenRateLimiter {
46
54
  if (oldest !== undefined) this.requests.delete(oldest);
47
55
  }
48
56
  }
49
- timestamps = [];
50
- this.requests.set(key, timestamps);
57
+ entries = [];
58
+ this.requests.set(key, entries);
51
59
  }
52
60
 
53
61
  const cutoff = now - this.windowMs;
54
62
 
55
- // Remove expired timestamps from the front
56
- while (timestamps.length > 0 && timestamps[0] <= cutoff) {
57
- timestamps.shift();
63
+ // Remove expired entries from the front
64
+ while (entries.length > 0 && entries[0].timestamp <= cutoff) {
65
+ entries.shift();
58
66
  }
59
67
 
60
- const remaining = Math.max(0, this.maxRequests - timestamps.length);
68
+ const remaining = Math.max(0, this.maxRequests - entries.length);
61
69
  const resetAt =
62
- timestamps.length > 0
63
- ? Math.ceil((timestamps[0] + this.windowMs) / 1000)
70
+ entries.length > 0
71
+ ? Math.ceil((entries[0].timestamp + this.windowMs) / 1000)
64
72
  : Math.ceil((now + this.windowMs) / 1000);
65
73
 
66
- if (timestamps.length >= this.maxRequests) {
74
+ if (entries.length >= this.maxRequests) {
67
75
  return {
68
76
  allowed: false,
69
77
  limit: this.maxRequests,
@@ -72,7 +80,7 @@ export class TokenRateLimiter {
72
80
  };
73
81
  }
74
82
 
75
- timestamps.push(now);
83
+ entries.push({ timestamp: now, path: path ?? "unknown" });
76
84
 
77
85
  return {
78
86
  allowed: true,
@@ -82,13 +90,36 @@ export class TokenRateLimiter {
82
90
  };
83
91
  }
84
92
 
93
+ /**
94
+ * Return a count of recent requests grouped by path for the given key.
95
+ * Sorted descending by count. Useful for diagnosing which endpoints
96
+ * are consuming the rate limit budget.
97
+ */
98
+ getRecentPathCounts(key: string): Array<{ path: string; count: number }> {
99
+ const entries = this.requests.get(key);
100
+ if (!entries || entries.length === 0) return [];
101
+
102
+ const now = Date.now();
103
+ const cutoff = now - this.windowMs;
104
+ const counts = new Map<string, number>();
105
+ for (const entry of entries) {
106
+ if (entry.timestamp > cutoff) {
107
+ counts.set(entry.path, (counts.get(entry.path) ?? 0) + 1);
108
+ }
109
+ }
110
+
111
+ return Array.from(counts.entries())
112
+ .map(([path, count]) => ({ path, count }))
113
+ .sort((a, b) => b.count - a.count);
114
+ }
115
+
85
116
  private evictStale(now: number): void {
86
117
  const cutoff = now - this.windowMs;
87
- for (const [key, timestamps] of this.requests) {
88
- while (timestamps.length > 0 && timestamps[0] <= cutoff) {
89
- timestamps.shift();
118
+ for (const [key, entries] of this.requests) {
119
+ while (entries.length > 0 && entries[0].timestamp <= cutoff) {
120
+ entries.shift();
90
121
  }
91
- if (timestamps.length === 0) {
122
+ if (entries.length === 0) {
92
123
  this.requests.delete(key);
93
124
  }
94
125
  }
@@ -115,8 +146,31 @@ export function rateLimitHeaders(
115
146
  }
116
147
 
117
148
  /** Return a 429 response with rate limit headers and a Retry-After hint. */
118
- export function rateLimitResponse(result: RateLimitResult): Response {
149
+ export function rateLimitResponse(
150
+ result: RateLimitResult,
151
+ diagnostics?: {
152
+ clientIp: string;
153
+ deniedPath: string;
154
+ limiterKind: "authenticated" | "unauthenticated";
155
+ pathCounts: Array<{ path: string; count: number }>;
156
+ },
157
+ ): Response {
119
158
  const retryAfter = Math.max(1, result.resetAt - Math.ceil(Date.now() / 1000));
159
+
160
+ if (diagnostics) {
161
+ log.warn(
162
+ {
163
+ clientIp: diagnostics.clientIp,
164
+ deniedPath: diagnostics.deniedPath,
165
+ limiterKind: diagnostics.limiterKind,
166
+ limit: result.limit,
167
+ retryAfterSec: retryAfter,
168
+ recentRequests: diagnostics.pathCounts,
169
+ },
170
+ `Rate limited ${diagnostics.limiterKind} request: ${diagnostics.deniedPath} (${result.limit} req/min exceeded)`,
171
+ );
172
+ }
173
+
120
174
  const body: HttpErrorResponse = {
121
175
  error: { code: "RATE_LIMITED", message: "Too Many Requests" },
122
176
  };
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { TwilioConversationRelayProvider } from "../../calls/twilio-provider.js";
6
- import { isTwilioWebhookValidationDisabled } from "../../config/env.js";
7
6
  import { loadConfig } from "../../config/loader.js";
8
7
  import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
9
8
  import { getLogger } from "../../util/logger.js";
@@ -51,22 +50,13 @@ export const GATEWAY_ONLY_BLOCKED_SUBPATHS = new Set([
51
50
  * Returns a 403 Response if signature validation fails.
52
51
  *
53
52
  * Fail-closed: if the auth token is not configured, the request is rejected
54
- * with 403 rather than silently skipping validation. An explicit local-dev
55
- * bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
53
+ * with 403 rather than silently skipping validation.
56
54
  */
57
55
  export async function validateTwilioWebhook(
58
56
  req: Request,
59
57
  ): Promise<{ body: string } | Response> {
60
58
  const rawBody = await req.text();
61
59
 
62
- // Allow explicit local-dev bypass -- must be exactly "true"
63
- if (isTwilioWebhookValidationDisabled()) {
64
- log.warn(
65
- "Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED",
66
- );
67
- return { body: rawBody };
68
- }
69
-
70
60
  const authToken = TwilioConversationRelayProvider.getAuthToken();
71
61
 
72
62
  if (!authToken) {
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * In-memory tracker that maps requestId to session info for pending
3
- * confirmation, secret, host_bash, and host_file interactions.
3
+ * confirmation, secret, host_bash, host_file, and host_cu interactions.
4
4
  *
5
5
  * When the agent loop emits a confirmation_request, secret_request,
6
- * host_bash_request, or host_file_request, the onEvent callback registers
7
- * the interaction here. Standalone HTTP endpoints (/v1/confirm, /v1/secret,
8
- * /v1/trust-rules, /v1/host-bash-result, /v1/host-file-result) look up
9
- * the session from this tracker to resolve the interaction.
6
+ * host_bash_request, host_file_request, or host_cu_request, the onEvent
7
+ * callback registers the interaction here. Standalone HTTP endpoints
8
+ * (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
9
+ * /v1/host-file-result, /v1/host-cu-result) look up the session from
10
+ * this tracker to resolve the interaction.
10
11
  */
11
12
 
12
13
  import type { Session } from "../daemon/session.js";
@@ -29,7 +30,7 @@ export interface ConfirmationDetails {
29
30
  export interface PendingInteraction {
30
31
  session: Session;
31
32
  conversationId: string;
32
- kind: "confirmation" | "secret" | "host_bash" | "host_file";
33
+ kind: "confirmation" | "secret" | "host_bash" | "host_file" | "host_cu";
33
34
  confirmationDetails?: ConfirmationDetails;
34
35
  }
35
36
 
@@ -82,19 +83,20 @@ export function getByConversation(
82
83
  * Remove pending confirmation and secret interactions for a given session.
83
84
  * Used when auto-denying all pending interactions (e.g. new user message).
84
85
  *
85
- * host_bash and host_file interactions are intentionally skipped — they
86
- * represent in-flight tool executions proxied to the client, not
86
+ * host_bash, host_file, and host_cu interactions are intentionally skipped
87
+ * — they represent in-flight tool executions proxied to the client, not
87
88
  * confirmations to auto-deny. Removing them would orphan the request: the
88
- * client would POST to /v1/host-bash-result or /v1/host-file-result after
89
- * completing the operation, get a 404, and the proxy timer would fire with
90
- * a spurious timeout error.
89
+ * client would POST to /v1/host-bash-result, /v1/host-file-result, or
90
+ * /v1/host-cu-result after completing the operation, get a 404, and the
91
+ * proxy timer would fire with a spurious timeout error.
91
92
  */
92
93
  export function removeBySession(session: Session): void {
93
94
  for (const [requestId, interaction] of pending) {
94
95
  if (
95
96
  interaction.session === session &&
96
97
  interaction.kind !== "host_bash" &&
97
- interaction.kind !== "host_file"
98
+ interaction.kind !== "host_file" &&
99
+ interaction.kind !== "host_cu"
98
100
  ) {
99
101
  pending.delete(requestId);
100
102
  }
@@ -5,7 +5,6 @@
5
5
  import * as deliveryStatus from "../../memory/delivery-status.js";
6
6
  import { httpError } from "../http-errors.js";
7
7
  export {
8
- deliverAttachmentsOnly,
9
8
  type DeliverReplyOptions,
10
9
  deliverReplyViaCallback,
11
10
  } from "../channel-reply-delivery.js";
@@ -38,6 +38,7 @@ export async function handleGetChannelReadiness(url: URL): Promise<Response> {
38
38
  return {
39
39
  channel: s.channel,
40
40
  ready: s.ready,
41
+ setupStatus: s.setupStatus,
41
42
  checkedAt: s.checkedAt,
42
43
  stale: s.stale,
43
44
  reasons: s.reasons,
@@ -91,6 +92,7 @@ export async function handleRefreshChannelReadiness(
91
92
  return {
92
93
  channel: s.channel,
93
94
  ready: s.ready,
95
+ setupStatus: s.setupStatus,
94
96
  checkedAt: s.checkedAt,
95
97
  stale: s.stale,
96
98
  reasons: s.reasons,
@@ -17,6 +17,7 @@ import {
17
17
  import { getConfig } from "../../config/loader.js";
18
18
  import { renderHistoryContent } from "../../daemon/handlers/shared.js";
19
19
  import { HostBashProxy } from "../../daemon/host-bash-proxy.js";
20
+ import { HostCuProxy } from "../../daemon/host-cu-proxy.js";
20
21
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
21
22
  import type { ServerMessage } from "../../daemon/message-protocol.js";
22
23
  import {
@@ -449,6 +450,12 @@ function makeHubPublisher(
449
450
  conversationId,
450
451
  kind: "host_file",
451
452
  });
453
+ } else if (msg.type === "host_cu_request") {
454
+ pendingInteractions.register(msg.requestId, {
455
+ session,
456
+ conversationId,
457
+ kind: "host_cu",
458
+ });
452
459
  }
453
460
 
454
461
  // ServerMessage is a large union; sessionId exists on most but not all variants.
@@ -640,9 +647,14 @@ export async function handleSendMessage(
640
647
  });
641
648
  session.setHostFileProxy(fileProxy);
642
649
  }
650
+ if (!session.isProcessing() || !session.hostCuProxy) {
651
+ const cuProxy = new HostCuProxy(onEvent);
652
+ session.setHostCuProxy(cuProxy);
653
+ }
643
654
  } else if (!session.isProcessing()) {
644
655
  session.setHostBashProxy(undefined);
645
656
  session.setHostFileProxy(undefined);
657
+ session.setHostCuProxy(undefined);
646
658
  }
647
659
  // Wire sendToClient to the SSE hub so all subsystems can reach the HTTP client.
648
660
  // Called after setHostBashProxy so updateSender targets the current proxy.
@@ -679,7 +691,13 @@ export async function handleSendMessage(
679
691
  attachments,
680
692
  session,
681
693
  onEvent,
682
- approvalConversationGenerator: deps.approvalConversationGenerator,
694
+ // Desktop path: disable NL classification to avoid consuming non-decision
695
+ // messages while a tool confirmation is pending. Deterministic code-prefix
696
+ // and callback parsing remain active. Mirrors session-process.ts behavior.
697
+ approvalConversationGenerator:
698
+ sourceChannel === "vellum"
699
+ ? undefined
700
+ : deps.approvalConversationGenerator,
683
701
  verifiedActorExternalUserId,
684
702
  verifiedActorPrincipalId,
685
703
  });
@@ -687,6 +705,7 @@ export async function handleSendMessage(
687
705
  return Response.json(
688
706
  {
689
707
  accepted: true,
708
+ conversationId: mapping.conversationId,
690
709
  ...(inlineReplyResult.messageId
691
710
  ? { messageId: inlineReplyResult.messageId }
692
711
  : {}),
@@ -751,7 +770,10 @@ export async function handleSendMessage(
751
770
  pendingInteractions.removeBySession(session);
752
771
  }
753
772
 
754
- return Response.json({ accepted: true, queued: true }, { status: 202 });
773
+ return Response.json(
774
+ { accepted: true, queued: true, conversationId: mapping.conversationId },
775
+ { status: 202 },
776
+ );
755
777
  }
756
778
 
757
779
  // Session is idle — persist and fire agent loop immediately
@@ -782,6 +804,7 @@ export async function handleSendMessage(
782
804
 
783
805
  if (slashResult.kind === "unknown") {
784
806
  session.processing = true;
807
+ let cleanupDeferred = false;
785
808
  try {
786
809
  const provenance = provenanceFromTrustContext(session.trustContext);
787
810
  const channelMeta = {
@@ -818,26 +841,54 @@ export async function handleSendMessage(
818
841
  sourceInterface,
819
842
  );
820
843
 
821
- // Emit fresh model info before the text delta so the client has
822
- // up-to-date configuredProviders when rendering /model, /models,
823
- // and provider shortcut commands (/gpt4, /opus, etc.).
824
- if (isModelSlashCommand(rawContent) || isProviderShortcut(rawContent)) {
825
- onEvent(buildModelInfoEvent());
826
- }
827
-
828
- onEvent({ type: "assistant_text_delta", text: slashResult.message });
829
- onEvent({
830
- type: "message_complete",
831
- sessionId: mapping.conversationId,
832
- });
844
+ // Snapshot model info now so the deferred callback cannot observe
845
+ // a config change from a concurrent request.
846
+ const modelInfoEvent =
847
+ isModelSlashCommand(rawContent) || isProviderShortcut(rawContent)
848
+ ? buildModelInfoEvent()
849
+ : null;
833
850
 
834
- return Response.json(
835
- { accepted: true, messageId: persisted.id },
851
+ const response = Response.json(
852
+ {
853
+ accepted: true,
854
+ messageId: persisted.id,
855
+ conversationId: mapping.conversationId,
856
+ },
836
857
  { status: 202 },
837
858
  );
859
+
860
+ // Defer event publishing to next tick so the HTTP response reaches the
861
+ // client first. This ensures the client's serverToLocalSessionMap is
862
+ // populated before SSE events arrive, preventing dropped events in new
863
+ // desktop threads.
864
+ //
865
+ // session.processing and drainQueue are also deferred so the current
866
+ // slash command's events are emitted before the next queued message
867
+ // starts processing.
868
+ const conversationId = mapping.conversationId;
869
+ const message = slashResult.message;
870
+ setTimeout(() => {
871
+ if (modelInfoEvent) {
872
+ onEvent(modelInfoEvent);
873
+ }
874
+ onEvent({ type: "assistant_text_delta", text: message });
875
+ onEvent({
876
+ type: "message_complete",
877
+ sessionId: conversationId,
878
+ });
879
+ session.processing = false;
880
+ session.drainQueue().catch(() => {});
881
+ }, 0);
882
+
883
+ cleanupDeferred = true;
884
+ return response;
838
885
  } finally {
839
- session.processing = false;
840
- session.drainQueue().catch(() => {});
886
+ // No-op for the slash-command early-return path (handled inside
887
+ // setTimeout above), but still needed for error paths.
888
+ if (!cleanupDeferred && session.processing) {
889
+ session.processing = false;
890
+ session.drainQueue().catch(() => {});
891
+ }
841
892
  }
842
893
  }
843
894
 
@@ -874,7 +925,10 @@ export async function handleSendMessage(
874
925
  );
875
926
  });
876
927
 
877
- return Response.json({ accepted: true, messageId }, { status: 202 });
928
+ return Response.json(
929
+ { accepted: true, messageId, conversationId: mapping.conversationId },
930
+ { status: 202 },
931
+ );
878
932
  }
879
933
 
880
934
  async function generateLlmSuggestion(