@vellumai/assistant 0.12.1 → 0.12.2-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -15,13 +15,16 @@ mock.module("../config/assistant-feature-flags.js", () => ({
15
15
  }));
16
16
 
17
17
  // `buildPolicyContext` (used by the integration tests below) precomputes the
18
- // proc-to-skills gate via `isV3TierActive`. Drive it through this slot so a
19
- // test can put the production threading path in the active / inactive state.
18
+ // retrospective skill-improvement gate. Keep the v3 tier active while the
19
+ // dedicated gate changes so the production threading test covers the new
20
+ // opt-out rather than the old v3 condition.
20
21
  let mockV3TierActive = true;
22
+ let mockSkillImprovementActive = true;
21
23
  mock.module("../config/memory-v3-gate.js", () => ({
22
24
  isMemoryEnabled: (config?: { memory?: { enabled?: boolean } }) =>
23
25
  config?.memory?.enabled !== false,
24
26
  isV3TierActive: () => mockV3TierActive,
27
+ isSkillImprovementActive: () => mockSkillImprovementActive,
25
28
  isMemoryV3Live: () => mockV3TierActive,
26
29
  usesConceptPageMemory: (memory?: {
27
30
  enabled?: boolean;
@@ -219,6 +222,7 @@ describe("Permission Checker (gateway IPC)", () => {
219
222
  mockCachedThreshold = "low";
220
223
  mockRefreshedThreshold = null;
221
224
  mockV3TierActive = true;
225
+ mockSkillImprovementActive = true;
222
226
  thresholdCallLog.length = 0;
223
227
  mockSkillCatalog = [];
224
228
  mockResolvedSkill = null;
@@ -1173,10 +1177,11 @@ describe("Permission Checker (gateway IPC)", () => {
1173
1177
  });
1174
1178
 
1175
1179
  test("grant does NOT fire when proc-to-skills is inactive, even for the retrospective turn", async () => {
1176
- // Same retrospective ToolContext, but the feature is inactive (flag off
1177
- // or v3 not live). buildPolicyContext stamps `procToSkillsActive: false`,
1178
- // so the grant is dead and the high-risk scaffold prompts.
1179
- mockV3TierActive = false;
1180
+ // The retrospective keeps its live v3 tier, but this independent flag is
1181
+ // off. buildPolicyContext stamps `procToSkillsActive: false`, so the
1182
+ // grant is dead and the high-risk scaffold prompts.
1183
+ mockV3TierActive = true;
1184
+ mockSkillImprovementActive = false;
1180
1185
  mockIpcClassifyRiskResult = {
1181
1186
  risk: "high",
1182
1187
  reason: "Skill scaffold",
@@ -26,6 +26,7 @@
26
26
  */
27
27
  import type { AgentEvent } from "../agent/loop.js";
28
28
  import { getConfig } from "../config/loader.js";
29
+ import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
29
30
  import type { CompactionLogsClickHouseConfig } from "../config/schemas/compaction-logs.js";
30
31
  import { credentialKey } from "../security/credential-key.js";
31
32
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -136,7 +137,7 @@ export interface ClickHouseCompactionLogStoreDeps {
136
137
  resolveUrl?: () => Promise<string | null>;
137
138
  /** Override the credential read for `clickhouse:password`. */
138
139
  resolvePassword?: () => Promise<string | null>;
139
- /** Override the credential read for `vellum:platform_assistant_id`. */
140
+ /** Override the in-memory platform assistant id. */
140
141
  resolveAssistantId?: () => Promise<string | null>;
141
142
  /** Override fetch for testing. */
142
143
  fetchImpl?: CompactionLogFetch;
@@ -163,8 +164,7 @@ export class ClickHouseCompactionLogStore {
163
164
  deps.resolvePassword ??
164
165
  (() => readCredentialOrNull("clickhouse", "password"));
165
166
  this.resolveAssistantId =
166
- deps.resolveAssistantId ??
167
- (() => readCredentialOrNull("vellum", "platform_assistant_id"));
167
+ deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
168
168
  this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
169
169
  }
170
170
 
@@ -35,7 +35,10 @@ import { findConversation } from "../daemon/conversation-registry.js";
35
35
  import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
36
36
  import type { TrustContext } from "../daemon/trust-context-types.js";
37
37
  import { clearAllConversationIds } from "../home/feed-writer.js";
38
- import type { ConversationDeletedInputContext } from "../hooks/types.js";
38
+ import type {
39
+ ConversationDeletedInputContext,
40
+ MessageDeletedInputContext,
41
+ } from "../hooks/types.js";
39
42
  import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
40
43
  import { HOOKS } from "../plugin-api/constants.js";
41
44
  import { forkConversationMemory } from "../plugins/defaults/memory/fork-conversation-memory.js";
@@ -81,7 +84,6 @@ import { ensureDisplayOrderMigration } from "./conversation-display-order-migrat
81
84
  import { ensureGroupMigration } from "./conversation-group-migration.js";
82
85
  import {
83
86
  isReferentialFork,
84
- type LineageBound,
85
87
  type LineageConversationRow,
86
88
  lineageMessageFilter,
87
89
  lineageMessagesAfterFilter,
@@ -121,6 +123,11 @@ import {
121
123
  } from "./job-handlers/message-lexical.js";
122
124
  import { buildLifecycleTelemetryEvent } from "./lifecycle-events-store.js";
123
125
  import { resolveMessageContentBlocks } from "./message-content-file.js";
126
+ import {
127
+ loadMessageBound,
128
+ type MessagesAfterRef,
129
+ resolveMessagesAfterBound,
130
+ } from "./message-cursor.js";
124
131
  import { mergeMessageMetadata } from "./message-metadata.js";
125
132
  import {
126
133
  rawAll,
@@ -2641,16 +2648,6 @@ function loadLineageRow(id: string): LineageConversationRow | null {
2641
2648
  return row ?? null;
2642
2649
  }
2643
2650
 
2644
- /** The `(createdAt, id)` bound of a single message, or null when it is gone. */
2645
- function loadMessageBound(messageId: string): LineageBound | null {
2646
- const row = getDb()
2647
- .select({ createdAt: messages.createdAt, id: messages.id })
2648
- .from(messages)
2649
- .where(eq(messages.id, messageId))
2650
- .get();
2651
- return row ?? null;
2652
- }
2653
-
2654
2651
  /**
2655
2652
  * The segments making up a conversation's logical message set. Non-forks and
2656
2653
  * copied forks resolve to a single unbounded segment after one row lookup, so
@@ -2972,81 +2969,67 @@ export function selectNewestSightFrameCapture(
2972
2969
  }
2973
2970
 
2974
2971
  /**
2975
- * Count messages in a conversation that were created strictly after the
2976
- * `afterMessageId` reference message. If `afterMessageId` is `null` or empty,
2977
- * counts all messages in the conversation. If the referenced message no
2978
- * longer exists (e.g. deleted by a separate flow), returns 0 — callers
2979
- * decide how to react to a vanished reference, and the conservative answer
2980
- * here is "no new work."
2972
+ * Count messages in a conversation created strictly after the `after`
2973
+ * reference. `null`, `""`, or a cursor with an empty id counts every message.
2974
+ * The reference resolves through `resolveMessagesAfterBound`: a live row's
2975
+ * `(createdAt, id)` is authoritative, a `MessageCursor` whose row has been
2976
+ * deleted still bounds the count from the `createdAt` it carries, and a bare
2977
+ * id whose row is gone counts 0: a caller holding only an id has no
2978
+ * defensible starting point, and the conservative answer is "no new work."
2981
2979
  *
2982
2980
  * Used by the memory-retrospective trigger check to decide whether to fire
2983
2981
  * the message-count trigger without loading message bodies.
2984
2982
  */
2985
2983
  export function countMessagesAfter(
2986
2984
  conversationId: string,
2987
- afterMessageId: string | null,
2985
+ after: MessagesAfterRef,
2988
2986
  ): number {
2989
2987
  const db = getDb();
2990
2988
  const segments = resolveLineage(conversationId);
2991
- if (afterMessageId === null || afterMessageId === "") {
2992
- const row = db
2993
- .select({ c: count() })
2994
- .from(messages)
2995
- .where(lineageMessageFilter(segments))
2996
- .get();
2997
- return row?.c ?? 0;
2998
- }
2999
- const ref = loadMessageBound(afterMessageId);
3000
- if (!ref) {
2989
+ const start = resolveMessagesAfterBound(after);
2990
+ if (start.kind === "vanished") {
3001
2991
  return 0;
3002
2992
  }
3003
- // Tie-breaker on `messages.id` so rows that share a millisecond timestamp
3004
- // with the reference are not permanently skipped. Mirrors the
3005
- // `(createdAt, id)` cursor pattern used by the backfill job-handler and
3006
- // turn-events-store.
3007
2993
  const row = db
3008
2994
  .select({ c: count() })
3009
2995
  .from(messages)
3010
- .where(lineageMessagesAfterFilter(segments, ref))
2996
+ .where(
2997
+ start.kind === "all"
2998
+ ? lineageMessageFilter(segments)
2999
+ : lineageMessagesAfterFilter(segments, start.bound),
3000
+ )
3011
3001
  .get();
3012
3002
  return row?.c ?? 0;
3013
3003
  }
3014
3004
 
3015
3005
  /**
3016
- * Return messages in a conversation created strictly after the
3017
- * `afterMessageId` reference. If the reference is `null`/empty, returns all
3018
- * messages. If the reference doesn't exist, returns an empty array (mirrors
3019
- * `countMessagesAfter`'s conservative semantics). Used by the
3020
- * memory-retrospective job handler to load the message slice it processes.
3006
+ * Return messages in a conversation created strictly after the `after`
3007
+ * reference, in `(createdAt, id)` order. Resolves the reference exactly as
3008
+ * `countMessagesAfter` does, returning an empty array where that returns 0.
3009
+ * Used by the memory-retrospective job handler to load the message slice it
3010
+ * processes.
3021
3011
  */
3022
3012
  export function getMessagesAfter(
3023
3013
  conversationId: string,
3024
- afterMessageId: string | null,
3014
+ after: MessagesAfterRef,
3025
3015
  ): MessageRow[] {
3026
3016
  const db = getDb();
3027
3017
  const segments = resolveLineage(conversationId);
3028
- if (afterMessageId === null || afterMessageId === "") {
3029
- // Secondary `asc(messages.id)` matches the non-null path's cursor
3030
- // ordering, so callers tracking `cutoffMessageId` across runs see a
3031
- // consistent ordering when multiple rows share a millisecond timestamp.
3032
- return db
3033
- .select()
3034
- .from(messages)
3035
- .where(lineageMessageFilter(segments))
3036
- .orderBy(asc(messages.createdAt), asc(messages.id))
3037
- .all()
3038
- .map(parseMessage);
3039
- }
3040
- const ref = loadMessageBound(afterMessageId);
3041
- if (!ref) {
3018
+ const start = resolveMessagesAfterBound(after);
3019
+ if (start.kind === "vanished") {
3042
3020
  return [];
3043
3021
  }
3044
- // Same `(createdAt, id)` cursor as `countMessagesAfter` rows sharing
3045
- // the reference's millisecond timestamp would otherwise be skipped.
3022
+ // Secondary `asc(messages.id)` mirrors the bound's tie-breaker, so callers
3023
+ // tracking `cutoffMessageId` across runs see a consistent ordering when
3024
+ // multiple rows share a millisecond timestamp.
3046
3025
  return db
3047
3026
  .select()
3048
3027
  .from(messages)
3049
- .where(lineageMessagesAfterFilter(segments, ref))
3028
+ .where(
3029
+ start.kind === "all"
3030
+ ? lineageMessageFilter(segments)
3031
+ : lineageMessagesAfterFilter(segments, start.bound),
3032
+ )
3050
3033
  .orderBy(asc(messages.createdAt), asc(messages.id))
3051
3034
  .all()
3052
3035
  .map(parseMessage);
@@ -4304,12 +4287,12 @@ export function deleteLastExchange(conversationId: string): number {
4304
4287
 
4305
4288
  // Collect attachment IDs linked to the messages being deleted so we can
4306
4289
  // scope orphan cleanup to only those candidates (not freshly uploaded ones).
4307
- const messageIds = db
4308
- .select({ id: messages.id })
4290
+ const deletedRows = db
4291
+ .select({ id: messages.id, createdAt: messages.createdAt })
4309
4292
  .from(messages)
4310
4293
  .where(condition)
4311
- .all()
4312
- .map((r) => r.id);
4294
+ .all();
4295
+ const messageIds = deletedRows.map((r) => r.id);
4313
4296
  const candidateAttachmentIds =
4314
4297
  messageIds.length > 0
4315
4298
  ? db
@@ -4356,6 +4339,17 @@ export function deleteLastExchange(conversationId: string): number {
4356
4339
  enqueueDeleteMessageLexical(deletedMessageId);
4357
4340
  }
4358
4341
 
4342
+ // Notify `message-deleted` hooks for each removed row, as the
4343
+ // single-message primitive does: undo removes the same tail a regenerate
4344
+ // does, and a hook keeping a cursor on one of these rows needs its position.
4345
+ for (const row of deletedRows) {
4346
+ void runHook(HOOKS.MESSAGE_DELETED, {
4347
+ conversationId,
4348
+ messageId: row.id,
4349
+ createdAt: row.createdAt,
4350
+ } satisfies MessageDeletedInputContext);
4351
+ }
4352
+
4359
4353
  return deleted;
4360
4354
  }
4361
4355
 
@@ -4594,9 +4588,13 @@ export function deleteMessageById(
4594
4588
  .map((r) => r.attachmentId)
4595
4589
  .filter((id): id is string => id !== undefined);
4596
4590
 
4597
- // Look up the conversation before the transaction so we can recalculate lastMessageAt.
4591
+ // Look up the conversation before the transaction so we can recalculate
4592
+ // lastMessageAt, and the row's createdAt for the `message-deleted` hook.
4598
4593
  const msgRow = db
4599
- .select({ conversationId: messages.conversationId })
4594
+ .select({
4595
+ conversationId: messages.conversationId,
4596
+ createdAt: messages.createdAt,
4597
+ })
4600
4598
  .from(messages)
4601
4599
  .where(eq(messages.id, messageId))
4602
4600
  .get();
@@ -4652,6 +4650,16 @@ export function deleteMessageById(
4652
4650
  // not go through the conversation-level purge.
4653
4651
  if (msgRow) {
4654
4652
  enqueueDeleteMessageLexical(messageId);
4653
+
4654
+ // Notify `message-deleted` hooks (e.g. the memory plugin stamping a
4655
+ // retrospective cursor that sat on this row). Fire-and-forget from this
4656
+ // synchronous primitive, like `conversation-deleted`; the context carries
4657
+ // the row's position because the row itself is gone.
4658
+ void runHook(HOOKS.MESSAGE_DELETED, {
4659
+ conversationId: msgRow.conversationId,
4660
+ messageId,
4661
+ createdAt: msgRow.createdAt,
4662
+ } satisfies MessageDeletedInputContext);
4655
4663
  }
4656
4664
 
4657
4665
  return result;
@@ -227,9 +227,17 @@ export function lineageMessagesAfterFilter(
227
227
  ): SQL {
228
228
  return and(
229
229
  lineageMessageFilter(segments),
230
- or(
231
- gt(messages.createdAt, after.createdAt),
232
- and(eq(messages.createdAt, after.createdAt), gt(messages.id, after.id)),
233
- ),
230
+ messagesAfterBoundFilter(after),
231
+ ) as SQL;
232
+ }
233
+
234
+ /**
235
+ * Predicate for rows strictly after a `(createdAt, id)` bound, with the id
236
+ * tie-breaker so rows sharing the bound's millisecond keep their order.
237
+ */
238
+ export function messagesAfterBoundFilter(after: LineageBound): SQL {
239
+ return or(
240
+ gt(messages.createdAt, after.createdAt),
241
+ and(eq(messages.createdAt, after.createdAt), gt(messages.id, after.id)),
234
242
  ) as SQL;
235
243
  }
@@ -17,6 +17,7 @@ import {
17
17
  wrapUntrustedContent,
18
18
  } from "../security/untrusted-content.js";
19
19
  import { getLogger } from "../util/logger.js";
20
+ import { safeStringSlice } from "../util/unicode.js";
20
21
  import { isLexicalBackfillComplete } from "./checkpoints.js";
21
22
  import { unseenAttentionStateConditions } from "./conversation-attention-store.js";
22
23
  import type { ConversationRow } from "./conversation-crud.js";
@@ -1443,8 +1444,7 @@ function buildExcerptFromText(
1443
1444
  if (!match) {
1444
1445
  // Neither the query nor any of its tokens is present (e.g. the lexical
1445
1446
  // index matched JSON structure instead); fall back to the text start.
1446
- return text
1447
- .slice(0, EXCERPT_WINDOW * 2)
1447
+ return safeStringSlice(text, 0, EXCERPT_WINDOW * 2)
1448
1448
  .replace(/\s+/g, " ")
1449
1449
  .trim();
1450
1450
  }
@@ -0,0 +1,23 @@
1
+ import { isMessageKey } from "../i18n/index.js";
2
+
3
+ const REPLACEABLE_PATTERNS = [
4
+ /^Runtime:\s/,
5
+ /^New Conversation$/,
6
+ /^Untitled$/,
7
+ /^Untitled Conversation$/,
8
+ /^Generating title\.\.\.$/,
9
+ ];
10
+
11
+ /**
12
+ * Check whether a title is a system-generated placeholder that can be
13
+ * replaced or omitted without discarding user-provided copy.
14
+ */
15
+ export function isReplaceableTitle(title: string | null): boolean {
16
+ if (title == null || title.trim() === "") {
17
+ return true;
18
+ }
19
+ if (isMessageKey(title.trim())) {
20
+ return true;
21
+ }
22
+ return REPLACEABLE_PATTERNS.some((pattern) => pattern.test(title));
23
+ }
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { SEND_USER_MESSAGE_DELIVERED_ACK } from "../config/send-user-message-constants.js";
11
+ import { MESSAGE_KEYS } from "../i18n/index.js";
11
12
  import {
12
13
  requestShortLabel,
13
14
  type ShortLabelTool,
@@ -24,8 +25,11 @@ import {
24
25
  type MessageRow,
25
26
  updateConversationTitle,
26
27
  } from "./conversation-crud.js";
28
+ import { isReplaceableTitle } from "./conversation-title-placeholders.js";
27
29
  import { projectPersistedAssistantContent } from "./user-facing-content.js";
28
30
 
31
+ export { isReplaceableTitle } from "./conversation-title-placeholders.js";
32
+
29
33
  const log = getLogger("conversation-title-service");
30
34
 
31
35
  // ── Types ────────────────────────────────────────────────────────────
@@ -65,8 +69,8 @@ export interface TitleContext {
65
69
 
66
70
  // ── Placeholder / loading state ──────────────────────────────────────
67
71
 
68
- export const GENERATING_TITLE = "Generating title...";
69
- const UNTITLED_FALLBACK = "Untitled Conversation";
72
+ export const GENERATING_TITLE = MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING;
73
+ const UNTITLED_FALLBACK = MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED;
70
74
 
71
75
  // ── `conversations.isAutoTitle` values ───────────────────────────────
72
76
  //
@@ -83,28 +87,6 @@ export const AUTO_TITLE_LLM = 1;
83
87
  */
84
88
  export const AUTO_TITLE_DETERMINISTIC = 2;
85
89
 
86
- // ── Replaceability check ─────────────────────────────────────────────
87
-
88
- const REPLACEABLE_PATTERNS = [
89
- /^Runtime:\s/,
90
- /^New Conversation$/,
91
- /^Untitled$/,
92
- /^Untitled Conversation$/,
93
- /^Generating title\.\.\.$/,
94
- ];
95
-
96
- /**
97
- * Check whether a title is a system-generated placeholder that can be
98
- * safely overwritten by auto-generated titles. Returns `false` for
99
- * user-provided custom titles.
100
- */
101
- export function isReplaceableTitle(title: string | null): boolean {
102
- if (title == null || title.trim() === "") {
103
- return true;
104
- }
105
- return REPLACEABLE_PATTERNS.some((pattern) => pattern.test(title));
106
- }
107
-
108
90
  /**
109
91
  * Whether auto-generation may overwrite the conversation's current title.
110
92
  * True for placeholder/system titles and for deterministic bootstrap titles
@@ -255,7 +237,7 @@ function settleForDeterministicTitle(
255
237
  * conversation creation (e.g. 5 new chats in quick succession) fires N
256
238
  * concurrent requests that can hit provider rate limits or contend for
257
239
  * API capacity, causing later calls to time out and fall back to
258
- * "Untitled Conversation".
240
+ * the untitled catalog key.
259
241
  *
260
242
  * A serial queue ensures at most one title-generation LLM call is
261
243
  * in-flight at a time. Each call is lightweight (~1–3 s for a ≤5-word
@@ -289,7 +271,7 @@ export function queueGenerateConversationTitle(
289
271
  // Replace loading placeholder with a retryable fallback.
290
272
  try {
291
273
  const conversation = getConversation(params.conversationId);
292
- if (conversation && conversation.title === GENERATING_TITLE) {
274
+ if (conversation && isReplaceableTitle(conversation.title)) {
293
275
  const fallback =
294
276
  deriveFallbackTitle(params.context) ?? UNTITLED_FALLBACK;
295
277
  updateConversationTitle(
@@ -424,6 +406,7 @@ function buildTitleSystemPrompt(): string {
424
406
  "- Think: what would make a scannable sidebar label?",
425
407
  "- Do NOT echo back what the user asked you to do",
426
408
  "- Do NOT respond to the conversation content",
409
+ "- Write the title in the same language as the conversation content",
427
410
  "- Do NOT assess feasibility or comment on capabilities",
428
411
  "- If input is sparse or references external context, extract a topic from the words that ARE present (e.g. 'so about that t-shirt...' → 'T-Shirt Discussion'). Never describe the absence, emptiness, or insufficiency of context — titles like 'Missing Context', 'Unclear Request', 'No Topic' are forbidden",
429
412
  ].join("\n");
@@ -33,6 +33,116 @@ const SHUTDOWN_GRACE_MS = 5_000;
33
33
  const STDOUT_CAPTURE_LIMIT = 16_384;
34
34
  const STDERR_CAPTURE_LIMIT = 4_096;
35
35
 
36
+ /**
37
+ * Which step of a local start failed. `install` covers the download, checksum
38
+ * and extraction of the binary; `spawn` the OS refusing to run it; `exited`
39
+ * the process dying before `/readyz` answered; `not_ready` the readiness
40
+ * deadline passing with the process still alive (or, in external mode, the
41
+ * remote instance never answering).
42
+ */
43
+ export type QdrantStartFailureKind =
44
+ | "install"
45
+ | "spawn"
46
+ | "exited"
47
+ | "not_ready";
48
+
49
+ /**
50
+ * A failed `QdrantManager.start()`. The message keeps the captured output for
51
+ * the log line; the fields carry the same facts in a form a reporter can use
52
+ * without parsing that message.
53
+ */
54
+ export class QdrantStartError extends Error {
55
+ constructor(
56
+ message: string,
57
+ readonly kind: QdrantStartFailureKind,
58
+ readonly exitCode: number | null,
59
+ readonly stdout: string,
60
+ readonly stderr: string,
61
+ options?: ErrorOptions,
62
+ ) {
63
+ super(message, options);
64
+ this.name = "QdrantStartError";
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Budget for the `panic` string {@link describeQdrantStartFailure} emits, in
70
+ * the bytes the telemetry server counts: JSON with every code unit above 0x7E
71
+ * escaped as `\uXXXX` (see `jsonByteLength` in `telemetry-wire.generated.ts`).
72
+ * A quarter of `WATCHDOG_DETAIL_MAX_JSON_BYTES`, leaving the other fields and
73
+ * the event envelope far from the limit that silently drops the event.
74
+ */
75
+ const PANIC_REPORT_MAX_JSON_BYTES = 1_024;
76
+
77
+ /**
78
+ * Longest prefix of `text` whose server-side JSON size fits `maxBytes`. Each
79
+ * UTF-16 code unit above 0x7E costs six bytes (astral characters two units,
80
+ * so twelve); quotes, backslashes and control characters cost what
81
+ * `JSON.stringify` escapes them to. Counting characters instead would let a
82
+ * non-ASCII reason overshoot the budget six-fold.
83
+ */
84
+ function truncateToJsonBytes(text: string, maxBytes: number): string {
85
+ let bytes = 0;
86
+ for (let i = 0; i < text.length; i++) {
87
+ const unit = text.charCodeAt(i);
88
+ bytes += unit > 0x7e ? 6 : JSON.stringify(text[i]).length - 2;
89
+ if (bytes > maxBytes) {
90
+ return text.slice(0, i);
91
+ }
92
+ }
93
+ return text;
94
+ }
95
+
96
+ /** Qdrant's panic hook logs `Panic occurred <location>: <reason>` on stdout. */
97
+ const PANIC_MARKER = "Panic occurred";
98
+
99
+ /**
100
+ * Reduce a start failure to the metadata a `watchdog` telemetry event may
101
+ * carry: the failing step, the exit code, and Qdrant's own one-line reason.
102
+ *
103
+ * The reason is engine output, never conversation content, so it is in the
104
+ * same class as the SQLite `integrity_check` strings `db-integrity-sample.ts`
105
+ * already reports. The one thing in it that is not pure engine text is the
106
+ * absolute data directory, which is swapped for a placeholder. The panic hook
107
+ * prints the reason after the backtrace, so it is everything from the last
108
+ * marker on; when Qdrant died without panicking (a port collision logs a plain
109
+ * error) the last stdout line is the best available explanation. Capped by
110
+ * the server's own byte measure well inside the detail budget. Anything that is not a
111
+ * {@link QdrantStartError} reports as `unknown`, so a new throw site can never
112
+ * silently drop the event.
113
+ */
114
+ export function describeQdrantStartFailure(
115
+ err: unknown,
116
+ dataDir: string = getDataDir(),
117
+ ): {
118
+ kind: QdrantStartFailureKind | "unknown";
119
+ exit_code: number | null;
120
+ panic: string | null;
121
+ } {
122
+ if (!(err instanceof QdrantStartError)) {
123
+ return { kind: "unknown", exit_code: null, panic: null };
124
+ }
125
+ const stdout = err.stdout.trim();
126
+ let reason: string | null = null;
127
+ if (stdout) {
128
+ const marker = stdout.lastIndexOf(PANIC_MARKER);
129
+ reason =
130
+ marker >= 0
131
+ ? stdout.slice(marker)
132
+ : (stdout
133
+ .split("\n")
134
+ .filter((line) => line.trim())
135
+ .at(-1) ?? null);
136
+ }
137
+ if (reason) {
138
+ reason = truncateToJsonBytes(
139
+ reason.split(dataDir).join("<data>"),
140
+ PANIC_REPORT_MAX_JSON_BYTES,
141
+ );
142
+ }
143
+ return { kind: err.kind, exit_code: err.exitCode, panic: reason };
144
+ }
145
+
36
146
  export function resolveQdrantReleaseAsset(
37
147
  os: NodeJS.Platform,
38
148
  cpu: string,
@@ -144,7 +254,11 @@ export class QdrantManager {
144
254
 
145
255
  const binaryPath = this.getBinaryPath();
146
256
  if (!existsSync(binaryPath)) {
147
- await this.installBinary(binaryPath);
257
+ try {
258
+ await this.installBinary(binaryPath);
259
+ } catch (err) {
260
+ throw this.startError(err, "install");
261
+ }
148
262
  }
149
263
 
150
264
  const spawnPath = this.ensureVellumSymlink(binaryPath);
@@ -154,21 +268,26 @@ export class QdrantManager {
154
268
  "Starting Qdrant",
155
269
  );
156
270
 
157
- const proc = Bun.spawn({
158
- cmd: [spawnPath],
159
- windowsHide: true,
160
- env: {
161
- ...process.env,
162
- QDRANT__SERVICE__HOST: this.host,
163
- QDRANT__SERVICE__HTTP_PORT: String(this.port),
164
- QDRANT__SERVICE__GRPC_PORT: "0", // disable gRPC
165
- QDRANT__TELEMETRY_DISABLED: "true",
166
- QDRANT__STORAGE__STORAGE_PATH: this.storagePath,
167
- QDRANT__LOG_LEVEL: "WARN",
168
- },
169
- stdout: "pipe",
170
- stderr: "pipe",
171
- });
271
+ let proc: Subprocess<"ignore", "pipe", "pipe">;
272
+ try {
273
+ proc = Bun.spawn({
274
+ cmd: [spawnPath],
275
+ windowsHide: true,
276
+ env: {
277
+ ...process.env,
278
+ QDRANT__SERVICE__HOST: this.host,
279
+ QDRANT__SERVICE__HTTP_PORT: String(this.port),
280
+ QDRANT__SERVICE__GRPC_PORT: "0", // disable gRPC
281
+ QDRANT__TELEMETRY_DISABLED: "true",
282
+ QDRANT__STORAGE__STORAGE_PATH: this.storagePath,
283
+ QDRANT__LOG_LEVEL: "WARN",
284
+ },
285
+ stdout: "pipe",
286
+ stderr: "pipe",
287
+ });
288
+ } catch (err) {
289
+ throw this.startError(err, "spawn");
290
+ }
172
291
  this.process = proc;
173
292
  this.drainOutputFrom(proc.stdout, proc.stderr);
174
293
 
@@ -311,6 +430,24 @@ export class QdrantManager {
311
430
  );
312
431
  }
313
432
 
433
+ /**
434
+ * Wrap a failure from a start step in a {@link QdrantStartError}. Errors the
435
+ * manager already classified pass through untouched.
436
+ */
437
+ private startError(err: unknown, kind: QdrantStartFailureKind): Error {
438
+ if (err instanceof QdrantStartError) {
439
+ return err;
440
+ }
441
+ return new QdrantStartError(
442
+ err instanceof Error ? err.message : String(err),
443
+ kind,
444
+ null,
445
+ this.stdoutBuffer,
446
+ this.stderrBuffer,
447
+ { cause: err },
448
+ );
449
+ }
450
+
314
451
  private async waitForReady(): Promise<void> {
315
452
  const start = Date.now();
316
453
  // Build a single exited-promise once so each race reuses the same handle.
@@ -327,9 +464,13 @@ export class QdrantManager {
327
464
 
328
465
  const throwOnExit = async (code: number): Promise<never> => {
329
466
  await this.outputDrained;
330
- throw new Error(
467
+ throw new QdrantStartError(
331
468
  `Qdrant process exited with code ${code} before becoming ready` +
332
469
  this.formatCapturedOutput(),
470
+ "exited",
471
+ code,
472
+ this.stdoutBuffer,
473
+ this.stderrBuffer,
333
474
  );
334
475
  };
335
476
 
@@ -363,9 +504,13 @@ export class QdrantManager {
363
504
  await throwOnExit(sleepOutcome.code);
364
505
  }
365
506
  }
366
- throw new Error(
507
+ throw new QdrantStartError(
367
508
  `Qdrant did not become ready within ${this.readyzTimeoutMs}ms at ${this.url}` +
368
509
  this.formatCapturedOutput(),
510
+ "not_ready",
511
+ null,
512
+ this.stdoutBuffer,
513
+ this.stderrBuffer,
369
514
  );
370
515
  }
371
516