@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
@@ -31,7 +31,7 @@
31
31
  // accounting; once newer real messages arrive it is copied into the next
32
32
  // run's fork as inert prefix context.
33
33
 
34
- import { and, eq, gt, like, or } from "drizzle-orm";
34
+ import { and, eq, like, or } from "drizzle-orm";
35
35
 
36
36
  import {
37
37
  countMessagesAfter,
@@ -43,6 +43,11 @@ import {
43
43
  SIGHT_FRAME_ATTACHMENT_IDS_KEY,
44
44
  } from "../../../persistence/conversation-types.js";
45
45
  import { getDb } from "../../../persistence/db-connection.js";
46
+ import {
47
+ messagesAfterBoundFilter,
48
+ type MessagesAfterRef,
49
+ resolveMessagesAfterBound,
50
+ } from "../../../persistence/message-cursor.js";
46
51
  import { messages } from "../../../persistence/schema/index.js";
47
52
  import { SKILL_CARD_MESSAGE_KIND } from "./memory-retrospective-constants.js";
48
53
 
@@ -98,9 +103,9 @@ export function isSkillCardMessage(row: { metadata: string | null }): boolean {
98
103
  */
99
104
  export function getRetrospectiveMessagesAfter(
100
105
  conversationId: string,
101
- afterMessageId: string | null,
106
+ after: MessagesAfterRef,
102
107
  ): MessageRow[] {
103
- const rows = getMessagesAfter(conversationId, afterMessageId);
108
+ const rows = getMessagesAfter(conversationId, after);
104
109
  const firstUnfinalized = rows.findIndex((row) => row.finalized !== 1);
105
110
  const bounded =
106
111
  firstUnfinalized === -1 ? rows : rows.slice(0, firstUnfinalized);
@@ -116,13 +121,13 @@ export function getRetrospectiveMessagesAfter(
116
121
  */
117
122
  export function countRetrospectiveMessagesAfter(
118
123
  conversationId: string,
119
- afterMessageId: string | null,
124
+ after: MessagesAfterRef,
120
125
  ): number {
121
- const total = countMessagesAfter(conversationId, afterMessageId);
126
+ const total = countMessagesAfter(conversationId, after);
122
127
  if (total === 0) {
123
128
  return 0;
124
129
  }
125
- const excluded = countExcludedMessagesAfter(conversationId, afterMessageId);
130
+ const excluded = countExcludedMessagesAfter(conversationId, after);
126
131
  return Math.max(0, total - excluded);
127
132
  }
128
133
 
@@ -167,7 +172,8 @@ export function messagesHaveUserActivity(
167
172
  * a non-tool_result block. Powers the `memory.retrospective.requireUserActivity`
168
173
  * enqueue gate, so only user rows are loaded. Cursor semantics mirror
169
174
  * `countMessagesAfter`: a null/`""` reference scans the whole conversation,
170
- * and a vanished reference means no new work.
175
+ * a cursor whose row is gone falls back to the timestamp it carries, and a
176
+ * bare id whose row is gone means no new work.
171
177
  *
172
178
  * Operates on the raw `content` column: rows that do not parse to a block
173
179
  * array (file-backed `{ ref }` rows, legacy plain strings) count as
@@ -177,37 +183,15 @@ export function messagesHaveUserActivity(
177
183
  */
178
184
  export function hasQualifyingUserMessageAfter(
179
185
  conversationId: string,
180
- afterMessageId: string | null,
186
+ after: MessagesAfterRef,
181
187
  ): boolean {
182
- const cursorId =
183
- afterMessageId === null || afterMessageId === "" ? null : afterMessageId;
184
- const db = getDb();
185
-
186
- let ref: { createdAt: number } | undefined;
187
- if (cursorId !== null) {
188
- ref = db
189
- .select({ createdAt: messages.createdAt })
190
- .from(messages)
191
- .where(eq(messages.id, cursorId))
192
- .get();
193
- if (!ref) {
194
- return false;
195
- }
188
+ const start = resolveMessagesAfterBound(after);
189
+ if (start.kind === "vanished") {
190
+ return false;
196
191
  }
197
-
198
192
  const afterCursor =
199
- cursorId !== null && ref
200
- ? [
201
- or(
202
- gt(messages.createdAt, ref.createdAt),
203
- and(
204
- eq(messages.createdAt, ref.createdAt),
205
- gt(messages.id, cursorId),
206
- ),
207
- ),
208
- ]
209
- : [];
210
- const rows = db
193
+ start.kind === "after" ? [messagesAfterBoundFilter(start.bound)] : [];
194
+ const rows = getDb()
211
195
  .select({ content: messages.content, metadata: messages.metadata })
212
196
  .from(messages)
213
197
  .where(
@@ -248,18 +232,14 @@ function rawUserContentCarriesActivity(raw: string | null): boolean {
248
232
  * — at most one per successful retrospective run); each candidate is then
249
233
  * JSON-verified by {@link isSkillCardMessage} so an incidental substring
250
234
  * match in unrelated metadata can never exclude a real message from
251
- * accounting. The cursor comparison mirrors `countMessagesAfter`'s
252
- * tie-breaker semantics, including the null/`""` "count everything" cases
253
- * and the vanished-reference "no new work" case.
235
+ * accounting. The cursor resolves exactly as `countMessagesAfter`'s does,
236
+ * including the null/`""` "count everything" cases and the vanished-reference
237
+ * "no new work" case.
254
238
  */
255
239
  function countExcludedMessagesAfter(
256
240
  conversationId: string,
257
- afterMessageId: string | null,
241
+ after: MessagesAfterRef,
258
242
  ): number {
259
- // The `""` sentinel (failure-only state rows) counts everything, matching
260
- // `countMessagesAfter`.
261
- const cursorId =
262
- afterMessageId === null || afterMessageId === "" ? null : afterMessageId;
263
243
  const db = getDb();
264
244
  const candidates = db
265
245
  .select({
@@ -282,18 +262,11 @@ function countExcludedMessagesAfter(
282
262
  return 0;
283
263
  }
284
264
 
285
- let ref: { createdAt: number } | undefined;
286
- if (cursorId !== null) {
287
- ref = db
288
- .select({ createdAt: messages.createdAt })
289
- .from(messages)
290
- .where(eq(messages.id, cursorId))
291
- .get();
292
- // Vanished reference: `countMessagesAfter` reported 0, so there is
293
- // nothing to subtract from.
294
- if (!ref) {
295
- return 0;
296
- }
265
+ const start = resolveMessagesAfterBound(after);
266
+ // Vanished reference: `countMessagesAfter` reported 0, so there is nothing
267
+ // to subtract from.
268
+ if (start.kind === "vanished") {
269
+ return 0;
297
270
  }
298
271
 
299
272
  let count = 0;
@@ -301,11 +274,12 @@ function countExcludedMessagesAfter(
301
274
  if (!isExcludedFromRetrospectiveAccounting(row)) {
302
275
  continue;
303
276
  }
304
- if (cursorId !== null && ref) {
305
- const after =
306
- row.createdAt > ref.createdAt ||
307
- (row.createdAt === ref.createdAt && row.id > cursorId);
308
- if (!after) {
277
+ if (start.kind === "after") {
278
+ const { createdAt, id } = start.bound;
279
+ const isAfter =
280
+ row.createdAt > createdAt ||
281
+ (row.createdAt === createdAt && row.id > id);
282
+ if (!isAfter) {
309
283
  continue;
310
284
  }
311
285
  }
@@ -0,0 +1,126 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory retrospective: cursor timestamp backfill.
3
+ // ---------------------------------------------------------------------------
4
+ //
5
+ // State rows written before `last_processed_created_at` existed carry an
6
+ // id-only cursor, which stops bounding reads the moment its row is deleted
7
+ // (see `memory-retrospective-state.ts`). This pass copies `createdAt` from
8
+ // each such row's message while the message still exists. Rows whose message
9
+ // is already gone stay NULL: nothing remains to recover, and they are counted
10
+ // so the stall is visible in the log. Runs detached at daemon init (ahead of
11
+ // any client regenerating a reply) and again at memory-worker startup;
12
+ // idempotent, keyset-paginated by conversation id, and yielding between pages.
13
+
14
+ import { and, asc, eq, gt, isNull, ne } from "drizzle-orm";
15
+
16
+ import { messageCreatedAtByIds } from "../../../persistence/message-reads.js";
17
+ import { memoryRetrospectiveState } from "../../../persistence/schema/index.js";
18
+ import { withSqliteRetry } from "./host-utils.js";
19
+ import { getLogger } from "./logging.js";
20
+ import { memoryDbOrNull } from "./memory-db.js";
21
+ import { ensureRetrospectiveCursorColumn } from "./memory-retrospective-state.js";
22
+
23
+ const log = getLogger("memory-retrospective-cursor-backfill");
24
+
25
+ const DEFAULT_PAGE_SIZE = 500;
26
+
27
+ export interface CursorBackfillResult {
28
+ /** Rows with a cursor id but no timestamp. */
29
+ scanned: number;
30
+ /** Rows whose message still exists and received its `createdAt`. */
31
+ backfilled: number;
32
+ /** Rows whose cursor message no longer exists; left NULL. */
33
+ unrecoverable: number;
34
+ }
35
+
36
+ /**
37
+ * Fill `last_processed_created_at` on every state row that has a cursor id
38
+ * but no timestamp. Best-effort: a missing memory connection or column is a
39
+ * no-op, and a row the job rewrites mid-pass is skipped by the update's
40
+ * `IS NULL` guard rather than stomped. Pages by conversation id so a large
41
+ * backlog never materializes at once or holds the event loop.
42
+ */
43
+ export async function backfillRetrospectiveCursorTimestamps(opts?: {
44
+ pageSize?: number;
45
+ }): Promise<CursorBackfillResult> {
46
+ const pageSize = opts?.pageSize ?? DEFAULT_PAGE_SIZE;
47
+ const result: CursorBackfillResult = {
48
+ scanned: 0,
49
+ backfilled: 0,
50
+ unrecoverable: 0,
51
+ };
52
+ const context = "backfillRetrospectiveCursorTimestamps";
53
+ const mdb = memoryDbOrNull(context);
54
+ if (!mdb || !ensureRetrospectiveCursorColumn(context)) {
55
+ return result;
56
+ }
57
+
58
+ let afterConversationId = "";
59
+ for (;;) {
60
+ const page = mdb
61
+ .select({
62
+ conversationId: memoryRetrospectiveState.conversationId,
63
+ messageId: memoryRetrospectiveState.lastProcessedMessageId,
64
+ })
65
+ .from(memoryRetrospectiveState)
66
+ .where(
67
+ and(
68
+ isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
69
+ ne(memoryRetrospectiveState.lastProcessedMessageId, ""),
70
+ gt(memoryRetrospectiveState.conversationId, afterConversationId),
71
+ ),
72
+ )
73
+ .orderBy(asc(memoryRetrospectiveState.conversationId))
74
+ .limit(pageSize)
75
+ .all();
76
+ if (page.length === 0) {
77
+ break;
78
+ }
79
+ afterConversationId = page[page.length - 1]!.conversationId;
80
+ result.scanned += page.length;
81
+
82
+ const createdAtById = messageCreatedAtByIds(
83
+ page.map((row) => row.messageId),
84
+ );
85
+ const recoverable = page.flatMap((row) => {
86
+ const createdAt = createdAtById.get(row.messageId);
87
+ return createdAt === undefined ? [] : [{ ...row, createdAt }];
88
+ });
89
+ result.unrecoverable += page.length - recoverable.length;
90
+
91
+ if (recoverable.length > 0) {
92
+ await withSqliteRetry(
93
+ () => {
94
+ mdb.transaction((tx) => {
95
+ for (const row of recoverable) {
96
+ tx.update(memoryRetrospectiveState)
97
+ .set({ lastProcessedCreatedAt: row.createdAt })
98
+ .where(
99
+ and(
100
+ eq(
101
+ memoryRetrospectiveState.conversationId,
102
+ row.conversationId,
103
+ ),
104
+ eq(
105
+ memoryRetrospectiveState.lastProcessedMessageId,
106
+ row.messageId,
107
+ ),
108
+ isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
109
+ ),
110
+ )
111
+ .run();
112
+ }
113
+ });
114
+ },
115
+ { op: context, context: { afterConversationId } },
116
+ );
117
+ result.backfilled += recoverable.length;
118
+ }
119
+ await Bun.sleep(0);
120
+ }
121
+
122
+ if (result.backfilled > 0 || result.unrecoverable > 0) {
123
+ log.info(result, "Retrospective cursor timestamps backfilled");
124
+ }
125
+ return result;
126
+ }
@@ -0,0 +1,79 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory retrospective: keep the cursor's timestamp when its row is deleted.
3
+ // ---------------------------------------------------------------------------
4
+ //
5
+ // The cursor normally sits on the latest assistant reply, and the retry route
6
+ // deletes exactly that row when the user regenerates it. A cursor written
7
+ // before the timestamp column existed carries only the id, so once the row is
8
+ // gone nothing can place it. The `message-deleted` hook delivers each deleted
9
+ // row's `(createdAt, id)` here: when the conversation's cursor is among the
10
+ // rows and has no timestamp yet, the row's `createdAt` is recorded. The
11
+ // timestamps travel in the call, so the write does not need the rows to
12
+ // still exist.
13
+
14
+ import { and, eq, isNull } from "drizzle-orm";
15
+
16
+ import { memoryRetrospectiveState } from "../../../persistence/schema/index.js";
17
+ import { withSqliteRetry } from "./host-utils.js";
18
+ import { getLogger } from "./logging.js";
19
+ import { memoryDbOrNull } from "./memory-db.js";
20
+ import { ensureRetrospectiveCursorColumn } from "./memory-retrospective-state.js";
21
+
22
+ const log = getLogger("memory-retrospective-cursor-preserve");
23
+
24
+ /**
25
+ * Record `createdAt` on the conversation's retrospective cursor when it points
26
+ * at one of `rows` and has no timestamp yet. Best-effort: an unavailable
27
+ * memory connection or column is a no-op, and a cursor the job has moved
28
+ * since the caller read it is skipped by the `IS NULL` guard.
29
+ */
30
+ export async function preserveRetrospectiveCursorTimestamps(
31
+ conversationId: string,
32
+ rows: ReadonlyArray<{ id: string; createdAt: number }>,
33
+ ): Promise<void> {
34
+ if (rows.length === 0) {
35
+ return;
36
+ }
37
+ const context = "preserveRetrospectiveCursorTimestamps";
38
+ try {
39
+ const mdb = memoryDbOrNull(context);
40
+ if (!mdb || !ensureRetrospectiveCursorColumn(context)) {
41
+ return;
42
+ }
43
+ const state = mdb
44
+ .select({
45
+ messageId: memoryRetrospectiveState.lastProcessedMessageId,
46
+ createdAt: memoryRetrospectiveState.lastProcessedCreatedAt,
47
+ })
48
+ .from(memoryRetrospectiveState)
49
+ .where(eq(memoryRetrospectiveState.conversationId, conversationId))
50
+ .get();
51
+ if (!state || state.createdAt !== null) {
52
+ return;
53
+ }
54
+ const row = rows.find((candidate) => candidate.id === state.messageId);
55
+ if (!row) {
56
+ return;
57
+ }
58
+ await withSqliteRetry(
59
+ () =>
60
+ mdb
61
+ .update(memoryRetrospectiveState)
62
+ .set({ lastProcessedCreatedAt: row.createdAt })
63
+ .where(
64
+ and(
65
+ eq(memoryRetrospectiveState.conversationId, conversationId),
66
+ eq(memoryRetrospectiveState.lastProcessedMessageId, row.id),
67
+ isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
68
+ ),
69
+ )
70
+ .run(),
71
+ { op: context, context: { conversationId } },
72
+ );
73
+ } catch (err) {
74
+ log.warn(
75
+ { err, conversationId },
76
+ "could not preserve the retrospective cursor timestamp before its row was deleted",
77
+ );
78
+ }
79
+ }
@@ -0,0 +1,28 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory retrospective: the cursor a state row hands to the messages-after
3
+ // reads.
4
+ // ---------------------------------------------------------------------------
5
+
6
+ import type { MessageCursor } from "../../../persistence/message-cursor.js";
7
+
8
+ /**
9
+ * The `(createdAt, id)` cursor a retrospective state row carries, or null
10
+ * when nothing has been processed yet (no row, or the `""` sentinel left by
11
+ * failure-only attempts). The timestamp lets the reads keep their bound after
12
+ * the cursor's row is deleted, which is what a regenerated reply does to the
13
+ * latest assistant message the cursor usually sits on.
14
+ */
15
+ export function retrospectiveCursor(
16
+ state: {
17
+ lastProcessedMessageId: string;
18
+ lastProcessedCreatedAt?: number | null;
19
+ } | null,
20
+ ): MessageCursor | null {
21
+ if (!state || state.lastProcessedMessageId === "") {
22
+ return null;
23
+ }
24
+ return {
25
+ id: state.lastProcessedMessageId,
26
+ createdAt: state.lastProcessedCreatedAt ?? null,
27
+ };
28
+ }
@@ -47,6 +47,7 @@ import { resolveCapabilities } from "../../../runtime/capabilities.js";
47
47
  import { getLogger } from "./logging.js";
48
48
  import { hasQualifyingUserMessageAfter } from "./memory-retrospective-accounting.js";
49
49
  import { isMemoryRetrospectiveSource } from "./memory-retrospective-constants.js";
50
+ import { retrospectiveCursor } from "./memory-retrospective-cursor.js";
50
51
  import { getRetrospectiveState } from "./memory-retrospective-state.js";
51
52
 
52
53
  const log = getLogger("memory-retrospective-enqueue");
@@ -139,7 +140,7 @@ function isRetrospectiveEnabled(): boolean {
139
140
  /**
140
141
  * The `memory.retrospective.requireUserActivity` gate: pass when the config
141
142
  * is off or the unprocessed tail (everything after the conversation's
142
- * `lastProcessedMessageId`) contains at least one user message carrying
143
+ * retrospective cursor) contains at least one user message carrying
143
144
  * non-tool_result content. A gate that cannot be evaluated (config or DB
144
145
  * unavailable) passes — an unevaluable gate must not silence retrospectives.
145
146
  */
@@ -153,10 +154,7 @@ function passesUserActivityGate(
153
154
  }
154
155
  const state = getRetrospectiveState(conversationId);
155
156
  if (
156
- hasQualifyingUserMessageAfter(
157
- conversationId,
158
- state?.lastProcessedMessageId ?? null,
159
- )
157
+ hasQualifyingUserMessageAfter(conversationId, retrospectiveCursor(state))
160
158
  ) {
161
159
  return true;
162
160
  }
@@ -58,7 +58,7 @@ import {
58
58
  parseClientOs,
59
59
  parseInterfaceId,
60
60
  } from "../../../channels/types.js";
61
- import { isV3TierActive } from "../../../config/memory-v3-gate.js";
61
+ import { isSkillImprovementActive } from "../../../config/memory-v3-gate.js";
62
62
  import type { AssistantConfig } from "../../../config/types.js";
63
63
  import { warmGuardianBindings } from "../../../contacts/guardian-delivery-reader.js";
64
64
  import { extractTurnContextTimestamp } from "../../../context/compactor.js";
@@ -95,6 +95,7 @@ import {
95
95
  MEMORY_RETROSPECTIVE_SOURCE,
96
96
  SKILL_MANAGEMENT_SKILL_ID,
97
97
  } from "./memory-retrospective-constants.js";
98
+ import { retrospectiveCursor } from "./memory-retrospective-cursor.js";
98
99
  import { loadRetrospectiveRunMessages } from "./memory-retrospective-fork-boundary.js";
99
100
  import { buildForkInstruction } from "./memory-retrospective-prompt.js";
100
101
  import {
@@ -357,7 +358,7 @@ export async function runForkBasedRetrospective(
357
358
  // `memory-retrospective-accounting.ts`.
358
359
  const newMessages = getRetrospectiveMessagesAfter(
359
360
  sourceConversationId,
360
- lastProcessedMessageId,
361
+ retrospectiveCursor(state),
361
362
  );
362
363
 
363
364
  if (newMessages.length === 0) {
@@ -448,7 +449,7 @@ export async function runForkBasedRetrospective(
448
449
  }
449
450
  const forkId = forkConversationRow.id;
450
451
 
451
- const procToSkillsActive = isV3TierActive(config);
452
+ const procToSkillsActive = isSkillImprovementActive(config);
452
453
  const instruction = buildForkInstruction({
453
454
  windowStartTimestamp,
454
455
  windowAnchorKind: turnContextTimestamp ? "turn_context" : "created_at",
@@ -650,6 +651,7 @@ export async function runForkBasedRetrospective(
650
651
  sourceConversationId,
651
652
  retrospectiveConversationId: forkId,
652
653
  cutoffMessageId,
654
+ cutoffCreatedAt: cutoffMessage.createdAt,
653
655
  newMessageCount: newMessages.length,
654
656
  prior,
655
657
  priorRemembers,
@@ -947,6 +949,11 @@ async function finalizeSuccessfulRetrospective(args: {
947
949
  sourceConversationId: string;
948
950
  retrospectiveConversationId: string;
949
951
  cutoffMessageId: string;
952
+ /**
953
+ * `createdAt` of the cutoff row, persisted beside its id so the cursor
954
+ * keeps bounding reads after the row is deleted.
955
+ */
956
+ cutoffCreatedAt: number;
950
957
  newMessageCount: number;
951
958
  prior: PriorRetrospective | null;
952
959
  priorRemembers: string[];
@@ -966,6 +973,7 @@ async function finalizeSuccessfulRetrospective(args: {
966
973
  sourceConversationId,
967
974
  retrospectiveConversationId,
968
975
  cutoffMessageId,
976
+ cutoffCreatedAt,
969
977
  newMessageCount,
970
978
  prior,
971
979
  priorRemembers,
@@ -977,6 +985,7 @@ async function finalizeSuccessfulRetrospective(args: {
977
985
  await upsertRetrospectiveState({
978
986
  conversationId: sourceConversationId,
979
987
  lastProcessedMessageId: cutoffMessageId,
988
+ lastProcessedCreatedAt: cutoffCreatedAt,
980
989
  lastRunAt: Date.now(),
981
990
  rememberedLog: appendToRememberedLog(priorRemembers, runRemembers),
982
991
  });
@@ -223,7 +223,7 @@ If your review window contains a PROCEDURE you actually carried out — a sequen
223
223
 
224
224
  When you do capture a procedure:
225
225
 
226
- 1. Deduplicate against existing skills first. Call \`find_similar_skills\` with the procedure's goal as its \`goal\` argument. Each hit carries a \`source\` (bundled, managed, plugin, workspace, or extra), and a managed hit also carries \`author\` (\`"assistant"\` if you authored it, \`"user"\` if a person did, omitted if untagged). You may only overwrite or refine a skill YOU authored — a hit with \`source: "managed"\` AND \`author: "assistant"\`. ANY other hit means the procedure is ALREADY COVERED: a non-managed source (bundled, plugin, workspace, or extra), OR a managed skill that is NOT \`author: "assistant"\` (a person wrote it, or it is untagged). For an ALREADY COVERED hit do not \`overwrite\` it, do not shadow it by creating a skill with its \`skill_id\`, and do not create a near-duplicate — skip it. Only when a returned skill is one of your own (\`source: "managed"\`, \`author: "assistant"\`) and is the SAME procedure, UPDATE it: call \`scaffold_managed_skill\` with that \`skill_id\` and \`overwrite: true\`, rewriting \`body_markdown\` from what you actually observed in the trace. Only CREATE a new skill (fresh \`skill_id\`) when no existing skill of any source covers the procedure. Bias strongly toward reusing or refining your own skills over spawning near-duplicates.
226
+ 1. Deduplicate against existing skills first. Call \`find_similar_skills\` with the procedure's goal as its \`goal\` argument. Each hit carries a \`source\` (bundled, managed, plugin, workspace, or extra), and a managed hit also carries \`author\` (\`"assistant"\` if you authored it, \`"user"\` if a person did, omitted if untagged). You may only overwrite or refine a skill YOU authored: a hit with \`source: "managed"\` AND \`author: "assistant"\`. ANY other hit means the procedure is ALREADY COVERED: a non-managed source (bundled, plugin, workspace, or extra), OR a managed skill that is NOT \`author: "assistant"\` (a person wrote it, or it is untagged). For an ALREADY COVERED hit do not \`overwrite\` it, do not shadow it by creating a skill with its \`skill_id\`, and do not create a near-duplicate. Skip it. Only when a returned skill is one of your own (\`source: "managed"\`, \`author: "assistant"\`) and is the SAME procedure, UPDATE it: call \`scaffold_managed_skill\` with that \`skill_id\` and \`overwrite: true\`, rewriting \`body_markdown\` from what you actually observed in the trace, and pass \`change_summary\` (the update is rejected without it): one or two short sentences (under 200 characters) for the person who reads the "Skill updated" notice, naming what you changed and what in the trace prompted it (for example "Added the retry after an expired session and the export endpoint that held steady."). The notice shows nothing else about the change, so name the concrete step, value, or gotcha rather than saying the skill was refined. Only CREATE a new skill (fresh \`skill_id\`) when no existing skill of any source covers the procedure. Bias strongly toward reusing or refining your own skills over spawning near-duplicates.
227
227
 
228
228
  2. Capture procedure-scoped knowledge alongside the body. Failure modes, gotchas, and cached values you observed in the trace (error signatures and how you recovered, preconditions, IDs/paths/endpoints that held steady) belong in companion files passed via \`scaffold_managed_skill\`'s \`files\` input (for example \`references/failure-modes.md\`), and the SKILL.md body should reference them so a future load surfaces them.
229
229