@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Route handler for resolving pending question prompts.
3
+ *
4
+ * POST /v1/question-response — a client (UI or remote channel) submits the
5
+ * user's selection for a pending ask-question interaction registered by
6
+ * {@link QuestionPrompter}. Two top-level shapes are accepted:
7
+ *
8
+ * - `kind: "submit"` carries a `responses` array — one entry per question in
9
+ * the original batch. The web client builds this locally and POSTs it once
10
+ * the user is done revising the card.
11
+ * - `kind: "close"` records that the user dismissed the card without
12
+ * answering; every entry is reported as `skipped`.
13
+ *
14
+ * For backwards-compat we also accept the prior single-question shape
15
+ * (`{ kind: "option" | "free_text", ... }`) as syntactic sugar for a
16
+ * one-element batch. That branch only succeeds against a single-question
17
+ * batch — multi-question batches reject it with a helpful error.
18
+ *
19
+ * Cross-talk safety: pending interactions of other kinds (`confirmation`,
20
+ * `secret`, host_*, etc.) return 404 here rather than being mis-resolved.
21
+ */
22
+ import { z } from "zod";
23
+
24
+ import {
25
+ buildBatchEntries,
26
+ type QuestionBatchMetadata,
27
+ type QuestionBatchSubmission,
28
+ QuestionBatchValidationError,
29
+ type QuestionPromptResult,
30
+ } from "../../permissions/question-prompter.js";
31
+ import { getLogger } from "../../util/logger.js";
32
+ import * as pendingInteractions from "../pending-interactions.js";
33
+ import { BadRequestError, NotFoundError } from "./errors.js";
34
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
35
+
36
+ const log = getLogger("question-routes");
37
+
38
+ // ── Batched (current) body shape ────────────────────────────────────
39
+
40
+ const SubmitEntry = z.discriminatedUnion("kind", [
41
+ z.object({
42
+ questionId: z.string(),
43
+ kind: z.literal("option"),
44
+ optionId: z.string(),
45
+ }),
46
+ z.object({
47
+ questionId: z.string(),
48
+ kind: z.literal("free_text"),
49
+ text: z.string(),
50
+ }),
51
+ z.object({
52
+ questionId: z.string(),
53
+ kind: z.literal("skip"),
54
+ }),
55
+ ]);
56
+
57
+ const SubmitBody = z.object({
58
+ requestId: z.string(),
59
+ kind: z.literal("submit"),
60
+ responses: z.array(SubmitEntry).min(1),
61
+ });
62
+
63
+ const CloseBody = z.object({
64
+ requestId: z.string(),
65
+ kind: z.literal("close"),
66
+ });
67
+
68
+ // ── Legacy single-question body shape (sugar for one-element batch) ──
69
+
70
+ const LegacyOptionBody = z.object({
71
+ requestId: z.string(),
72
+ kind: z.literal("option"),
73
+ optionId: z.string(),
74
+ });
75
+
76
+ const LegacyFreeTextBody = z.object({
77
+ requestId: z.string(),
78
+ kind: z.literal("free_text"),
79
+ text: z.string(),
80
+ });
81
+
82
+ // All four variants are mutually exclusive by their `kind` literal, so use
83
+ // `discriminatedUnion` rather than plain `union`. The generated OpenAPI then
84
+ // emits `oneOf` for the body (matching the pre-batched-shape spec) instead
85
+ // of the looser `anyOf` that `z.union` produces.
86
+ const QuestionResponseBody = z.discriminatedUnion("kind", [
87
+ SubmitBody,
88
+ CloseBody,
89
+ LegacyOptionBody,
90
+ LegacyFreeTextBody,
91
+ ]);
92
+
93
+ type SubmitBody = z.infer<typeof SubmitBody>;
94
+ type CloseBody = z.infer<typeof CloseBody>;
95
+ type LegacyOptionBody = z.infer<typeof LegacyOptionBody>;
96
+ type LegacyFreeTextBody = z.infer<typeof LegacyFreeTextBody>;
97
+ type QuestionResponseBody = z.infer<typeof QuestionResponseBody>;
98
+
99
+ /**
100
+ * POST /v1/question-response — resolve a pending ask-question interaction.
101
+ */
102
+ function handleQuestionResponse({ body }: RouteHandlerArgs) {
103
+ const parsed = QuestionResponseBody.safeParse(body);
104
+ if (!parsed.success) {
105
+ throw new BadRequestError(
106
+ `Invalid question response body: ${parsed.error.message}`,
107
+ );
108
+ }
109
+
110
+ const response: QuestionResponseBody = parsed.data;
111
+ const { requestId } = response;
112
+
113
+ const interaction = pendingInteractions.get(requestId);
114
+ if (!interaction || interaction.kind !== "question") {
115
+ log.warn(
116
+ { requestId, foundKind: interaction?.kind },
117
+ "Question response for unknown or wrong-kind requestId",
118
+ );
119
+ throw new NotFoundError(
120
+ "No pending question interaction found for this requestId",
121
+ );
122
+ }
123
+
124
+ // Build + validate the result BEFORE touching `pendingInteractions`, so a
125
+ // bad payload leaves the pending interaction (and its timer) intact and the
126
+ // user gets another chance to submit a correct batch.
127
+ let result: QuestionPromptResult;
128
+ try {
129
+ if (response.kind === "close") {
130
+ const { orderedIds } = readBatchMetadata(interaction);
131
+ result = {
132
+ entries: orderedIds.map((id) => ({
133
+ questionId: id,
134
+ decision: "skipped" as const,
135
+ })),
136
+ overall: "closed",
137
+ };
138
+ } else {
139
+ const submissions = buildSubmissions(response, interaction);
140
+ result = buildCompletedResult(submissions, interaction);
141
+ }
142
+ } catch (err) {
143
+ if (err instanceof QuestionBatchValidationError) {
144
+ throw new BadRequestError(err.message);
145
+ }
146
+ throw err;
147
+ }
148
+
149
+ // Validation passed — deregister now to clear the prompter timer, then
150
+ // hand the result to the prompter's caller via rpcResolve.
151
+ pendingInteractions.resolve(requestId);
152
+
153
+ log.info(
154
+ {
155
+ requestId,
156
+ overall: result.overall,
157
+ conversationId: interaction.conversationId,
158
+ },
159
+ "Question resolved",
160
+ );
161
+
162
+ (interaction.rpcResolve as
163
+ | ((value: QuestionPromptResult) => void)
164
+ | undefined)?.(result);
165
+
166
+ return { success: true };
167
+ }
168
+
169
+ /**
170
+ * Normalize the incoming body to a `QuestionBatchSubmission[]` for the
171
+ * submit/legacy paths. Returns `null` for the `close` path (no submissions).
172
+ */
173
+ function buildSubmissions(
174
+ body: SubmitBody | LegacyOptionBody | LegacyFreeTextBody,
175
+ interaction: ReturnType<typeof pendingInteractions.get>,
176
+ ): QuestionBatchSubmission[] {
177
+ if (body.kind === "submit") return body.responses;
178
+
179
+ // Legacy single-question shim: synthesize a one-element batch. The
180
+ // prompter stashed the ordered ids on the interaction metadata so we can
181
+ // pick the (single) target questionId here.
182
+ const { orderedIds } = readBatchMetadata(interaction);
183
+ if (orderedIds.length === 0) {
184
+ throw new QuestionBatchValidationError(
185
+ "Legacy single-question payload requires a registered batch with at least one question",
186
+ );
187
+ }
188
+ if (orderedIds.length > 1) {
189
+ throw new QuestionBatchValidationError(
190
+ 'Legacy single-question payload cannot answer a multi-question batch; submit `{ kind: "submit", responses: [...] }` covering every question instead.',
191
+ );
192
+ }
193
+ const questionId = orderedIds[0]!;
194
+ if (body.kind === "option") {
195
+ return [{ questionId, kind: "option", optionId: body.optionId }];
196
+ }
197
+ return [{ questionId, kind: "free_text", text: body.text }];
198
+ }
199
+
200
+ /**
201
+ * Build a `completed` QuestionPromptResult from a batched submission and the
202
+ * per-question metadata the prompter stashed on the interaction. Delegates
203
+ * the validation + ordering loop to {@link buildBatchEntries} so the
204
+ * prompter and the route share a single implementation.
205
+ */
206
+ function buildCompletedResult(
207
+ submissions: QuestionBatchSubmission[],
208
+ interaction: ReturnType<typeof pendingInteractions.get>,
209
+ ): QuestionPromptResult {
210
+ const { orderedIds, optionsById } = readBatchMetadata(interaction);
211
+ if (orderedIds.length === 0) {
212
+ throw new QuestionBatchValidationError(
213
+ "No registered question ids for this batch",
214
+ );
215
+ }
216
+ const entries = buildBatchEntries(
217
+ orderedIds,
218
+ (qid, oid) => (optionsById[qid] ?? []).includes(oid),
219
+ new Set(Object.keys(optionsById)),
220
+ submissions,
221
+ );
222
+ return { entries, overall: "completed" };
223
+ }
224
+
225
+ /**
226
+ * Pull the prompter-stashed batch bookkeeping off a pending interaction.
227
+ * Returns empty defaults if the metadata is absent.
228
+ */
229
+ function readBatchMetadata(
230
+ interaction: ReturnType<typeof pendingInteractions.get>,
231
+ ): QuestionBatchMetadata {
232
+ const meta = interaction?.metadata as Partial<QuestionBatchMetadata> | undefined;
233
+ return {
234
+ orderedIds: meta?.orderedIds ?? [],
235
+ optionsById: meta?.optionsById ?? {},
236
+ };
237
+ }
238
+
239
+ // ---------------------------------------------------------------------------
240
+ // Route definitions
241
+ // ---------------------------------------------------------------------------
242
+
243
+ export const ROUTES: RouteDefinition[] = [
244
+ {
245
+ operationId: "question_response",
246
+ endpoint: "question-response",
247
+ method: "POST",
248
+ handler: handleQuestionResponse,
249
+ requireGuardian: true,
250
+ summary: "Resolve a pending ask-question prompt",
251
+ description:
252
+ "Submit the user's batched response (or close the card) for a pending question prompt by requestId. Legacy single-question payloads remain accepted as syntactic sugar for a one-element batch.",
253
+ tags: ["approvals"],
254
+ requestBody: QuestionResponseBody,
255
+ responseBody: z.object({
256
+ success: z.boolean(),
257
+ }),
258
+ },
259
+ ];
@@ -13,9 +13,7 @@ import {
13
13
  getConversation,
14
14
  updateConversationTitle,
15
15
  } from "../../memory/conversation-crud.js";
