@vellumai/assistant 0.12.1 → 0.12.2-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -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
 
@@ -115,6 +115,11 @@ For everything else in your review window, use the \`remember\` tool on facts, p
115
115
  expect(out).toContain(
116
116
  "\n---\n\nIf your review window contains a PROCEDURE you actually carried out",
117
117
  );
118
+ // An UPDATE is announced by a notice whose only account of the change is
119
+ // what the pass passes here, so the instruction has to ask for it.
120
+ expect(out).toContain(
121
+ "and pass `change_summary` (the update is rejected without it): one or two short sentences (under 200 characters)",
122
+ );
118
123
  expect(
119
124
  out.endsWith(
120
125
  "skills are for executed, reusable procedures, not for facts.\n",
@@ -6,16 +6,22 @@ import { setConfig } from "../../../../__tests__/helpers/set-config.js";
6
6
  // pipeline (both flags default true under the real loader).
7
7
  setConfig("memory", { enabled: false, v2: { enabled: false } });
8
8
 
9
+ import { createConversation } from "../../../../persistence/conversation-crud.js";
9
10
  import {
10
11
  getDb,
11
12
  getMemorySqlite,
13
+ resetDb,
12
14
  } from "../../../../persistence/db-connection.js";
13
15
  import { initializeDb } from "../../../../persistence/db-init.js";
16
+ import { messages } from "../../../../persistence/schema/index.js";
14
17
  import {
18
+ _resetRetrospectiveCursorColumnForTests,
15
19
  appendToRememberedLog,
16
20
  bumpRetrospectiveLastRunAt,
21
+ ensureRetrospectiveCursorColumn,
17
22
  forkRetrospectiveState,
18
23
  getRetrospectiveState,
24
+ listRetrospectiveStates,
19
25
  REMEMBERED_LOG_MAX_BYTES,
20
26
  REMEMBERED_LOG_MAX_ENTRIES,
21
27
  upsertRetrospectiveState,
@@ -176,6 +182,219 @@ describe("memory-retrospective-state remembered log persistence", () => {
176
182
  });
177
183
  });
178
184
 
185
+ describe("memory-retrospective-state cursor timestamp", () => {
186
+ beforeEach(() => {
187
+ resetTables();
188
+ });
189
+
190
+ test("upsert persists lastProcessedCreatedAt and both reads return it", async () => {
191
+ await upsertRetrospectiveState({
192
+ conversationId: "conv-ts",
193
+ lastProcessedMessageId: "m1",
194
+ lastProcessedCreatedAt: 1_234,
195
+ lastRunAt: 1000,
196
+ });
197
+
198
+ expect(getRetrospectiveState("conv-ts")?.lastProcessedCreatedAt).toBe(
199
+ 1_234,
200
+ );
201
+ expect(
202
+ listRetrospectiveStates(10).find((s) => s.conversationId === "conv-ts")
203
+ ?.lastProcessedCreatedAt,
204
+ ).toBe(1_234);
205
+ });
206
+
207
+ test("an upsert that omits the timestamp records unknown for the new pointer", async () => {
208
+ await upsertRetrospectiveState({
209
+ conversationId: "conv-ts",
210
+ lastProcessedMessageId: "m1",
211
+ lastProcessedCreatedAt: 1_234,
212
+ lastRunAt: 1000,
213
+ });
214
+ await upsertRetrospectiveState({
215
+ conversationId: "conv-ts",
216
+ lastProcessedMessageId: "m2",
217
+ lastRunAt: 2000,
218
+ });
219
+
220
+ const state = getRetrospectiveState("conv-ts");
221
+ expect(state?.lastProcessedMessageId).toBe("m2");
222
+ expect(state?.lastProcessedCreatedAt).toBeNull();
223
+ });
224
+
225
+ test("bumpRetrospectiveLastRunAt leaves the timestamp untouched", async () => {
226
+ await upsertRetrospectiveState({
227
+ conversationId: "conv-ts",
228
+ lastProcessedMessageId: "m1",
229
+ lastProcessedCreatedAt: 1_234,
230
+ lastRunAt: 1000,
231
+ });
232
+ await bumpRetrospectiveLastRunAt("conv-ts", 5000);
233
+
234
+ const state = getRetrospectiveState("conv-ts");
235
+ expect(state?.lastRunAt).toBe(5000);
236
+ expect(state?.lastProcessedCreatedAt).toBe(1_234);
237
+ });
238
+
239
+ test("forking carries the forked pointer's createdAt, read through the main handle", async () => {
240
+ const db = getDb();
241
+ createConversation({ id: "conv-fork-ts-src" });
242
+ createConversation({ id: "conv-fork-ts-dst" });
243
+ for (const [id, conversationId] of [
244
+ ["src-m1", "conv-fork-ts-src"],
245
+ ["dst-m1", "conv-fork-ts-dst"],
246
+ ] as const) {
247
+ db.insert(messages)
248
+ .values({
249
+ id,
250
+ conversationId,
251
+ role: "user",
252
+ content: "[]",
253
+ createdAt: 4_000,
254
+ metadata: null,
255
+ })
256
+ .run();
257
+ }
258
+ await upsertRetrospectiveState({
259
+ conversationId: "conv-fork-ts-src",
260
+ lastProcessedMessageId: "src-m1",
261
+ lastProcessedCreatedAt: 4_000,
262
+ lastRunAt: 1,
263
+ });
264
+
265
+ forkRetrospectiveState({
266
+ database: db,
267
+ sourceConversationId: "conv-fork-ts-src",
268
+ forkedConversationId: "conv-fork-ts-dst",
269
+ forkedMessageIds: new Map([["src-m1", "dst-m1"]]),
270
+ lastCopiedSourceMessageId: "src-m1",
271
+ });
272
+
273
+ const child = getRetrospectiveState("conv-fork-ts-dst");
274
+ expect(child?.lastProcessedMessageId).toBe("dst-m1");
275
+ expect(child?.lastProcessedCreatedAt).toBe(4_000);
276
+
277
+ db.run(`DELETE FROM messages`);
278
+ db.run(`DELETE FROM conversations`);
279
+ });
280
+
281
+ test("a source cursor whose row was regenerated away is placed by its stored bound, not clamped past the copies", async () => {
282
+ const db = getDb();
283
+ createConversation({ id: "conv-fork-bound-src" });
284
+ createConversation({ id: "conv-fork-bound-dst" });
285
+ // src-m2 (createdAt 2_000) was the cursor; a regenerate deleted it and
286
+ // src-m3 replaced it. The fork copies src-m1 and src-m3.
287
+ for (const [id, conversationId, createdAt] of [
288
+ ["src-m1", "conv-fork-bound-src", 1_000],
289
+ ["src-m3", "conv-fork-bound-src", 3_000],
290
+ ["dst-m1", "conv-fork-bound-dst", 1_000],
291
+ ["dst-m3", "conv-fork-bound-dst", 3_000],
292
+ ] as const) {
293
+ db.insert(messages)
294
+ .values({
295
+ id,
296
+ conversationId,
297
+ role: "user",
298
+ content: "[]",
299
+ createdAt,
300
+ metadata: null,
301
+ })
302
+ .run();
303
+ }
304
+ await upsertRetrospectiveState({
305
+ conversationId: "conv-fork-bound-src",
306
+ lastProcessedMessageId: "src-m2",
307
+ lastProcessedCreatedAt: 2_000,
308
+ lastRunAt: 1,
309
+ });
310
+
311
+ forkRetrospectiveState({
312
+ database: db,
313
+ sourceConversationId: "conv-fork-bound-src",
314
+ forkedConversationId: "conv-fork-bound-dst",
315
+ forkedMessageIds: new Map([
316
+ ["src-m1", "dst-m1"],
317
+ ["src-m3", "dst-m3"],
318
+ ]),
319
+ lastCopiedSourceMessageId: "src-m3",
320
+ });
321
+
322
+ const child = getRetrospectiveState("conv-fork-bound-dst");
323
+ expect(child?.lastProcessedMessageId).toBe("dst-m1");
324
+ expect(child?.lastProcessedCreatedAt).toBe(1_000);
325
+
326
+ // A bound before every copied row leaves the child with nothing processed.
327
+ await upsertRetrospectiveState({
328
+ conversationId: "conv-fork-bound-src",
329
+ lastProcessedMessageId: "src-m0",
330
+ lastProcessedCreatedAt: 500,
331
+ lastRunAt: 1,
332
+ });
333
+ forkRetrospectiveState({
334
+ database: db,
335
+ sourceConversationId: "conv-fork-bound-src",
336
+ forkedConversationId: "conv-fork-bound-dst",
337
+ forkedMessageIds: new Map([
338
+ ["src-m1", "dst-m1"],
339
+ ["src-m3", "dst-m3"],
340
+ ]),
341
+ lastCopiedSourceMessageId: "src-m3",
342
+ });
343
+ expect(
344
+ getRetrospectiveState("conv-fork-bound-dst")?.lastProcessedMessageId,
345
+ ).toBe("");
346
+
347
+ db.run(`DELETE FROM messages`);
348
+ db.run(`DELETE FROM conversations`);
349
+ });
350
+
351
+ test("ensureRetrospectiveCursorColumn adds the column to a legacy table once and is a no-op after", () => {
352
+ const raw = getMemorySqlite()!;
353
+ raw.exec("DROP TABLE memory_retrospective_state");
354
+ raw.exec(`
355
+ CREATE TABLE memory_retrospective_state (
356
+ conversation_id TEXT PRIMARY KEY,
357
+ last_processed_message_id TEXT NOT NULL,
358
+ last_run_at INTEGER NOT NULL,
359
+ remembered_log TEXT
360
+ )
361
+ `);
362
+ _resetRetrospectiveCursorColumnForTests();
363
+
364
+ expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
365
+ expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
366
+
367
+ const columns = raw
368
+ .query("PRAGMA table_info(memory_retrospective_state)")
369
+ .all() as Array<{ name: string }>;
370
+ expect(
371
+ columns.filter((c) => c.name === "last_processed_created_at"),
372
+ ).toHaveLength(1);
373
+ });
374
+
375
+ test("a replaced memory connection is probed again, so a restored database without the column gets it", () => {
376
+ expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
377
+
378
+ // What a restore or import does: the stored connections are dropped and
379
+ // reopened. Strip the column on the reopened file to stand in for an
380
+ // older database.
381
+ resetDb();
382
+ const reopened = getMemorySqlite()!;
383
+ reopened.exec(
384
+ "ALTER TABLE memory_retrospective_state DROP COLUMN last_processed_created_at",
385
+ );
386
+
387
+ expect(getRetrospectiveState("conv-none")).toBeNull();
388
+ expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
389
+ const columns = reopened
390
+ .query("PRAGMA table_info(memory_retrospective_state)")
391
+ .all() as Array<{ name: string }>;
392
+ expect(columns.some((c) => c.name === "last_processed_created_at")).toBe(
393
+ true,
394
+ );
395
+ });
396
+ });
397
+
179
398
  describe("fail-soft when the underlying statement fails", () => {
180
399
  // The memory connection is present, but the relocated table is gone (a
181
400
  // corrupt/dropped table, SQLITE_FULL, I/O error, or SQLITE_BUSY after
@@ -412,3 +412,34 @@ describe("listSweepCandidateConversationIds", () => {
412
412
  ]);
413
413
  });
414
414
  });
415
+
416
+ describe("runRetrospectiveSweep: cursor survives a regenerated reply", () => {
417
+ beforeEach(() => {
418
+ resetTables();
419
+ enqueueCalls = [];
420
+ enqueueDeclines = new Set();
421
+ });
422
+
423
+ test("a stale conversation whose cursor row was regenerated away is still swept", async () => {
424
+ const conv = createConversation({ id: "conv-a" });
425
+ insertMessage(conv.id, { createdAt: 1_000 });
426
+ const reply = insertMessage(conv.id, {
427
+ role: "assistant",
428
+ createdAt: 2_000,
429
+ });
430
+ await upsertRetrospectiveState({
431
+ conversationId: conv.id,
432
+ lastProcessedMessageId: reply,
433
+ lastProcessedCreatedAt: 2_000,
434
+ lastRunAt: Date.now() - SWEEP_INTERVAL_MS - 60_000,
435
+ });
436
+ getDb().delete(messages).where(eq(messages.id, reply)).run();
437
+ insertMessage(conv.id, { role: "assistant", createdAt: 3_000 });
438
+
439
+ await runRetrospectiveSweep(makeConfig());
440
+
441
+ expect(enqueueCalls).toEqual([
442
+ { conversationId: conv.id, trigger: "sweep" },
443
+ ]);
444
+ });
445
+ });