@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
@@ -38,6 +38,14 @@ export type FeedItemStatus = "new" | "seen" | "acted_on" | "dismissed";
38
38
  /** Visual urgency treatment — controls badge color independently of sort priority. */
39
39
  export type FeedItemUrgency = "low" | "medium" | "high" | "critical";
40
40
 
41
+ /** Broad category for grouping and filtering feed items. */
42
+ export type FeedItemCategory =
43
+ | "security"
44
+ | "scheduling"
45
+ | "background"
46
+ | "email"
47
+ | "system";
48
+
41
49
  /**
42
50
  * A single action button attached to a feed item.
43
51
  *
@@ -81,7 +89,13 @@ export interface FeedItem {
81
89
  type: FeedItemType;
82
90
  /** Integer in [0, 100]; higher values sort earlier. */
83
91
  priority: number;
84
- title: string;
92
+ /**
93
+ * Optional short header. Omit when the source did not supply one — the
94
+ * notification pipeline never manufactures a title from rendered copy
95
+ * (LLM-echoed bodies stutter against `summary`). Clients fall back to
96
+ * `summary` when rendering a row.
97
+ */
98
+ title?: string;
85
99
  summary: string;
86
100
  /** Event time (ISO-8601). */
87
101
  timestamp: string;
@@ -96,6 +110,14 @@ export interface FeedItem {
96
110
  conversationId?: string;
97
111
  /** Server-driven detail panel descriptor; when present, the client opens this panel kind. */
98
112
  detailPanel?: FeedItemDetailPanel;
113
+ /** Broad category for grouping and filtering feed items. */
114
+ category?: FeedItemCategory;
115
+ /** True when this item represents an assistant-initiated share or a high-importance system event. Used by clients to split inbox vs activity surfaces. */
116
+ noteworthy?: boolean;
117
+ /** True when the assistant herself emitted this item (e.g. via the `notifications send` skill). Drives clients to swap the row's leading icon for the persona avatar; system-generated items keep the category icon. */
118
+ fromAssistant?: boolean;
119
+ /** Arbitrary structured data the detail panel or other consumers can use. */
120
+ metadata?: Record<string, unknown>;
99
121
  /** Internal: ISO-8601 writer-record time, used for ordering + TTL. */
100
122
  createdAt: string;
101
123
  }
@@ -164,6 +186,14 @@ const feedItemDetailPanelSchema = z.object({
164
186
  kind: feedItemDetailPanelKindSchema,
165
187
  });
166
188
 
