@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
@@ -30,6 +30,7 @@
30
30
  * can never abort a turn.
31
31
  */
32
32
  import { getConfigReadOnly } from "../config/loader.js";
33
+ import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
33
34
  import type { LlmRequestLogsClickHouseConfig } from "../config/schemas/llm-request-logs.js";
34
35
  import { credentialKey } from "../security/credential-key.js";
35
36
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -71,7 +72,7 @@ export interface ClickHouseLlmRequestLogSinkDeps {
71
72
  resolveUrl?: () => Promise<string | null>;
72
73
  /** Override the credential read for `clickhouse:password`. */
73
74
  resolvePassword?: () => Promise<string | null>;
74
- /** Override the credential read for `vellum:platform_assistant_id`. */
75
+ /** Override the in-memory platform assistant id. */
75
76
  resolveAssistantId?: () => Promise<string | null>;
76
77
  /** Override fetch for testing. */
77
78
  fetchImpl?: ClickHouseSinkFetch;
@@ -98,8 +99,7 @@ export class ClickHouseLlmRequestLogSink implements LlmRequestLogWriter {
98
99
  deps.resolvePassword ??
99
100
  (() => readCredentialOrNull("clickhouse", "password"));
100
101
  this.resolveAssistantId =
101
- deps.resolveAssistantId ??
102
- (() => readCredentialOrNull("vellum", "platform_assistant_id"));
102
+ deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
103
103
  this.fetchImpl = deps.fetchImpl ?? globalThis.fetch.bind(globalThis);
104
104
  }
105
105
 
@@ -15,6 +15,7 @@
15
15
  * than the local source. Acceptable for the "internal use while we
16
16
  * finetune prompts" use case; revisit when mirror updates are added.
17
17
  */
18
+ import { resolvePlatformAssistantIdOrNull } from "../config/platform-identity.js";
18
19
  import type { LlmRequestLogsClickHouseConfig } from "../config/schemas/llm-request-logs.js";
19
20
  import { credentialKey } from "../security/credential-key.js";
20
21
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -104,7 +105,7 @@ export interface ClickHouseLlmRequestLogSourceDeps {
104
105
  resolveUrl?: () => Promise<string | null>;
105
106
  /** Override the credential read for `clickhouse:password`. */
106
107
  resolvePassword?: () => Promise<string | null>;
107
- /** Override the credential read for `vellum:platform_assistant_id`. */
108
+ /** Override the in-memory platform assistant id. */
108
109
  resolveAssistantId?: () => Promise<string | null>;
109
110
  /** Override the turn-id resolver (default: `getAssistantMessageIdsInTurn`). */
110
111
  resolveTurnMessageIds?: (messageId: string) => string[];
@@ -138,8 +139,7 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
138
139
  deps.resolvePassword ??
139
140
  (() => readCredentialOrNull("clickhouse", "password"));
140
141
  this.resolveAssistantId =
141
- deps.resolveAssistantId ??
142
- (() => readCredentialOrNull("vellum", "platform_assistant_id"));
142
+ deps.resolveAssistantId ?? resolvePlatformAssistantIdOrNull;
143
143
  this.resolveTurnMessageIds =
144
144
  deps.resolveTurnMessageIds ?? getAssistantMessageIdsInTurn;
145
145
  this.resolveMessage = deps.resolveMessage ?? getMessageById;
@@ -0,0 +1,78 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Message cursors: the "(createdAt, id) after which" reference behind the
3
+ // messages-after reads, resilient to the referenced row being deleted.
4
+ // ---------------------------------------------------------------------------
5
+ //
6
+ // A bare message id bounds a read only while its row exists. A regenerated
7
+ // reply (the conversation retry route discards the latest assistant turn) or a
8
+ // retried turn deletes the row, and a caller holding just the id can no longer
9
+ // say where it sat. A `MessageCursor` carries the row's `createdAt` alongside
10
+ // its id, so the `(createdAt, id)` bound is reconstructible from the cursor
11
+ // alone. The live row stays authoritative whenever it exists.
12
+
13
+ import { eq } from "drizzle-orm";
14
+
15
+ import type { LineageBound } from "./conversation-lineage.js";
16
+ import { getDb } from "./db-connection.js";
17
+ import { messages } from "./schema/index.js";
18
+
19
+ export { messagesAfterBoundFilter } from "./conversation-lineage.js";
20
+
21
+ /**
22
+ * A `(createdAt, id)` message reference. `createdAt` is null when the holder
23
+ * never recorded it; such a cursor bounds a read only while its row exists.
24
+ */
25
+ export interface MessageCursor {
26
+ readonly id: string;
27
+ readonly createdAt: number | null;
28
+ }
29
+
30
+ /**
31
+ * Accepted "after" references: a message id, a {@link MessageCursor}, or
32
+ * null. `null`, `""`, and a cursor whose id is `""` all mean "no lower bound".
33
+ */
34
+ export type MessagesAfterRef = string | MessageCursor | null;
35
+
36
+ /** Where a messages-after read starts. */
37
+ export type MessagesAfterBound =
38
+ /** No lower bound: every row the conversation owns. */
39
+ | { kind: "all" }
40
+ /** Rows strictly after `bound` in `(createdAt, id)` order. */
41
+ | { kind: "after"; bound: LineageBound }
42
+ /**
43
+ * The referenced row is gone and the reference carried no timestamp, so
44
+ * the read has no defensible starting point. Readers return nothing.
45
+ */
46
+ | { kind: "vanished" };
47
+
48
+ /** The `(createdAt, id)` bound of a single message, or null when it is gone. */
49
+ export function loadMessageBound(messageId: string): LineageBound | null {
50
+ const row = getDb()
51
+ .select({ createdAt: messages.createdAt, id: messages.id })
52
+ .from(messages)
53
+ .where(eq(messages.id, messageId))
54
+ .get();
55
+ return row ?? null;
56
+ }
57
+
58
+ /**
59
+ * Resolve an "after" reference to the bound a read filters on. A live row
60
+ * wins; a cursor whose row is gone falls back to the `createdAt` it carries,
61
+ * paired with its id so same-millisecond neighbours keep their order.
62
+ */
63
+ export function resolveMessagesAfterBound(
64
+ after: MessagesAfterRef,
65
+ ): MessagesAfterBound {
66
+ const id = typeof after === "string" ? after : (after?.id ?? "");
67
+ if (id === "") {
68
+ return { kind: "all" };
69
+ }
70
+ const live = loadMessageBound(id);
71
+ if (live) {
72
+ return { kind: "after", bound: live };
73
+ }
74
+ if (typeof after === "object" && after !== null && after.createdAt !== null) {
75
+ return { kind: "after", bound: { createdAt: after.createdAt, id } };
76
+ }
77
+ return { kind: "vanished" };
78
+ }
@@ -190,6 +190,36 @@ export function existingMessageIds(
190
190
  );
191
191
  }
192
192
 
193
+ /**
194
+ * `createdAt` of each of `messageIds` that exists as a row, keyed by id, in
195
+ * any state.
196
+ *
197
+ * Any-state deliberately: a row's `createdAt` is fixed at insert, so its
198
+ * completeness does not change the answer, and the callers (retrospective
199
+ * cursor bookkeeping) hold ids taken from rows they have already reviewed.
200
+ * An empty input returns an empty map without querying.
201
+ */
202
+ export function messageCreatedAtByIds(
203
+ messageIds: readonly string[],
204
+ opts?: { db?: MessageReadHandle },
205
+ ): Map<string, number> {
206
+ const db = opts?.db ?? getDb();
207
+ const result = new Map<string, number>();
208
+ // Chunked so a whole-conversation id list stays under SQLite's bound
209
+ // parameter limit.
210
+ for (let offset = 0; offset < messageIds.length; offset += 500) {
211
+ const rows = db
212
+ .select({ id: messages.id, createdAt: messages.createdAt })
213
+ .from(messages)
214
+ .where(inArray(messages.id, messageIds.slice(offset, offset + 500)))
215
+ .all();
216
+ for (const row of rows) {
217
+ result.set(row.id, row.createdAt);
218
+ }
219
+ }
220
+ return result;
221
+ }
222
+
193
223
  /**
194
224
  * The conversation that owns `messageId`, in any state, or null when the
195
225
  * message does not exist.
@@ -1,4 +1,5 @@
1
1
  import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+ import { tableExists } from "./schema-introspection.js";
2
3
 
3
4
  /**
4
5
  * Add a required contact_id column to assistant_ingress_invites.
@@ -10,8 +11,16 @@ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
10
11
  * 1. Add the column as nullable.
11
12
  * 2. Delete legacy rows that have no contact binding (NULL).
12
13
  * 3. Rebuild the table to enforce NOT NULL (SQLite cannot ALTER COLUMN).
14
+ *
15
+ * The rebuild copies whichever session-id column is live:
16
+ * `source_conversation_id` (after migrateRenameCreatedBySessionIdColumns)
17
+ * or `created_by_session_id`.
13
18
  */
14
19
  export function migrateInviteContactId(database: DrizzleDb): void {
20
+ if (!tableExists(database, "assistant_ingress_invites")) {
21
+ return;
22
+ }
23
+
15
24
  const raw = getSqliteFrom(database);
16
25
 
17
26
  const cols = (
@@ -20,25 +29,28 @@ export function migrateInviteContactId(database: DrizzleDb): void {
20
29
  notnull: number;
21
30
  }>
22
31
  ).map((c) => ({ name: c.name, notnull: c.notnull }));
32
+ const colNames = new Set(cols.map((c) => c.name));
23
33
 
24
34
  const col = cols.find((c) => c.name === "contact_id");
25
35
 
26
- // Step 1: Add the column as nullable
27
36
  if (!col) {
28
37
  raw.exec(
29
38
  /*sql*/ `ALTER TABLE assistant_ingress_invites ADD COLUMN contact_id TEXT`,
30
39
  );
31
40
  }
32
41
 
33
- // Step 2: Delete legacy rows with no contact binding
34
42
  raw.exec(
35
43
  /*sql*/ `DELETE FROM assistant_ingress_invites WHERE contact_id IS NULL OR contact_id = ''`,
36
44
  );
37
45
 
38
- // Step 3: Rebuild the table with NOT NULL on contact_id
39
46
  if (col && col.notnull === 1) {
40
47
  return;
41
- } // already NOT NULL
48
+ }
49
+
50
+ const sessionCol = colNames.has("source_conversation_id")
51
+ ? "source_conversation_id"
52
+ : "created_by_session_id";
53
+ const sessionSelect = colNames.has(sessionCol) ? sessionCol : "NULL";
42
54
 
43
55
  raw.exec("PRAGMA foreign_keys = OFF");
44
56
  try {
@@ -49,7 +61,7 @@ export function migrateInviteContactId(database: DrizzleDb): void {
49
61
  id TEXT PRIMARY KEY,
50
62
  source_channel TEXT NOT NULL,
51
63
  token_hash TEXT NOT NULL,
52
- created_by_session_id TEXT,
64
+ ${sessionCol} TEXT,
53
65
  note TEXT,
54
66
  max_uses INTEGER NOT NULL DEFAULT 1 CHECK (max_uses > 0),
55
67
  use_count INTEGER NOT NULL DEFAULT 0 CHECK (use_count >= 0),
@@ -70,7 +82,7 @@ export function migrateInviteContactId(database: DrizzleDb): void {
70
82
  );
71
83
 
72
84
  INSERT INTO assistant_ingress_invites_new
73
- SELECT id, source_channel, token_hash, created_by_session_id, note,
85
+ SELECT id, source_channel, token_hash, ${sessionSelect}, note,
74
86
  max_uses, use_count, expires_at, status,
75
87
  redeemed_by_external_user_id, redeemed_by_external_chat_id,
76
88
  redeemed_at, expected_external_user_id, voice_code_hash,
@@ -0,0 +1,149 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import { getSqliteFrom } from "../../db-connection.js";
7
+ import * as schema from "../../schema.js";
8
+ import { migrateInviteContactId } from "../157-invite-contact-id.js";
9
+ import { tableExists, tableHasColumn } from "../schema-introspection.js";
10
+
11
+ function createTestDb() {
12
+ const sqlite = new Database(":memory:");
13
+ sqlite.exec("PRAGMA journal_mode=WAL");
14
+ return drizzle(sqlite, { schema });
15
+ }
16
+
17
+ function columnNotNull(raw: Database, name: string): boolean {
18
+ const cols = raw.query(`PRAGMA table_info(assistant_ingress_invites)`).all() as Array<{
19
+ name: string;
20
+ notnull: number;
21
+ }>;
22
+ return cols.some((c) => c.name === name && c.notnull === 1);
23
+ }
24
+
25
+ describe("migrateInviteContactId", () => {
26
+ test("rebuilds a legacy created_by_session_id table with NOT NULL contact_id", () => {
27
+ const db = createTestDb();
28
+ const raw = getSqliteFrom(db);
29
+ raw.exec(/*sql*/ `
30
+ CREATE TABLE assistant_ingress_invites (
31
+ id TEXT PRIMARY KEY,
32
+ source_channel TEXT NOT NULL,
33
+ token_hash TEXT NOT NULL,
34
+ created_by_session_id TEXT,
35
+ note TEXT,
36
+ max_uses INTEGER NOT NULL DEFAULT 1,
37
+ use_count INTEGER NOT NULL DEFAULT 0,
38
+ expires_at INTEGER NOT NULL,
39
+ status TEXT NOT NULL DEFAULT 'active',
40
+ redeemed_by_external_user_id TEXT,
41
+ redeemed_by_external_chat_id TEXT,
42
+ redeemed_at INTEGER,
43
+ expected_external_user_id TEXT,
44
+ voice_code_hash TEXT,
45
+ voice_code_digits INTEGER,
46
+ invite_code_hash TEXT,
47
+ friend_name TEXT,
48
+ guardian_name TEXT,
49
+ created_at INTEGER NOT NULL,
50
+ updated_at INTEGER NOT NULL
51
+ )
52
+ `);
53
+ raw.run(
54
+ `INSERT INTO assistant_ingress_invites
55
+ (id, source_channel, token_hash, created_by_session_id, max_uses, use_count,
56
+ expires_at, status, created_at, updated_at)
57
+ VALUES ('inv-keep', 'slack', 'hash-keep', 'conv-1', 1, 0, 9, 'active', 1, 1)`,
58
+ );
59
+ raw.exec(
60
+ `ALTER TABLE assistant_ingress_invites ADD COLUMN contact_id TEXT`,
61
+ );
62
+ raw.run(
63
+ `UPDATE assistant_ingress_invites SET contact_id = 'contact-keep' WHERE id = 'inv-keep'`,
64
+ );
65
+ raw.run(
66
+ `INSERT INTO assistant_ingress_invites
67
+ (id, source_channel, token_hash, max_uses, use_count, expires_at, status,
68
+ created_at, updated_at)
69
+ VALUES ('inv-drop', 'slack', 'hash-drop', 1, 0, 9, 'active', 1, 1)`,
70
+ );
71
+
72
+ migrateInviteContactId(db);
73
+
74
+ expect(tableHasColumn(db, "assistant_ingress_invites", "contact_id")).toBe(
75
+ true,
76
+ );
77
+ expect(columnNotNull(raw, "contact_id")).toBe(true);
78
+ expect(
79
+ tableHasColumn(db, "assistant_ingress_invites", "created_by_session_id"),
80
+ ).toBe(true);
81
+ const rows = raw
82
+ .query(`SELECT id, contact_id FROM assistant_ingress_invites ORDER BY id`)
83
+ .all() as Array<{ id: string; contact_id: string }>;
84
+ expect(rows).toEqual([{ id: "inv-keep", contact_id: "contact-keep" }]);
85
+ });
86
+
87
+ test("rebuilds after created_by_session_id was renamed to source_conversation_id", () => {
88
+ const db = createTestDb();
89
+ const raw = getSqliteFrom(db);
90
+ raw.exec(/*sql*/ `
91
+ CREATE TABLE assistant_ingress_invites (
92
+ id TEXT PRIMARY KEY,
93
+ source_channel TEXT NOT NULL,
94
+ token_hash TEXT NOT NULL,
95
+ source_conversation_id TEXT,
96
+ note TEXT,
97
+ max_uses INTEGER NOT NULL DEFAULT 1,
98
+ use_count INTEGER NOT NULL DEFAULT 0,
99
+ expires_at INTEGER NOT NULL,
100
+ status TEXT NOT NULL DEFAULT 'active',
101
+ redeemed_by_external_user_id TEXT,
102
+ redeemed_by_external_chat_id TEXT,
103
+ redeemed_at INTEGER,
104
+ expected_external_user_id TEXT,
105
+ voice_code_hash TEXT,
106
+ voice_code_digits INTEGER,
107
+ invite_code_hash TEXT,
108
+ friend_name TEXT,
109
+ guardian_name TEXT,
110
+ created_at INTEGER NOT NULL,
111
+ updated_at INTEGER NOT NULL
112
+ )
113
+ `);
114
+ raw.exec(
115
+ `ALTER TABLE assistant_ingress_invites ADD COLUMN contact_id TEXT`,
116
+ );
117
+ raw.run(
118
+ `INSERT INTO assistant_ingress_invites
119
+ (id, source_channel, token_hash, source_conversation_id, contact_id,
120
+ max_uses, use_count, expires_at, status, created_at, updated_at)
121
+ VALUES ('inv-1', 'slack', 'hash-1', 'conv-9', 'contact-1', 1, 0, 9, 'active', 1, 1)`,
122
+ );
123
+
124
+ migrateInviteContactId(db);
125
+
126
+ expect(columnNotNull(raw, "contact_id")).toBe(true);
127
+ expect(
128
+ tableHasColumn(db, "assistant_ingress_invites", "source_conversation_id"),
129
+ ).toBe(true);
130
+ expect(
131
+ tableHasColumn(db, "assistant_ingress_invites", "created_by_session_id"),
132
+ ).toBe(false);
133
+ const row = raw
134
+ .query(
135
+ `SELECT source_conversation_id, contact_id FROM assistant_ingress_invites WHERE id = 'inv-1'`,
136
+ )
137
+ .get() as { source_conversation_id: string; contact_id: string };
138
+ expect(row).toEqual({
139
+ source_conversation_id: "conv-9",
140
+ contact_id: "contact-1",
141
+ });
142
+ });
143
+
144
+ test("is a no-op when the table does not exist", () => {
145
+ const db = createTestDb();
146
+ migrateInviteContactId(db);
147
+ expect(tableExists(db, "assistant_ingress_invites")).toBe(false);
148
+ });
149
+ });
@@ -104,6 +104,11 @@ export const memoryRetrospectiveState = sqliteTable(
104
104
  conversationId: text("conversation_id").primaryKey(),
105
105
  lastProcessedMessageId: text("last_processed_message_id").notNull(),
106
106
  lastRunAt: integer("last_run_at").notNull(),
107
+ // `createdAt` of the `last_processed_message_id` row when the pointer was
108
+ // written, so the cursor still bounds reads after that row is deleted.
109
+ // NULL when unknown. Added lazily by `ensureRetrospectiveCursorColumn`
110
+ // (memory-retrospective-state.ts), not by the migration chain.
111
+ lastProcessedCreatedAt: integer("last_processed_created_at"),
107
112
  // JSON array of strings — cumulative `remember` contents from prior
108
113
  // retrospective passes (capped; see memory-retrospective-state.ts).
109
114
  // NULL for rows that predate migration 281 or have no saves yet.
@@ -24,7 +24,9 @@ mock.module("../providers/platform-proxy/context.js", () => ({
24
24
  resolveManagedProxyContext: () => mockResolveManagedProxyContext(),
25
25
  }));
26
26
 
27
+ const actualEnv = await import("../config/env.js");
27
28
  mock.module("../config/env.js", () => ({
29
+ ...actualEnv,
28
30
  getPlatformAssistantId: () => mockAssistantId,
29
31
  }));
30
32
 
@@ -113,11 +115,11 @@ describe("VellumPlatformClient", () => {
113
115
  platformBaseUrl: "",
114
116
  assistantApiKey: "",
115
117
  };
116
- mockAssistantId = "";
118
+ mockAssistantId = "stored-assistant-id";
117
119
  mockSecureKeys = {
118
120
  "vellum:platform_base_url": "https://stored-platform.example.com/",
119
121
  "vellum:assistant_api_key": "stored-api-key",
120
- "vellum:platform_assistant_id": " stored-assistant-id ",
122
+ "vellum:platform_assistant_id": " vault-assistant-id ",
121
123
  };
122
124
 
123
125
  const client = await VellumPlatformClient.create();
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { PlatformCredentialVerificationStatus } from "@vellumai/service-contracts/platform-credential";
9
9
 
10
- import { getPlatformAssistantId } from "../config/env.js";
10
+ import { resolvePlatformAssistantId } from "../config/platform-identity.js";
11
11
  import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
12
12
  import { credentialKey } from "../security/credential-key.js";
13
13
  import { getSecureKeyAsync } from "../security/secure-keys.js";
@@ -63,7 +63,7 @@ async function resolvePlatformClientConfig(): Promise<PlatformClientConfig | nul
63
63
 
64
64
  let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
65
65
  let apiKey = ctx.enabled ? ctx.assistantApiKey : "";
66
- let assistantId = getPlatformAssistantId();
66
+ const assistantId = await resolvePlatformAssistantId();
67
67
 
68
68
  // Fall back to credential store for values not yet rehydrated (standalone CLI).
69
69
  if (!baseUrl) {
@@ -76,14 +76,6 @@ async function resolvePlatformClientConfig(): Promise<PlatformClientConfig | nul
76
76
  (await getSecureKeyAsync(credentialKey("vellum", "assistant_api_key"))) ??
77
77
  "";
78
78
  }
79
- if (!assistantId) {
80
- assistantId =
81
- (
82
- await getSecureKeyAsync(
83
- credentialKey("vellum", "platform_assistant_id"),
84
- )
85
- )?.trim() ?? "";
86
- }
87
79
 
88
80
  if (!baseUrl || !apiKey) {
89
81
  const level = _missingPrereqsWarned ? "debug" : "warn";
@@ -34,6 +34,8 @@ export const HOOKS = {
34
34
  CONVERSATION_DELETED: "conversation-deleted",
35
35
  /** Fires once when every conversation is wiped at once (the clear-all reset), after the main tables are cleared. Carries no `conversationId`; cleanup hooks wipe their own per-conversation state wholesale. */
36
36
  CONVERSATIONS_CLEARED: "conversations-cleared",
37
+ /** Fires once per deleted message row, after the row is removed, carrying the row's `(createdAt, id)` so a hook can settle state that pointed at it. Fire-and-forget cleanup signal, like `conversation-deleted`; whole-conversation deletes dispatch that hook instead. */
38
+ MESSAGE_DELETED: "message-deleted",
37
39
  } as const;
38
40
 
39
41
  /** Union of every hook name declared in {@link HOOKS}. */
@@ -110,6 +110,7 @@ export type {
110
110
  HookBroadcast,
111
111
  HookFunction,
112
112
  InitContext,
113
+ MessageDeletedContext,
113
114
  ModelProfileInfo,
114
115
  PluginLogger,
115
116
  PostCompactContext,
@@ -16,6 +16,7 @@ export type {
16
16
  ConversationDeletedContext,
17
17
  ConversationsClearedContext,
18
18
  HookBroadcast,
19
+ MessageDeletedContext,
19
20
  PluginLogger,
20
21
  PostCompactContext,
21
22
  PostModelCallContext,
@@ -60,6 +61,7 @@ export type {
60
61
  * - `post-model-call` — {@link PostModelCallContext}
61
62
  * - `conversation-deleted` — {@link ConversationDeletedContext}
62
63
  * - `conversations-cleared` — {@link ConversationsClearedContext}
64
+ * - `message-deleted`: {@link MessageDeletedContext}
63
65
  */
64
66
  export type HookFunction<TCtx = unknown> = (
65
67
  ctx: TCtx,
@@ -65,6 +65,7 @@ import maxTokensContinuePkg from "./max-tokens-continue/package.json" with { typ
65
65
  import memoryConversationDeleted from "./memory/hooks/conversation-deleted.js";
66
66
  import memoryConversationsCleared from "./memory/hooks/conversations-cleared.js";
67
67
  import memoryInit from "./memory/hooks/init.js";
68
+ import memoryMessageDeleted from "./memory/hooks/message-deleted.js";
68
69
  import memoryPostCompact from "./memory/hooks/post-compact.js";
69
70
  import memoryShutdown from "./memory/hooks/shutdown.js";
70
71
  import memoryUserPromptSubmit from "./memory/hooks/user-prompt-submit.js";
@@ -199,6 +200,7 @@ export const defaultMemoryPlugin: Plugin = {
199
200
  "post-compact": memoryPostCompact,
200
201
  "conversation-deleted": memoryConversationDeleted,
201
202
  "conversations-cleared": memoryConversationsCleared,
203
+ "message-deleted": memoryMessageDeleted,
202
204
  },
203
205
  injectors: memoryInjectors,
204
206
  };
@@ -434,3 +434,49 @@ describe("ambient camera frames are not retrospective work", () => {
434
434
  expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
435
435
  });
436
436
  });
437
+
438
+ describe("retrospective accounting: a cursor survives its row's deletion", () => {
439
+ beforeEach(() => {
440
+ const db = getDb();
441
+ db.run(`DELETE FROM messages`);
442
+ db.run(`DELETE FROM conversations`);
443
+ createConversation({ id: CONV });
444
+ });
445
+
446
+ test("a deleted cursor row with a stored timestamp still bounds the count, the slice, and the user-activity probe", () => {
447
+ insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
448
+ const reply = insertRaw({
449
+ role: "assistant",
450
+ content: TEXT,
451
+ createdAt: 2_000,
452
+ });
453
+ // Regenerate: the reply the cursor sits on is discarded and replaced.
454
+ getDb().delete(messages).where(eq(messages.id, reply)).run();
455
+ const regenerated = insertRaw({
456
+ role: "assistant",
457
+ content: TEXT,
458
+ createdAt: 3_000,
459
+ });
460
+ const followUp = insertRaw({
461
+ role: "user",
462
+ content: TEXT,
463
+ createdAt: 4_000,
464
+ });
465
+
466
+ const cursor = { id: reply, createdAt: 2_000 };
467
+ expect(countRetrospectiveMessagesAfter(CONV, cursor)).toBe(2);
468
+ expect(
469
+ getRetrospectiveMessagesAfter(CONV, cursor).map((r) => r.id),
470
+ ).toEqual([regenerated, followUp]);
471
+ expect(hasQualifyingUserMessageAfter(CONV, cursor)).toBe(true);
472
+ });
473
+
474
+ test("a deleted cursor row without a timestamp still reads as no new work", () => {
475
+ insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
476
+
477
+ const cursor = { id: "gone", createdAt: null };
478
+ expect(countRetrospectiveMessagesAfter(CONV, cursor)).toBe(0);
479
+ expect(getRetrospectiveMessagesAfter(CONV, cursor)).toEqual([]);
480
+ expect(hasQualifyingUserMessageAfter(CONV, cursor)).toBe(false);
481
+ });
482
+ });