@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) 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/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -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
+ });
@@ -0,0 +1,118 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { setConfig } from "../../../../__tests__/helpers/set-config.js";
4
+
5
+ // Disable memory so persistence writes don't index into the real memory
6
+ // pipeline (both flags default true under the real loader).
7
+ setConfig("memory", { enabled: false, v2: { enabled: false } });
8
+
9
+ import { createConversation } from "../../../../persistence/conversation-crud.js";
10
+ import {
11
+ getDb,
12
+ getMemorySqlite,
13
+ } from "../../../../persistence/db-connection.js";
14
+ import { initializeDb } from "../../../../persistence/db-init.js";
15
+ import { messages } from "../../../../persistence/schema/index.js";
16
+ import { backfillRetrospectiveCursorTimestamps } from "../memory-retrospective-cursor-backfill.js";
17
+ import {
18
+ ensureRetrospectiveCursorColumn,
19
+ getRetrospectiveState,
20
+ } from "../memory-retrospective-state.js";
21
+
22
+ await initializeDb();
23
+
24
+ function resetTables(): void {
25
+ const db = getDb();
26
+ db.run(`DELETE FROM messages`);
27
+ db.run(`DELETE FROM conversations`);
28
+ getMemorySqlite()!.exec(`DELETE FROM memory_retrospective_state`);
29
+ }
30
+
31
+ /** A state row as written before the timestamp column existed. */
32
+ function insertLegacyState(conversationId: string, messageId: string): void {
33
+ ensureRetrospectiveCursorColumn("test");
34
+ getMemorySqlite()!
35
+ .query(
36
+ `INSERT INTO memory_retrospective_state
37
+ (conversation_id, last_processed_message_id, last_run_at, remembered_log, last_processed_created_at)
38
+ VALUES (?, ?, 1, NULL, NULL)`,
39
+ )
40
+ .run(conversationId, messageId);
41
+ }
42
+
43
+ function insertMessage(
44
+ conversationId: string,
45
+ id: string,
46
+ createdAt: number,
47
+ ): void {
48
+ getDb()
49
+ .insert(messages)
50
+ .values({
51
+ id,
52
+ conversationId,
53
+ role: "user",
54
+ content: "[]",
55
+ createdAt,
56
+ metadata: null,
57
+ })
58
+ .run();
59
+ }
60
+
61
+ describe("backfillRetrospectiveCursorTimestamps", () => {
62
+ beforeEach(() => {
63
+ resetTables();
64
+ });
65
+
66
+ test("fills the timestamp from the cursor's message, leaves vanished cursors NULL, and is idempotent", async () => {
67
+ createConversation({ id: "conv-a" });
68
+ createConversation({ id: "conv-b" });
69
+ createConversation({ id: "conv-c" });
70
+ insertMessage("conv-a", "a-m1", 1_000);
71
+ insertLegacyState("conv-a", "a-m1");
72
+ // Cursor row already deleted: nothing left to recover.
73
+ insertLegacyState("conv-b", "gone");
74
+ // The failure-only sentinel is never a cursor, so it is never scanned.
75
+ insertLegacyState("conv-c", "");
76
+
77
+ const first = await backfillRetrospectiveCursorTimestamps();
78
+
79
+ expect(first).toEqual({ scanned: 2, backfilled: 1, unrecoverable: 1 });
80
+ expect(getRetrospectiveState("conv-a")?.lastProcessedCreatedAt).toBe(1_000);
81
+ expect(getRetrospectiveState("conv-b")?.lastProcessedCreatedAt).toBeNull();
82
+ expect(getRetrospectiveState("conv-c")?.lastProcessedCreatedAt).toBeNull();
83
+
84
+ const second = await backfillRetrospectiveCursorTimestamps();
85
+
86
+ expect(second).toEqual({ scanned: 1, backfilled: 0, unrecoverable: 1 });
87
+ });
88
+
89
+ test("pages through the backlog by conversation id, advancing past unrecoverable rows", async () => {
90
+ for (const id of ["conv-1", "conv-2", "conv-3", "conv-4", "conv-5"]) {
91
+ createConversation({ id });
92
+ }
93
+ insertMessage("conv-1", "m-1", 1_000);
94
+ insertLegacyState("conv-1", "m-1");
95
+ insertLegacyState("conv-2", "gone-2");
96
+ insertMessage("conv-3", "m-3", 3_000);
97
+ insertLegacyState("conv-3", "m-3");
98
+ insertLegacyState("conv-4", "gone-4");
99
+ insertMessage("conv-5", "m-5", 5_000);
100
+ insertLegacyState("conv-5", "m-5");
101
+
102
+ const result = await backfillRetrospectiveCursorTimestamps({ pageSize: 2 });
103
+
104
+ expect(result).toEqual({ scanned: 5, backfilled: 3, unrecoverable: 2 });
105
+ expect(getRetrospectiveState("conv-1")?.lastProcessedCreatedAt).toBe(1_000);
106
+ expect(getRetrospectiveState("conv-3")?.lastProcessedCreatedAt).toBe(3_000);
107
+ expect(getRetrospectiveState("conv-5")?.lastProcessedCreatedAt).toBe(5_000);
108
+ expect(getRetrospectiveState("conv-4")?.lastProcessedCreatedAt).toBeNull();
109
+ });
110
+
111
+ test("a table with nothing to fill is a clean no-op", async () => {
112
+ expect(await backfillRetrospectiveCursorTimestamps()).toEqual({
113
+ scanned: 0,
114
+ backfilled: 0,
115
+ unrecoverable: 0,
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,83 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { setConfig } from "../../../../__tests__/helpers/set-config.js";
4
+
5
+ // Disable memory so persistence writes don't index into the real memory
6
+ // pipeline (both flags default true under the real loader).
7
+ setConfig("memory", { enabled: false, v2: { enabled: false } });
8
+
9
+ import { getMemorySqlite } from "../../../../persistence/db-connection.js";
10
+ import { initializeDb } from "../../../../persistence/db-init.js";
11
+ import { preserveRetrospectiveCursorTimestamps } from "../memory-retrospective-cursor-preserve.js";
12
+ import {
13
+ ensureRetrospectiveCursorColumn,
14
+ getRetrospectiveState,
15
+ upsertRetrospectiveState,
16
+ } from "../memory-retrospective-state.js";
17
+
18
+ await initializeDb();
19
+
20
+ /** A state row as written before the timestamp column existed. */
21
+ function insertLegacyState(conversationId: string, messageId: string): void {
22
+ ensureRetrospectiveCursorColumn("test");
23
+ getMemorySqlite()!
24
+ .query(
25
+ `INSERT INTO memory_retrospective_state
26
+ (conversation_id, last_processed_message_id, last_run_at, remembered_log, last_processed_created_at)
27
+ VALUES (?, ?, 1, NULL, NULL)`,
28
+ )
29
+ .run(conversationId, messageId);
30
+ }
31
+
32
+ describe("preserveRetrospectiveCursorTimestamps", () => {
33
+ beforeEach(() => {
34
+ getMemorySqlite()!.exec(`DELETE FROM memory_retrospective_state`);
35
+ });
36
+
37
+ test("records the timestamp of a legacy cursor whose row is about to be deleted", async () => {
38
+ insertLegacyState("conv-a", "reply-1");
39
+
40
+ await preserveRetrospectiveCursorTimestamps("conv-a", [
41
+ { id: "tool-1", createdAt: 1_500 },
42
+ { id: "reply-1", createdAt: 2_000 },
43
+ ]);
44
+
45
+ expect(getRetrospectiveState("conv-a")?.lastProcessedCreatedAt).toBe(2_000);
46
+ });
47
+
48
+ test("leaves a cursor that already has a timestamp, points elsewhere, or does not exist untouched", async () => {
49
+ await upsertRetrospectiveState({
50
+ conversationId: "conv-stamped",
51
+ lastProcessedMessageId: "reply-1",
52
+ lastProcessedCreatedAt: 1_000,
53
+ lastRunAt: 1,
54
+ });
55
+ insertLegacyState("conv-elsewhere", "older-reply");
56
+
57
+ await preserveRetrospectiveCursorTimestamps("conv-stamped", [
58
+ { id: "reply-1", createdAt: 9_999 },
59
+ ]);
60
+ await preserveRetrospectiveCursorTimestamps("conv-elsewhere", [
61
+ { id: "reply-1", createdAt: 9_999 },
62
+ ]);
63
+ await preserveRetrospectiveCursorTimestamps("conv-missing", [
64
+ { id: "reply-1", createdAt: 9_999 },
65
+ ]);
66
+
67
+ expect(getRetrospectiveState("conv-stamped")?.lastProcessedCreatedAt).toBe(
68
+ 1_000,
69
+ );
70
+ expect(
71
+ getRetrospectiveState("conv-elsewhere")?.lastProcessedCreatedAt,
72
+ ).toBeNull();
73
+ expect(getRetrospectiveState("conv-missing")).toBeNull();
74
+ });
75
+
76
+ test("an empty row list is a no-op", async () => {
77
+ insertLegacyState("conv-a", "reply-1");
78
+
79
+ await preserveRetrospectiveCursorTimestamps("conv-a", []);
80
+
81
+ expect(getRetrospectiveState("conv-a")?.lastProcessedCreatedAt).toBeNull();
82
+ });
83
+ });
@@ -1,5 +1,7 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import type { MessagesAfterRef } from "../../../../persistence/message-cursor.js";
4
+
3
5
  // ---------------------------------------------------------------------------
4
6
  // Mock state — reset between tests.
5
7
  // ---------------------------------------------------------------------------
@@ -17,6 +19,7 @@ let cfgThrows = false;
17
19
  let mockStateRow: {
18
20
  conversationId: string;
19
21
  lastProcessedMessageId: string;
22
+ lastProcessedCreatedAt: number | null;
20
23
  lastRunAt: number;
21
24
  rememberedLog: string[];
22
25
  } | null = null;
@@ -24,7 +27,7 @@ let tailQualifies = true;
24
27
  let gateProbeThrows = false;
25
28
  let gateProbeCalls: Array<{
26
29
  conversationId: string;
27
- afterMessageId: string | null;
30
+ after: MessagesAfterRef;
28
31
  }> = [];
29
32
 
30
33
  mock.module("../../../../persistence/conversation-crud.js", () => ({
@@ -68,9 +71,9 @@ mock.module("../memory-retrospective-state.js", () => ({
68
71
  mock.module("../memory-retrospective-accounting.js", () => ({
69
72
  hasQualifyingUserMessageAfter: (
70
73
  conversationId: string,
71
- afterMessageId: string | null,
74
+ after: MessagesAfterRef,
72
75
  ) => {
73
- gateProbeCalls.push({ conversationId, afterMessageId });
76
+ gateProbeCalls.push({ conversationId, after });
74
77
  if (gateProbeThrows) {
75
78
  throw new Error("messages table unavailable");
76
79
  }
@@ -170,10 +173,11 @@ describe("enqueueMemoryRetrospectiveIfEnabled", () => {
170
173
  expect(gateProbeCalls).toHaveLength(0);
171
174
  });
172
175
 
173
- test("gate probes the tail from the state row's cursor", () => {
176
+ test("gate probes the tail from the state row's cursor, timestamp included", () => {
174
177
  mockStateRow = {
175
178
  conversationId: "c1",
176
179
  lastProcessedMessageId: "m9",
180
+ lastProcessedCreatedAt: 9_000,
177
181
  lastRunAt: 1,
178
182
  rememberedLog: [],
179
183
  };
@@ -183,7 +187,7 @@ describe("enqueueMemoryRetrospectiveIfEnabled", () => {
183
187
  });
184
188
 
185
189
  expect(gateProbeCalls).toEqual([
186
- { conversationId: "c1", afterMessageId: "m9" },
190
+ { conversationId: "c1", after: { id: "m9", createdAt: 9_000 } },
187
191
  ]);
188
192
  });
189
193
 
@@ -20,6 +20,7 @@ let mockState: StateRow = null;
20
20
  let stateUpserts: Array<{
21
21
  conversationId: string;
22
22
  lastProcessedMessageId: string;
23
+ lastProcessedCreatedAt?: number | null;
23
24
  lastRunAt: number;
24
25
  rememberedLog?: string[];
25
26
  }> = [];
@@ -149,6 +150,7 @@ mock.module("../memory-retrospective-state.js", () => ({
149
150
  upsertRetrospectiveState: (args: {
150
151
  conversationId: string;
151
152
  lastProcessedMessageId: string;
153
+ lastProcessedCreatedAt?: number | null;
152
154
  lastRunAt: number;
153
155
  rememberedLog?: string[];
154
156
  }) => {
@@ -332,15 +334,16 @@ mock.module("../../../../persistence/jobs-store.js", () => ({
332
334
  },
333
335
  }));
334
336
 
335
- // The v3-tier gate. Drives both `buildForkInstruction`'s skill-authoring
336
- // section (proc-to-skills) and the wake's origin pin behavior. Default inactive
337
- // (remember-only), matching a stock install; tests flip it on to assert the
338
- // authoring section.
339
- let mockV3TierActive = false;
337
+ // Retrospective skill improvement is distinct from the v3 tier. Keep v3 live
338
+ // while flipping the authoring gate so these tests prove the job reads the
339
+ // dedicated predicate rather than using the broader tier predicate.
340
+ let mockV3TierActive = true;
341
+ let mockSkillImprovementActive = false;
340
342
  mock.module("../../../../config/memory-v3-gate.js", () => ({
341
343
  isMemoryEnabled: (config?: { memory?: { enabled?: boolean } }) =>
342
344
  config?.memory?.enabled !== false,
343
345
  isV3TierActive: () => mockV3TierActive,
346
+ isSkillImprovementActive: () => mockSkillImprovementActive,
344
347
  isMemoryV3Live: () => mockV3TierActive,
345
348
  usesConceptPageMemory: (memory?: {
346
349
  enabled?: boolean;
@@ -504,7 +507,8 @@ describe("memoryRetrospectiveJob", () => {
504
507
  processingStartedAtById = {};
505
508
  mockResolvedUserSlug = "alice";
506
509
  resolveUserSlugCalls = [];
507
- mockV3TierActive = false;
510
+ mockV3TierActive = true;
511
+ mockSkillImprovementActive = false;
508
512
  });
509
513
 
510
514
  test("first-run happy path: no state row, no prior retrospective, both pointer fields set on success", async () => {
@@ -518,6 +522,11 @@ describe("memoryRetrospectiveJob", () => {
518
522
  }
519
523
  expect(stateUpserts).toHaveLength(1);
520
524
  expect(stateUpserts[0]!.lastProcessedMessageId).toBe("m3");
525
+ // The cutoff's `createdAt` rides along so the cursor keeps bounding reads
526
+ // after a regenerate deletes m3.
527
+ expect(stateUpserts[0]!.lastProcessedCreatedAt).toBe(
528
+ Date.parse("2026-05-11T10:10:00Z"),
529
+ );
521
530
  expect(lastRunAtBumps).toHaveLength(0);
522
531
  expect(wakeCalls).toHaveLength(1);
523
532
  // Forks off the source so future runs can find it via
@@ -1308,7 +1317,7 @@ describe("memoryRetrospectiveJob", () => {
1308
1317
  });
1309
1318
 
1310
1319
  test("wake allows memory saves + skill authoring and suppresses the internal wake surface", async () => {
1311
- mockV3TierActive = true;
1320
+ mockSkillImprovementActive = true;
1312
1321
  await memoryRetrospectiveJob(makeJob(), stubConfig);
1313
1322
 
1314
1323
  expect(forkCalls).toHaveLength(1);
@@ -1331,8 +1340,9 @@ describe("memoryRetrospectiveJob", () => {
1331
1340
  expect(opts.hintRole).toBe("user");
1332
1341
  });
1333
1342
 
1334
- test("wake is remember-only when proc-to-skills is inactive", async () => {
1335
- mockV3TierActive = false;
1343
+ test("wake is remember-only when skill improvement is disabled on a live v3 tier", async () => {
1344
+ mockV3TierActive = true;
1345
+ mockSkillImprovementActive = false;
1336
1346
  await memoryRetrospectiveJob(makeJob(), stubConfig);
1337
1347
 
1338
1348
  expect(wakeCalls).toHaveLength(1);
@@ -2538,7 +2548,7 @@ describe("memoryRetrospectiveJob", () => {
2538
2548
  });
2539
2549
 
2540
2550
  test("proc-to-skills active: instruction carries the pre-check + dedup + companion-file directives", async () => {
2541
- mockV3TierActive = true;
2551
+ mockSkillImprovementActive = true;
2542
2552
 
2543
2553
  await memoryRetrospectiveJob(makeJob(), stubConfig);
2544
2554