189
+ const feedItemCategorySchema = z.enum([
190
+ "security",
191
+ "scheduling",
192
+ "background",
193
+ "email",
194
+ "system",
195
+ ]);
196
+
167
197
  /**
168
198
  * Schema for a single `FeedItem`.
169
199
  *
@@ -178,7 +208,7 @@ export const feedItemSchema = z.object({
178
208
  id: z.string(),
179
209
  type: feedItemTypeSchema,
180
210
  priority: z.number().int().min(0).max(100),
181
- title: z.string(),
211
+ title: z.string().optional(),
182
212
  summary: z.string(),
183
213
  timestamp: z.string(),
184
214
  status: feedItemStatusSchema.default("new"),
@@ -187,6 +217,10 @@ export const feedItemSchema = z.object({
187
217
  urgency: feedItemUrgencySchema.optional(),
188
218
  conversationId: z.string().optional(),
189
219
  detailPanel: feedItemDetailPanelSchema.optional(),
220
+ category: feedItemCategorySchema.optional(),
221
+ noteworthy: z.boolean().optional(),
222
+ fromAssistant: z.boolean().optional(),
223
+ metadata: z.record(z.string(), z.unknown()).optional(),
190
224
  createdAt: z.string(),
191
225
  });
192
226
 
@@ -44,6 +44,7 @@ export async function emitPostConnectNudge(service: string): Promise<void> {
44
44
  "I can triage your inbox, summarize new emails, or draft replies to important threads.",
45
45
  timestamp: now.toISOString(),
46
46
  status: "new",
47
+ category: "email",
47
48
  expiresAt,
48
49
  createdAt: now.toISOString(),
49
50
  actions: [
package/src/index.ts CHANGED
@@ -1,5 +1,22 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ import { red } from "./cli/lib/cli-colors.js";
4
+ import {
5
+ detectUnknownCommand,
6
+ formatUnknownCommandMessage,
7
+ } from "./cli/lib/unknown-command.js";
3
8
  import { buildCliProgram } from "./cli/program.js";
4
9
 
5
- (await buildCliProgram()).parse();
10
+ const program = await buildCliProgram();
11
+
12
+ // Commander processes `--help` before any action or hook fires, so
13
+ // `assistant <unknown> --help` would dump the root help instead of flagging
14
+ // the typo. Pre-scan argv so the unknown-command error wins over the help
15
+ // short-circuit. See cli/lib/unknown-command.ts.
16
+ const unknown = detectUnknownCommand(program, process.argv.slice(2));
17
+ if (unknown) {
18
+ process.stderr.write(`${red(formatUnknownCommandMessage(unknown))}\n`);
19
+ process.exit(1);
20
+ }
21
+
22
+ program.parse();
@@ -135,19 +135,26 @@ export async function cliIpcCall<T = unknown>(
135
135
 
136
136
  const reqId = crypto.randomUUID();
137
137
 
138
- opts?.signal?.addEventListener("abort", () => {
139
- finish({ ok: false, error: "Request aborted" });
140
- }, { once: true });
138
+ opts?.signal?.addEventListener(
139
+ "abort",
140
+ () => {
141
+ finish({ ok: false, error: "Request aborted" });
142
+ },
143
+ { once: true },
144
+ );
141
145
 
142
146
  const reader = new IpcFrameReader(
143
147
  (envelope) => {
144
148
  if (envelope.id !== reqId) return;
145
149
  const msg = envelope as IpcResponse;
146
150
  if (msg.error) {
147
- finish({ ok: false, error: msg.error,
151
+ finish({
152
+ ok: false,
153
+ error: msg.error,
148
154
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
149
155
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
150
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
156
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
157
+ });
151
158
  } else {
152
159
  finish({ ok: true, result: msg.result as T });
153
160
  }
@@ -199,7 +206,13 @@ export async function cliIpcCallBinary(
199
206
  opts?: { timeoutMs?: number; signal?: AbortSignal },
200
207
  ): Promise<
201
208
  | { ok: true; headers: Record<string, string>; bytes: Uint8Array }
202
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown }
209
+ | {
210
+ ok: false;
211
+ error: string;
212
+ statusCode?: number;
213
+ errorCode?: string;
214
+ errorDetails?: unknown;
215
+ }
203
216
  > {
204
217
  if (opts?.signal?.aborted) {
205
218
  throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
@@ -215,7 +228,13 @@ export async function cliIpcCallBinary(
215
228
  const finish = (
216
229
  result:
217
230
  | { ok: true; headers: Record<string, string>; bytes: Uint8Array }
218
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown },
231
+ | {
232
+ ok: false;
233
+ error: string;
234
+ statusCode?: number;
235
+ errorCode?: string;
236
+ errorDetails?: unknown;
237
+ },
219
238
  ) => {
220
239
  if (settled) return;
221
240
  settled = true;
@@ -226,8 +245,14 @@ export async function cliIpcCallBinary(
226
245
  };
227
246
 
228
247
  const connectTimer = setTimeout(() => {
229
- log.debug({ method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS }, "CLI IPC binary connect timed out");
230
- finish({ ok: false, error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.` });
248
+ log.debug(
249
+ { method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS },
250
+ "CLI IPC binary connect timed out",
251
+ );
252
+ finish({
253
+ ok: false,
254
+ error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.`,
255
+ });
231
256
  }, CONNECT_TIMEOUT_MS);
232
257
 
233
258
  const socket = new Socket();
@@ -235,7 +260,10 @@ export async function cliIpcCallBinary(
235
260
 
236
261
  socket.on("error", (err) => {
237
262
  const code = (err as NodeJS.ErrnoException).code;
238
- log.debug({ err, code, method, socketPath }, "CLI IPC binary socket error");
263
+ log.debug(
264
+ { err, code, method, socketPath },
265
+ "CLI IPC binary socket error",
266
+ );
239
267
  finish({
240
268
  ok: false,
241
269
  error:
@@ -258,23 +286,37 @@ export async function cliIpcCallBinary(
258
286
 
259
287
  const reqId = crypto.randomUUID();
260
288
 
261
- opts?.signal?.addEventListener("abort", () => {
262
- finish({ ok: false, error: "Request aborted" });
263
- }, { once: true });
289
+ opts?.signal?.addEventListener(
290
+ "abort",
291
+ () => {
292
+ finish({ ok: false, error: "Request aborted" });
293
+ },
294
+ { once: true },
295
+ );
264
296
 
265
297
  const reader = new IpcFrameReader(
266
298
  (envelope, binary) => {
267
299
  if (envelope.id !== reqId) return;
268
300
  const msg = envelope as IpcResponse;
269
301
  if (msg.error) {
270
- finish({ ok: false, error: msg.error,
302
+ finish({
303
+ ok: false,
304
+ error: msg.error,
271
305
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
272
306
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
273
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
307
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
308
+ });
274
309
  } else if (binary === undefined) {
275
- finish({ ok: false, error: "Expected binary frame but received JSON-only response" });
310
+ finish({
311
+ ok: false,
312
+ error: "Expected binary frame but received JSON-only response",
313
+ });
276
314
  } else {
277
- finish({ ok: true, headers: (envelope.headers ?? {}) as Record<string, string>, bytes: binary });
315
+ finish({
316
+ ok: true,
317
+ headers: (envelope.headers ?? {}) as Record<string, string>,
318
+ bytes: binary,
319
+ });
278
320
  }
279
321
  },
280
322
  (err) => finish({ ok: false, error: err.message }),
@@ -285,7 +327,10 @@ export async function cliIpcCallBinary(
285
327
  writeMessage(socket, { id: reqId, method, params });
286
328
 
287
329
  callTimer = setTimeout(() => {
288
- log.debug({ method, socketPath, timeoutMs: callTimeoutMs }, "CLI IPC binary call timed out");
330
+ log.debug(
331
+ { method, socketPath, timeoutMs: callTimeoutMs },
332
+ "CLI IPC binary call timed out",
333
+ );
289
334
  finish({ ok: false, error: "Request timed out" });
290
335
  }, callTimeoutMs);
291
336
 
@@ -323,24 +368,42 @@ export async function cliIpcCallStream(
323
368
  params?: Record<string, unknown>,
324
369
  opts?: { firstByteTimeoutMs?: number; signal?: AbortSignal },
325
370
  ): Promise<
326
- | { ok: true; headers: Record<string, string>; body: ReadableStream<Uint8Array>; abort: () => void }
327
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown }
371
+ | {
372
+ ok: true;
373
+ headers: Record<string, string>;
374
+ body: ReadableStream<Uint8Array>;
375
+ abort: () => void;
376
+ }
377
+ | {
378
+ ok: false;
379
+ error: string;
380
+ statusCode?: number;
381
+ errorCode?: string;
382
+ errorDetails?: unknown;
383
+ }
328
384
  > {
329
385
  if (opts?.signal?.aborted) {
330
386
  throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
331
387
  }
332
388
 
333
389
  const socketPath = getAssistantSocketPath();
334
- const firstByteTimeoutMs = opts?.firstByteTimeoutMs ?? DEFAULT_FIRST_BYTE_TIMEOUT_MS;
390
+ const firstByteTimeoutMs =
391
+ opts?.firstByteTimeoutMs ?? DEFAULT_FIRST_BYTE_TIMEOUT_MS;
335
392
 
336
393
  return new Promise((resolve) => {
337
394
  let settled = false;
338
395
  let firstByteTimer: ReturnType<typeof setTimeout> | undefined;
339
- let streamController: ReadableStreamDefaultController<Uint8Array> | undefined;
340
-
341
- const finishError = (
342
- result: { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown },
343
- ) => {
396
+ let streamController:
397
+ | ReadableStreamDefaultController<Uint8Array>
398
+ | undefined;
399
+
400
+ const finishError = (result: {
401
+ ok: false;
402
+ error: string;
403
+ statusCode?: number;
404
+ errorCode?: string;
405
+ errorDetails?: unknown;
406
+ }) => {
344
407
  if (settled) return;
345
408
  settled = true;
346
409
  clearTimeout(connectTimer);
@@ -366,8 +429,14 @@ export async function cliIpcCallStream(
366
429
  };
367
430
 
368
431
  const connectTimer = setTimeout(() => {
369
- log.debug({ method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS }, "CLI IPC stream connect timed out");
370
- finishError({ ok: false, error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.` });
432
+ log.debug(
433
+ { method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS },
434
+ "CLI IPC stream connect timed out",
435
+ );
436
+ finishError({
437
+ ok: false,
438
+ error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.`,
439
+ });
371
440
  }, CONNECT_TIMEOUT_MS);
372
441
 
373
442
  const socket = new Socket();
@@ -375,7 +444,10 @@ export async function cliIpcCallStream(
375
444
 
376
445
  socket.on("error", (err) => {
377
446
  const code = (err as NodeJS.ErrnoException).code;
378
- log.debug({ err, code, method, socketPath }, "CLI IPC stream socket error");
447
+ log.debug(
448
+ { err, code, method, socketPath },
449
+ "CLI IPC stream socket error",
450
+ );
379
451
  if (!settled) {
380
452
  finishError({
381
453
  ok: false,
@@ -399,24 +471,35 @@ export async function cliIpcCallStream(
399
471
  : "Connection closed before response",
400
472
  });
401
473
  } else if (streamController) {
402
- streamController.error(new Error("Connection closed before stream ended"));
474
+ streamController.error(
475
+ new Error("Connection closed before stream ended"),
476
+ );
403
477
  streamController = undefined;
404
478
  }
405
479
  });
406
480
 
407
481
  const reqId = crypto.randomUUID();
408
482
 
409
- opts?.signal?.addEventListener("abort", () => { abort(); }, { once: true });
483
+ opts?.signal?.addEventListener(
484
+ "abort",
485
+ () => {
486
+ abort();
487
+ },
488
+ { once: true },
489
+ );
410
490
 
411
491
  const reader = new IpcFrameReader(
412
492
  (envelope) => {
413
493
  // Non-streaming envelope with error (e.g. method not found, auth failure)
414
494
  if (envelope.id !== reqId) return;
415
495
  const msg = envelope as IpcResponse;
416
- finishError({ ok: false, error: msg.error ?? "Unexpected non-streaming response",
496
+ finishError({
497
+ ok: false,
498
+ error: msg.error ?? "Unexpected non-streaming response",
417
499
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
418
500
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
419
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
501
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
502
+ });
420
503
  },
421
504
  (err) => finishError({ ok: false, error: err.message }),
422
505
  {
@@ -437,7 +520,12 @@ export async function cliIpcCallStream(
437
520
  });
438
521
  settled = true;
439
522
  clearTimeout(connectTimer);
440
- resolve({ ok: true, headers: (envelope.headers ?? {}) as Record<string, string>, body, abort });
523
+ resolve({
524
+ ok: true,
525
+ headers: (envelope.headers ?? {}) as Record<string, string>,
526
+ body,
527
+ abort,
528
+ });
441
529
  },
442
530
  onStreamChunk: (chunk) => {
443
531
  streamController?.enqueue(chunk);
@@ -455,8 +543,14 @@ export async function cliIpcCallStream(
455
543
  writeMessage(socket, { id: reqId, method, params });
456
544
 
457
545
  firstByteTimer = setTimeout(() => {
458
- log.debug({ method, socketPath, timeoutMs: firstByteTimeoutMs }, "CLI IPC stream first-byte timeout");
459
- finishError({ ok: false, error: "Stream timed out waiting for first byte" });
546
+ log.debug(
547
+ { method, socketPath, timeoutMs: firstByteTimeoutMs },
548
+ "CLI IPC stream first-byte timeout",
549
+ );
550
+ finishError({
551
+ ok: false,
552
+ error: "Stream timed out waiting for first byte",
553
+ });
460
554
  }, firstByteTimeoutMs);
461
555
 
462
556
  socket.on("data", (chunk) => {
@@ -491,13 +585,21 @@ export function exitFromIpcResult(
491
585
  _cmd?: unknown,
492
586
  ): never {
493
587
  process.stderr.write((r.error ?? "Unknown error") + "\n");
494
- if (r.statusCode === undefined) {
495
- process.exit(10);
496
- } else if (r.statusCode >= 500) {
497
- process.exit(3);
498
- } else if (r.statusCode >= 400) {
499
- process.exit(2);
500
- } else {
501
- process.exit(1);
502
- }
588
+ process.exit(exitCodeFromIpcResult(r));
589
+ }
590
+
591
+ /**
592
+ * Map an IPC error result to its CLI process exit code without exiting.
593
+ *
594
+ * Use this when callers want to emit a structured response (e.g. a JSON
595
+ * error envelope in `--json` mode) before terminating with the same status
596
+ * code that {@link exitFromIpcResult} would produce.
597
+ *
598
+ * Exit code matrix matches {@link exitFromIpcResult}.
599
+ */
600
+ export function exitCodeFromIpcResult(r: { statusCode?: number }): number {
601
+ if (r.statusCode === undefined) return 10;
602
+ if (r.statusCode >= 500) return 3;
603
+ if (r.statusCode >= 400) return 2;
604
+ return 1;
503
605
  }