16
- import { getLogger } from "../../util/logger.js";
17
- import { buildAssistantEvent } from "../assistant-event.js";
18
- import { assistantEventHub } from "../assistant-event-hub.js";
16
+ import { publishConversationTitleChanged } from "../sync/resource-sync-events.js";
19
17
  import { BadRequestError, NotFoundError } from "./errors.js";
20
18
  import type { RouteDefinition } from "./types.js";
21
19
 
@@ -24,8 +22,6 @@ const RenameConversationBody = z.object({
24
22
  title: z.string().min(1),
25
23
  });
26
24
 
27
- const log = getLogger("rename-conversation-routes");
28
-
29
25
  export const ROUTES: RouteDefinition[] = [
30
26
  {
31
27
  operationId: "rename_conversation",
@@ -50,34 +46,7 @@ export const ROUTES: RouteDefinition[] = [
50
46
 
51
47
  updateConversationTitle(conversationId, title, 0);
52
48
 
53
- assistantEventHub
54
- .publish(
55
- buildAssistantEvent(
56
- {
57
- type: "conversation_title_updated",
58
- conversationId,
59
- title,
60
- },
61
- conversationId,
62
- ),
63
- )
64
- .catch((err) => {
65
- log.warn({ err }, "Failed to publish conversation_title_updated");
66
- });
67
-
68
- assistantEventHub
69
- .publish(
70
- buildAssistantEvent({
71
- type: "conversation_list_invalidated",
72
- reason: "renamed",
73
- }),
74
- )
75
- .catch((err) => {
76
- log.warn(
77
- { err },
78
- "Failed to publish conversation_list_invalidated for rename",
79
- );
80
- });
49
+ publishConversationTitleChanged(conversationId, title);
81
50
 
82
51
  return { ok: true };
83
52
  },
@@ -78,7 +78,8 @@ function handleCreateSchedule(body: Record<string, unknown>) {
78
78
  const expression =
79
79
  typeof body.expression === "string" ? body.expression.trim() : "";
80
80
  const message = typeof body.message === "string" ? body.message : "";
81
- const timezoneRaw = typeof body.timezone === "string" ? body.timezone.trim() : "";
81
+ const timezoneRaw =
82
+ typeof body.timezone === "string" ? body.timezone.trim() : "";
82
83
  const timezone = timezoneRaw === "" ? null : timezoneRaw;
83
84
  const enabled = body.enabled !== false;
84
85
  const mode = (body.mode as string | undefined) ?? "execute";
@@ -298,16 +299,12 @@ export const ROUTES: RouteDefinition[] = [
298
299
  .boolean()
299
300
  .describe("Whether the schedule starts active (default true)")
300
301
  .optional(),
301
- mode: z
302
- .string()
303
- .describe("Currently must be 'execute'")
304
- .optional(),
302
+ mode: z.string().describe("Currently must be 'execute'").optional(),
305
303
  }),
306
304
  responseBody: z.object({
307
305
  schedules: z.array(z.unknown()).describe("Updated schedule list"),
308
306
  }),
309
- handler: ({ body }: RouteHandlerArgs) =>
310
- handleCreateSchedule(body ?? {}),
307
+ handler: ({ body }: RouteHandlerArgs) => handleCreateSchedule(body ?? {}),
311
308
  },
312
309
  {
313
310
  operationId: "listScheduleRuns",
@@ -11,6 +11,7 @@ import {
11
11
  getMessages,
12
12
  type MessageRow,
13
13
  } from "../../memory/conversation-crud.js";
14
+ import { getConversationUsageTotals } from "../../memory/llm-usage-store.js";
14
15
  import { getSubagentManager } from "../../subagent/index.js";
15
16
  import { getLogger } from "../../util/logger.js";
16
17
  import { BadRequestError, NotFoundError } from "./errors.js";
@@ -29,6 +30,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
29
30
  export interface SubagentDetailResult {
30
31
  subagentId: string;
31
32
  objective?: string;
33
+ usage?: { inputTokens: number; outputTokens: number; estimatedCost: number };
32
34
  events: Array<{
33
35
  type: string;
34
36
  content: string;
@@ -94,7 +96,11 @@ export function parseSubagentMessages(
94
96
  typeof block.text === "string"
95
97
  ) {
96
98
  events.push({ type: "text", content: block.text, messageId: m.id });
97
- } else if (block.type === "tool_use") {
99
+ } else if (
100
+ block.type === "tool_use" ||
101
+ block.type === "server_tool_use" ||
102
+ block.type === "mcp_tool_use"
103
+ ) {
98
104
  const name = typeof block.name === "string" ? block.name : "unknown";
99
105
  const input = isRecord(block.input)
100
106
  ? (block.input as Record<string, unknown>)
@@ -106,7 +112,11 @@ export function parseSubagentMessages(
106
112
  toolName: name,
107
113
  });
108
114
  if (id) pendingTools.set(id, name);
109
- } else if (block.type === "tool_result") {
115
+ } else if (
116
+ block.type === "tool_result" ||
117
+ block.type === "web_search_tool_result" ||
118
+ block.type === "mcp_tool_result"
119
+ ) {
110
120
  const toolUseId =
111
121
  typeof block.tool_use_id === "string" ? block.tool_use_id : "";
112
122
  const resultContent =
@@ -114,13 +124,18 @@ export function parseSubagentMessages(
114
124
  ? block.content
115
125
  : Array.isArray(block.content)
116
126
  ? (block.content as unknown[])
117
- .filter(
118
- (b): b is Record<string, unknown> =>
119
- isRecord(b) &&
120
- (b as Record<string, unknown>).type === "text" &&
121
- typeof (b as Record<string, unknown>).text === "string",
122
- )
123
- .map((b) => b.text as string)
127
+ .filter((b): b is Record<string, unknown> => isRecord(b))
128
+ .map((b) => {
129
+ if (b.type === "text" && typeof b.text === "string")
130
+ return b.text;
131
+ if (
132
+ b.type === "web_search_result" &&
133
+ typeof b.title === "string"
134
+ )
135
+ return `${b.title}\n${typeof b.url === "string" ? b.url : ""}`;
136
+ return null;
137
+ })
138
+ .filter((s): s is string => s != null)
124
139
  .join("\n")
125
140
  : "";
126
141
  const isError = block.is_error === true;
@@ -142,7 +157,30 @@ function getSubagentDetail(
142
157
  subagentId: string,
143
158
  conversationId: string,
144
159
  ): SubagentDetailResult {
145
- return parseSubagentMessages(subagentId, getMessages(conversationId));
160
+ const messages = getMessages(conversationId);
161
+ log.info(
162
+ {
163
+ subagentId,
164
+ conversationId,
165
+ messageCount: messages.length,
166
+ roles: messages.map((m) => m.role),
167
+ },
168
+ "getSubagentDetail: raw messages from DB",
169
+ );
170
+ const result = parseSubagentMessages(subagentId, messages);
171
+ log.info(
172
+ {
173
+ subagentId,
174
+ eventCount: result.events.length,
175
+ eventTypes: result.events.map((e) => `${e.type}:${e.toolName ?? ""}`),
176
+ },
177
+ "getSubagentDetail: parsed events",
178
+ );
179
+ const usage = getConversationUsageTotals(conversationId);
180
+ if (usage.inputTokens > 0 || usage.outputTokens > 0) {
181
+ result.usage = usage;
182
+ }
183
+ return result;
146
184
  }
147
185
 
148
186
  // ---------------------------------------------------------------------------
@@ -150,6 +188,47 @@ function getSubagentDetail(
150
188
  // ---------------------------------------------------------------------------
151
189
 
152
190
  export const ROUTES: RouteDefinition[] = [
191
+ {
192
+ operationId: "reconcileSubagents",
193
+ endpoint: "subagents/reconcile",
194
+ method: "GET",
195
+ policyKey: "subagents",
196
+ summary: "Reconcile subagent live status",
197
+ description:
198
+ "Returns the live in-memory status of all subagents known to the daemon for a given parent conversation. Subagents not in the response are orphaned.",
199
+ tags: ["subagents"],
200
+ queryParams: [
201
+ {
202
+ name: "parentConversationId",
203
+ schema: { type: "string" },
204
+ description: "Parent conversation ID",
205
+ },
206
+ ],
207
+ responseBody: z.object({
208
+ subagents: z.record(
209
+ z.string(),
210
+ z.object({
211
+ status: z.string(),
212
+ }),
213
+ ),
214
+ }),
215
+ handler: ({ queryParams }) => {
216
+ const parentConversationId = queryParams?.parentConversationId;
217
+ if (!parentConversationId) {
218
+ throw new BadRequestError(
219
+ "parentConversationId query parameter is required",
220
+ );
221
+ }
222
+ const manager = getSubagentManager();
223
+ const children = manager.getChildrenOf(parentConversationId);
224
+ const subagents: Record<string, { status: string }> = {};
225
+ for (const child of children) {
226
+ subagents[child.config.id] = { status: child.status };
227
+ }
228
+ return { subagents };
229
+ },
230
+ },
231
+
153
232
  {
154
233
  operationId: "getSubagentDetail",
155
234
  endpoint: "subagents/:id",
@@ -168,14 +247,19 @@ export const ROUTES: RouteDefinition[] = [
168
247
  responseBody: z.object({
169
248
  subagentId: z.string(),
170
249
  objective: z.string(),
250
+ usage: z
251
+ .object({
252
+ inputTokens: z.number(),
253
+ outputTokens: z.number(),
254
+ estimatedCost: z.number(),
255
+ })
256
+ .optional(),
171
257
  events: z.array(z.unknown()).describe("Subagent event objects"),
172
258
  }),
173
259
  handler: ({ pathParams, queryParams }) => {
174
260
  const conversationId = queryParams?.conversationId;
175
261
  if (!conversationId) {
176
- throw new BadRequestError(
177
- "conversationId query parameter is required",
178
- );
262
+ throw new BadRequestError("conversationId query parameter is required");
179
263
  }
180
264
 
181
265
  const manager = getSubagentManager();
@@ -209,11 +293,7 @@ export const ROUTES: RouteDefinition[] = [
209
293
  }
210
294
 
211
295
  const manager = getSubagentManager();
212
- const aborted = manager.abort(
213
- pathParams!.id,
214
- () => {},
215
- conversationId,
216
- );
296
+ const aborted = manager.abort(pathParams!.id, () => {}, conversationId);
217
297
 
218
298
  if (!aborted) {
219
299
  log.warn(
@@ -7,6 +7,7 @@
7
7
  import { z } from "zod";
8
8
 
9
9
  import { recordLifecycleEvent } from "../../memory/lifecycle-events-store.js";
10
+ import { getUsageTelemetryReporter } from "../../telemetry/usage-telemetry-reporter.js";
10
11
  import { getLogger } from "../../util/logger.js";
11
12
  import { BadRequestError } from "./errors.js";
12
13
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -32,6 +33,15 @@ function handleRecordLifecycleEvent({ body }: RouteHandlerArgs) {
32
33
  return { id: event.id, event_name: event.eventName };
33
34
  }
34
35
 
36
+ async function handleTelemetryFlush() {
37
+ const reporter = getUsageTelemetryReporter();
38
+ if (!reporter) {
39
+ return { flushed: false, reason: "disabled" };
40
+ }
41
+ await reporter.flush();
42
+ return { flushed: true };
43
+ }
44
+
35
45
  export const ROUTES: RouteDefinition[] = [
36
46
  {
37
47
  operationId: "telemetry_lifecycle",
@@ -58,4 +68,21 @@ export const ROUTES: RouteDefinition[] = [
58
68
  ]),
59
69
  handler: handleRecordLifecycleEvent,
60
70
  },
71
+ {
72
+ operationId: "telemetry_flush",
73
+ endpoint: "telemetry/flush",
74
+ method: "POST",
75
+ summary: "Flush pending telemetry events",
76
+ description:
77
+ "Force-flush all pending usage, turn, and lifecycle telemetry events to the platform.",
78
+ tags: ["telemetry"],
79
+ responseBody: z.union([
80
+ z.object({ flushed: z.literal(true) }),
81
+ z.object({
82
+ flushed: z.literal(false),
83
+ reason: z.string(),
84
+ }),
85
+ ]),
86
+ handler: handleTelemetryFlush,
87
+ },
61
88
  ];
@@ -94,10 +94,35 @@ async function doSynthesize(
94
94
  throw new ServiceUnavailableError("TTS provider is not configured");
95
95
  }
96
96
 
97
- throw new BadGatewayError("TTS synthesis failed");
97
+ throw new BadGatewayError(formatTtsFailureMessage(err));
98
98
  }
99
99
  }
100
100
 
101
+ /**
102
+ * Build a user-facing error message for a failed TTS synthesis, embedding the
103
+ * upstream provider's message when available.
104
+ *
105
+ * The provider adapters surface a clean upstream message (e.g. "Free users
106
+ * cannot use library voices via the API…") and `synthesize-text` already
107
+ * prefixes those with `"TTS synthesis failed (provider: <id>): "`. We pass
108
+ * pre-prefixed messages through verbatim and only add the base prefix for
109
+ * raw provider errors, so users never see double- or triple-prefixed
110
+ * messages on the desktop / channels.
111
+ *
112
+ * Exported for unit testing.
113
+ */
114
+ export function formatTtsFailureMessage(err: unknown): string {
115
+ const base = "TTS synthesis failed";
116
+ if (err instanceof Error && err.message && err.message.trim()) {
117
+ const trimmed = err.message.trim();
118
+ if (/^TTS synthesis failed\b/i.test(trimmed)) {
119
+ return trimmed;
120
+ }
121
+ return `${base}: ${trimmed}`;
122
+ }
123
+ return base;
124
+ }
125
+
101
126
  // ---------------------------------------------------------------------------
102
127
  // Response headers — shared by both routes
103
128
  // ---------------------------------------------------------------------------
@@ -191,7 +216,7 @@ async function handleSynthesizeCliTts({ body }: RouteHandlerArgs) {
191
216
  throw new ServiceUnavailableError("TTS provider is not configured");
192
217
  }
193
218
 
194
- throw new BadGatewayError("TTS synthesis failed");
219
+ throw new BadGatewayError(formatTtsFailureMessage(err));
195
220
  }
196
221
  }
197
222
 
@@ -9,6 +9,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { beforeAll, describe, expect, test } from "bun:test";
11
11
 
12
+ import { SYNC_TAGS } from "../../daemon/message-types/sync.js";
13
+ import type { AssistantEvent } from "../assistant-event.js";
14
+ import { assistantEventHub } from "../assistant-event-hub.js";
15
+
12
16
  // ---------------------------------------------------------------------------
13
17
  // Create a temp workspace directory for isolation
14
18
  // ---------------------------------------------------------------------------
@@ -59,6 +63,15 @@ function getRoute(operationId: string): RouteDefinition {
59
63
  return route;
60
64
  }
61
65
 
66
+ async function waitFor(predicate: () => boolean): Promise<void> {
67
+ const deadline = Date.now() + 500;
68
+ while (Date.now() < deadline) {
69
+ if (predicate()) return;
70
+ await new Promise((resolve) => setTimeout(resolve, 5));
71
+ }
72
+ throw new Error("Timed out waiting for workspace route event");
73
+ }
74
+
62
75
  // ===========================================================================
63
76
  // resolveWorkspacePath
64
77
  // ===========================================================================
@@ -492,6 +505,36 @@ describe("POST /v1/workspace/write", () => {
492
505
  handler({ body: { path: "subdir", content: "should fail" } }),
493
506
  ).toThrow(ConflictError);
494
507
  });
508
+
509
+ test("publishes sounds sync events when writing sounds config", async () => {
510
+ const received: AssistantEvent[] = [];
511
+ const subscription = assistantEventHub.subscribe({
512
+ type: "process",
513
+ callback: (event) => {
514
+ received.push(event);
515
+ },
516
+ });
517
+
518
+ try {
519
+ handler({
520
+ body: {
521
+ path: "data/sounds/config.json",
522
+ content: "{}",
523
+ },
524
+ });
525
+ await waitFor(() => received.length === 2);
526
+ expect(received.map((event) => event.message.type)).toEqual([
527
+ "sounds_config_updated",
528
+ "sync_changed",
529
+ ]);
530
+ expect(received[1]!.message).toEqual({
531
+ type: "sync_changed",
532
+ tags: [SYNC_TAGS.assistantSounds],
533
+ });
534
+ } finally {
535
+ subscription.dispose();
536
+ }
537
+ });
495
538
  });
496
539
 
497
540
  // ===========================================================================