@@ -229,6 +229,63 @@ describe("LiveVoiceSession STT", () => {
229
229
  ]);
230
230
  });
231
231
 
232
+ test("retains transcriber handle when stop() throws so close() can clean up", async () => {
233
+ class ThrowingStopTranscriber extends MockStreamingTranscriber {
234
+ stopCalls = 0;
235
+ override stop(): void {
236
+ this.stopCalls += 1;
237
+ if (this.stopCalls === 1) {
238
+ throw new Error("stop failed");
239
+ }
240
+ }
241
+ }
242
+
243
+ const transcriber = new ThrowingStopTranscriber();
244
+ const { frames, session } = createSessionWithTranscriber(transcriber);
245
+
246
+ await session.start();
247
+ await session.handleClientFrame({ type: "ptt_release" });
248
+
249
+ expect(transcriber.stopCalls).toBe(1);
250
+ expect(
251
+ frames.some(
252
+ (frame) =>
253
+ frame.type === "error" &&
254
+ frame.message.includes(
255
+ "Live voice transcription could not be stopped",
256
+ ),
257
+ ),
258
+ ).toBe(true);
259
+
260
+ await session.close("websocket_close");
261
+
262
+ expect(transcriber.stopCalls).toBe(2);
263
+ });
264
+
265
+ test("retains transcriber handle when stop() throws so interrupt() can clean up", async () => {
266
+ class ThrowingStopTranscriber extends MockStreamingTranscriber {
267
+ stopCalls = 0;
268
+ override stop(): void {
269
+ this.stopCalls += 1;
270
+ if (this.stopCalls === 1) {
271
+ throw new Error("stop failed");
272
+ }
273
+ }
274
+ }
275
+
276
+ const transcriber = new ThrowingStopTranscriber();
277
+ const { session } = createSessionWithTranscriber(transcriber);
278
+
279
+ await session.start();
280
+ await session.handleClientFrame({ type: "ptt_release" });
281
+
282
+ expect(transcriber.stopCalls).toBe(1);
283
+
284
+ await session.handleClientFrame({ type: "interrupt" });
285
+
286
+ expect(transcriber.stopCalls).toBe(2);
287
+ });
288
+
232
289
  test("uses the production streaming transcriber resolver by default", () => {
233
290
  const source = readFileSync(
234
291
  new URL("../live-voice-session.ts", import.meta.url),
package/src/mcp/client.ts CHANGED
@@ -14,6 +14,24 @@ const log = getLogger("mcp-client");
14
14
 
15
15
  const CONNECT_TIMEOUT_MS = 30_000;
16
16
 
17
+ // MCP servers occasionally return tools with a missing or non-object
18
+ // `inputSchema` (spec violation, but seen in the wild). Coerce to a valid
19
+ // empty object schema so downstream code that assumes `input_schema: object`
20
+ // (e.g. `injectActivityField`) doesn't crash.
21
+ function normalizeInputSchema(
22
+ raw: unknown,
23
+ toolName: string,
24
+ ): Record<string, unknown> {
25
+ if (raw != null && typeof raw === "object" && !Array.isArray(raw)) {
26
+ return raw as Record<string, unknown>;
27
+ }
28
+ log.warn(
29
+ { toolName, received: typeof raw },
30
+ "MCP tool returned non-object inputSchema; defaulting to empty object schema",
31
+ );
32
+ return { type: "object", properties: {} };
33
+ }
34
+
17
35
  export interface McpToolInfo {
18
36
  name: string;
19
37
  description: string;
@@ -80,9 +98,7 @@ export class McpClient {
80
98
  `mcp:${this.serverId}:tokens`,
81
99
  );
82
100
  if (cachedTokens) {
83
- const callbackTransport = getIsPlatform()
84
- ? "gateway"
85
- : "loopback";
101
+ const callbackTransport = getIsPlatform() ? "gateway" : "loopback";
86
102
  this.oauthProvider = new McpOAuthProvider(
87
103
  this.serverId,
88
104
  transportConfig.url,
@@ -164,7 +180,7 @@ export class McpClient {
164
180
  return result.tools.map((tool) => ({
165
181
  name: tool.name,
166
182
  description: tool.description ?? "",
167
- inputSchema: tool.inputSchema as Record<string, unknown>,
183
+ inputSchema: normalizeInputSchema(tool.inputSchema, tool.name),
168
184
  }));
169
185
  }
170
186
 
@@ -8,8 +8,8 @@
8
8
  * credentials are unavailable.
9
9
  */
10
10
 
11
- import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
12
- import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
11
+ import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
12
+ import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
13
13
  import { getProviderKeyAsync } from "../security/secure-keys.js";
14
14
  import type { ImageGenCredentials, ImageGenProvider } from "./types.js";
15
15
 
@@ -33,7 +33,7 @@ export async function resolveImageGenCredentials(opts: {
33
33
  // Resolve platform URL + assistant API key from a single snapshot so
34
34
  // baseUrl and assistantApiKey can't diverge if the credential is cleared
35
35
  // between lookups.
36
- const meta = MANAGED_PROVIDER_META[provider];
36
+ const meta = PLATFORM_PROVIDER_META[provider];
37
37
  const ctx = await resolveManagedProxyContext();
38
38
  if (
39
39
  !meta?.managed ||
@@ -93,17 +93,13 @@ describe("bookmark-crud", () => {
93
93
  messageId: "msg-1",
94
94
  });
95
95
 
96
- const first = createBookmark(db, {
97
- messageId: "msg-1",
98
- conversationId: "conv-1",
99
- });
100
- const second = createBookmark(db, {
101
- messageId: "msg-1",
102
- conversationId: "conv-1",
103
- });
96
+ const first = createBookmark(db, { messageId: "msg-1" });
97
+ const second = createBookmark(db, { messageId: "msg-1" });
104
98
 
105
- expect(second.id).toBe(first.id);
106
- expect(second.createdAt).toBe(first.createdAt);
99
+ expect(first.inserted).toBe(true);
100
+ expect(second.inserted).toBe(false);
101
+ expect(second.bookmark.id).toBe(first.bookmark.id);
102
+ expect(second.bookmark.createdAt).toBe(first.bookmark.createdAt);
107
103
 
108
104
  const all = listBookmarks(db);
109
105
  expect(all.length).toBe(1);
@@ -119,11 +115,10 @@ describe("bookmark-crud", () => {
119
115
  messageRole: "assistant",
120
116
  });
121
117
 
122
- const summary = createBookmark(db, {
123
- messageId: "msg-summary",
124
- conversationId: "conv-summary",
125
- });
118
+ const result = createBookmark(db, { messageId: "msg-summary" });
126
119
 
120
+ expect(result.inserted).toBe(true);
121
+ const summary = result.bookmark;
127
122
  expect(summary.conversationTitle).toBe("Title goes here");
128
123
  expect(summary.messagePreview).toBe("summary body");
129
124
  expect(summary.messageRole).toBe("assistant");
@@ -179,14 +174,8 @@ describe("bookmark-crud", () => {
179
174
  conversationId: "conv-drop",
180
175
  messageId: "msg-drop",
181
176
  });
182
- createBookmark(db, {
183
- messageId: "msg-keep",
184
- conversationId: "conv-keep",
185
- });
186
- createBookmark(db, {
187
- messageId: "msg-drop",
188
- conversationId: "conv-drop",
189
- });
177
+ createBookmark(db, { messageId: "msg-keep" });
178
+ createBookmark(db, { messageId: "msg-drop" });
190
179
  expect(listBookmarks(db).length).toBe(2);
191
180
 
192
181
  // Deleting the parent conversation cascades through messages → bookmarks.
@@ -203,7 +192,7 @@ describe("bookmark-crud", () => {
203
192
  conversationId: "conv-d",
204
193
  messageId: "msg-d",
205
194
  });
206
- createBookmark(db, { messageId: "msg-d", conversationId: "conv-d" });
195
+ createBookmark(db, { messageId: "msg-d" });
207
196
  expect(listBookmarks(db).length).toBe(1);
208
197
 
209
198
  expect(deleteBookmarkByMessageId(db, "msg-d")).toBe(true);
@@ -223,9 +212,8 @@ describe("bookmark-crud", () => {
223
212
  messageRole: "user",
224
213
  });
225
214
 
226
- const summary = createBookmark(db, {
215
+ const { bookmark: summary } = createBookmark(db, {
227
216
  messageId: "msg-blocks",
228
- conversationId: "conv-blocks",
229
217
  });
230
218
 
231
219
  // Without the decode step, this would render as the raw JSON literal
@@ -248,11 +236,29 @@ describe("bookmark-crud", () => {
248
236
  messageRole: "assistant",
249
237
  });
250
238
 
251
- const summary = createBookmark(db, {
239
+ const { bookmark: summary } = createBookmark(db, {
252
240
  messageId: "msg-multi",
253
- conversationId: "conv-multi",
254
241
  });
255
242
 
256
243
  expect(summary.messagePreview).toBe("first paragraph\nsecond paragraph");
257
244
  });
245
+
246
+ test("createBookmark derives conversationId from the message row", () => {
247
+ const { db, raw } = setupDb();
248
+ seedConversationAndMessage(raw, {
249
+ conversationId: "conv-real",
250
+ messageId: "msg-x",
251
+ });
252
+
253
+ const { bookmark: summary } = createBookmark(db, { messageId: "msg-x" });
254
+ expect(summary.conversationId).toBe("conv-real");
255
+ expect(listBookmarks(db)[0]?.conversationId).toBe("conv-real");
256
+ });
257
+
258
+ test("createBookmark throws when the message id does not exist", () => {
259
+ const { db } = setupDb();
260
+ expect(() => createBookmark(db, { messageId: "ghost" })).toThrow(
261
+ /Message ghost not found/,
262
+ );
263
+ });
258
264
  });