@vellumai/assistant 0.8.3 → 0.8.4

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 (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -32,7 +32,9 @@
32
32
  // conversations left by a mid-run crash are swept by
33
33
  // `memory-retrospective-startup-cleanup.ts`.
34
34
 
35
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
35
36
  import type { AssistantConfig } from "../config/types.js";
37
+ import { extractTurnContextTimestamp } from "../context/compactor.js";
36
38
  import { resolveTurnTimezoneContext } from "../daemon/date-context.js";
37
39
  import {
38
40
  getAssistantName,
@@ -40,13 +42,17 @@ import {
40
42
  } from "../daemon/identity-helpers.js";
41
43
  import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../daemon/trust-context.js";
42
44
  import { formatMessageSliceForTranscript } from "../export/transcript-formatter.js";
45
+ import type { Message } from "../providers/types.js";
43
46
  import { wakeAgentForOpportunity } from "../runtime/agent-wake.js";
44
47
  import { getLogger } from "../util/logger.js";
45
48
  import { getWorkspaceDir } from "../util/platform.js";
46
49
  import { bootstrapConversation } from "./conversation-bootstrap.js";
47
50
  import {
51
+ addMessage,
48
52
  deleteConversation,
49
53
  findMostRecentRetrospectiveFor,
54
+ forkConversation,
55
+ getConversation,
50
56
  getMessages,
51
57
  getMessagesAfter,
52
58
  } from "./conversation-crud.js";
@@ -56,7 +62,9 @@ import {
56
62
  type MemoryJobType,
57
63
  } from "./jobs-store.js";
58
64
  import {
65
+ MEMORY_RETROSPECTIVE_FORK_SOURCE,
59
66
  MEMORY_RETROSPECTIVE_GROUP_ID,
67
+ MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
60
68
  MEMORY_RETROSPECTIVE_SOURCE,
61
69
  } from "./memory-retrospective-constants.js";
62
70
  import {
@@ -65,6 +73,17 @@ import {
65
73
  upsertRetrospectiveState,
66
74
  } from "./memory-retrospective-state.js";
67
75
 
76
+ /**
77
+ * Feature flag that switches the retrospective handler between the legacy
78
+ * transcript-based path (renders the new-message slice into a `<transcript>`
79
+ * block and wakes an empty background conversation) and the new fork-based
80
+ * path (forks the source through its latest message, persists a user-role
81
+ * instruction, and wakes the fork). The fork path lets the retrospective hit
82
+ * the provider prompt cache and read compaction summary + tail messages
83
+ * natively.
84
+ */
85
+ const MEMORY_RETROSPECTIVE_FORK_FLAG = "memory-retrospective-fork" as const;
86
+
68
87
  const log = getLogger("memory-retrospective-job");
69
88
 
70
89
  /**
@@ -96,6 +115,24 @@ export async function memoryRetrospectiveJob(
96
115
  return { kind: "no_new_messages" };
97
116
  }
98
117
 
118
+ const useFork = isAssistantFeatureFlagEnabled(
119
+ MEMORY_RETROSPECTIVE_FORK_FLAG,
120
+ config,
121
+ );
122
+ return useFork
123
+ ? runForkBasedRetrospective(sourceConversationId, config)
124
+ : runLegacyRetrospective(sourceConversationId, config);
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Legacy path — transcript-rendered slice + empty background conversation.
129
+ // Kept behind the `memory-retrospective-fork` flag for safe rollback.
130
+ // ---------------------------------------------------------------------------
131
+
132
+ async function runLegacyRetrospective(
133
+ sourceConversationId: string,
134
+ config: AssistantConfig,
135
+ ): Promise<MemoryRetrospectiveOutcome> {
99
136
  // 1. Load state + compute the message slice.
100
137
  const state = getRetrospectiveState(sourceConversationId);
101
138
  const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;
@@ -142,7 +179,7 @@ export async function memoryRetrospectiveJob(
142
179
  assistantName: getAssistantName(),
143
180
  userName: resolveUserName(getWorkspaceDir()),
144
181
  });
145
- const prompt = buildPrompt({
182
+ const prompt = buildLegacyPrompt({
146
183
  transcript,
147
184
  priorRemembers,
148
185
  timeZone: timezoneContext.effectiveTimezone,
@@ -191,17 +228,7 @@ export async function memoryRetrospectiveJob(
191
228
  lastRunAt: Date.now(),
192
229
  });
193
230
 
194
- const followUpJobIds: string[] = [];
195
- for (const jobType of FOLLOW_UP_JOB_TYPES) {
196
- try {
197
- followUpJobIds.push(enqueueMemoryJob(jobType, {}));
198
- } catch (err) {
199
- log.warn(
200
- { err, jobType },
201
- "memory-retrospective: failed to enqueue follow-up job; continuing",
202
- );
203
- }
204
- }
231
+ const followUpJobIds = enqueueFollowUpJobs();
205
232
 
206
233
  log.info(
207
234
  {
@@ -210,6 +237,7 @@ export async function memoryRetrospectiveJob(
210
237
  cutoffMessageId,
211
238
  newMessageCount: newMessages.length,
212
239
  priorRememberCount: priorRemembers.length,
240
+ kind: "legacy",
213
241
  },
214
242
  "memory-retrospective invoked",
215
243
  );
@@ -251,6 +279,243 @@ export async function memoryRetrospectiveJob(
251
279
  };
252
280
  }
253
281
 
282
+ // ---------------------------------------------------------------------------
283
+ // Fork-based path — fork the source through its latest message, persist a
284
+ // user-role retrospective instruction at the tail, and wake the fork. The
285
+ // fork inherits compaction state (summary + tail messages) via the existing
286
+ // `forkConversation` machinery, and its prefix matches the source's prefix
287
+ // so provider prompt caching hits.
288
+ // ---------------------------------------------------------------------------
289
+
290
+ async function runForkBasedRetrospective(
291
+ sourceConversationId: string,
292
+ config: AssistantConfig,
293
+ ): Promise<MemoryRetrospectiveOutcome> {
294
+ const sourceConversation = getConversation(sourceConversationId);
295
+ if (!sourceConversation) {
296
+ log.warn(
297
+ { sourceConversationId },
298
+ "memory-retrospective (fork): source conversation not found; skipping",
299
+ );
300
+ return { kind: "no_new_messages" };
301
+ }
302
+
303
+ const state = getRetrospectiveState(sourceConversationId);
304
+ const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;
305
+ const newMessages = getMessagesAfter(
306
+ sourceConversationId,
307
+ lastProcessedMessageId,
308
+ );
309
+
310
+ if (newMessages.length === 0) {
311
+ return { kind: "no_new_messages" };
312
+ }
313
+
314
+ const cutoffMessage = newMessages[newMessages.length - 1];
315
+ if (!cutoffMessage) {
316
+ return { kind: "no_new_messages" };
317
+ }
318
+ const cutoffMessageId = cutoffMessage.id;
319
+
320
+ // The fork carries the full conversation, so the agent needs an explicit
321
+ // anchor telling it where the review window begins. Prefer the user
322
+ // turn's `<turn_context>` `current_time:` (matches the conversation's
323
+ // own clock); fall back to ISO-formatted `createdAt` when the slice
324
+ // begins with an assistant turn or tool_result-only user message.
325
+ const windowStartTimestamp =
326
+ findFirstTurnContextTimestamp(newMessages) ??
327
+ new Date(newMessages[0]!.createdAt).toISOString();
328
+
329
+ // Pull prior `remember` calls BEFORE forking — otherwise
330
+ // `findMostRecentRetrospectiveFor` could locate this run's own fork.
331
+ const priorRemembers =
332
+ collectPriorRetrospectiveRemembers(sourceConversationId);
333
+
334
+ // Pin the fork to `cutoffMessageId` so messages arriving between the slice
335
+ // read above and this call don't sneak into the fork. Without
336
+ // `throughMessageId`, the fork snapshots the latest source message at fork
337
+ // time and this run would process turns past the cutoff while state only
338
+ // advances to `cutoffMessageId`, causing the next retrospective to
339
+ // reprocess (and potentially re-`remember`) those same turns.
340
+ //
341
+ // `forkConversation` inherits `contextSummary` /
342
+ // `contextCompactedMessageCount` / `contextCompactedAt` when the fork
343
+ // point sits within the visible window. Compacted source ⇒ compacted
344
+ // fork ⇒ summary + tail visible to the agent natively.
345
+ let forkConversationRow: ReturnType<typeof forkConversation>;
346
+ try {
347
+ forkConversationRow = forkConversation({
348
+ conversationId: sourceConversationId,
349
+ throughMessageId: cutoffMessageId,
350
+ source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
351
+ title: `${sourceConversation.title ?? "Untitled"} (Retrospective)`,
352
+ conversationType: "background",
353
+ groupId: MEMORY_RETROSPECTIVE_GROUP_ID,
354
+ });
355
+ } catch (err) {
356
+ bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
357
+ log.error(
358
+ { err, sourceConversationId },
359
+ "memory-retrospective (fork): forkConversation failed",
360
+ );
361
+ throw err;
362
+ }
363
+ const forkId = forkConversationRow.id;
364
+
365
+ const timezoneContext = resolveTurnTimezoneContext({
366
+ configuredUserTimeZone: config.ui.userTimezone ?? null,
367
+ detectedTimezone: config.ui.detectedTimezone ?? null,
368
+ });
369
+ const instruction = buildForkInstruction({
370
+ windowStartTimestamp,
371
+ priorRemembers,
372
+ timeZone: timezoneContext.effectiveTimezone,
373
+ isFirstPass: lastProcessedMessageId == null,
374
+ });
375
+ try {
376
+ await addMessage(
377
+ forkId,
378
+ "user",
379
+ JSON.stringify([{ type: "text", text: instruction }]),
380
+ { kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND, hidden: true },
381
+ { skipIndexing: true },
382
+ );
383
+ } catch (err) {
384
+ log.error(
385
+ { err, forkId, sourceConversationId },
386
+ "memory-retrospective (fork): failed to persist instruction message",
387
+ );
388
+ safeDeleteForkOnFailure(forkId);
389
+ bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
390
+ throw err;
391
+ }
392
+
393
+ // `skipHintInjection: true` because the instruction is already a
394
+ // persisted message — the wake's hint sandwich would only duplicate it.
395
+ let wakeSucceeded = false;
396
+ let failureReason: string | undefined;
397
+ let threw: unknown;
398
+ try {
399
+ const result = await wakeAgentForOpportunity({
400
+ conversationId: forkId,
401
+ hint: "",
402
+ source: MEMORY_RETROSPECTIVE_SOURCE,
403
+ trustContext: INTERNAL_GUARDIAN_TRUST_CONTEXT,
404
+ callSite: "memoryRetrospective",
405
+ hintRole: "user",
406
+ skipHintInjection: true,
407
+ suppressAutoCompaction: true,
408
+ // The fork's title already reads "(Retrospective)", so an empty-body
409
+ // "Conversation Woke" surface card on top of it would be noise. Suppress
410
+ // it — clients should display the fork as a normal background conv.
411
+ suppressWakeSurface: true,
412
+ });
413
+ wakeSucceeded = result.invoked;
414
+ failureReason = result.reason;
415
+ } catch (err) {
416
+ threw = err;
417
+ failureReason = err instanceof Error ? err.message : String(err);
418
+ log.error(
419
+ { err, forkId, sourceConversationId },
420
+ "memory-retrospective (fork): wake threw",
421
+ );
422
+ }
423
+
424
+ if (wakeSucceeded) {
425
+ upsertRetrospectiveState({
426
+ conversationId: sourceConversationId,
427
+ lastProcessedMessageId: cutoffMessageId,
428
+ lastRunAt: Date.now(),
429
+ });
430
+
431
+ const followUpJobIds = enqueueFollowUpJobs();
432
+
433
+ log.info(
434
+ {
435
+ sourceConversationId,
436
+ backgroundConversationId: forkId,
437
+ cutoffMessageId,
438
+ newMessageCount: newMessages.length,
439
+ priorRememberCount: priorRemembers.length,
440
+ windowStartTimestamp,
441
+ kind: "fork",
442
+ },
443
+ "memory-retrospective invoked",
444
+ );
445
+ return {
446
+ kind: "invoked",
447
+ backgroundConversationId: forkId,
448
+ cutoffMessageId,
449
+ newMessageCount: newMessages.length,
450
+ followUpJobIds,
451
+ };
452
+ }
453
+
454
+ bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
455
+ safeDeleteForkOnFailure(forkId);
456
+
457
+ if (threw !== undefined) {
458
+ throw threw;
459
+ }
460
+
461
+ return {
462
+ kind: "wake_failed",
463
+ reason: failureReason,
464
+ conversationId: forkId,
465
+ };
466
+ }
467
+
468
+ function enqueueFollowUpJobs(): string[] {
469
+ const followUpJobIds: string[] = [];
470
+ for (const jobType of FOLLOW_UP_JOB_TYPES) {
471
+ try {
472
+ followUpJobIds.push(enqueueMemoryJob(jobType, {}));
473
+ } catch (err) {
474
+ log.warn(
475
+ { err, jobType },
476
+ "memory-retrospective: failed to enqueue follow-up job; continuing",
477
+ );
478
+ }
479
+ }
480
+ return followUpJobIds;
481
+ }
482
+
483
+ function safeDeleteForkOnFailure(forkId: string): void {
484
+ try {
485
+ deleteConversation(forkId);
486
+ } catch (err) {
487
+ log.warn(
488
+ { err, forkId },
489
+ "memory-retrospective (fork): failed to delete fork on wake failure; continuing",
490
+ );
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Walk the slice and return the `<turn_context>` `current_time:` value from
496
+ * the first message that has one (typically the first user message). The
497
+ * agent uses this as the explicit anchor for the review window inside its
498
+ * forked history.
499
+ */
500
+ function findFirstTurnContextTimestamp(
501
+ messages: Array<{ role: string; content: string }>,
502
+ ): string | null {
503
+ for (const row of messages) {
504
+ if (row.role !== "user") continue;
505
+ let blocks: unknown;
506
+ try {
507
+ blocks = JSON.parse(row.content);
508
+ } catch {
509
+ continue;
510
+ }
511
+ if (!Array.isArray(blocks)) continue;
512
+ const message = { role: "user", content: blocks } as Message;
513
+ const ts = extractTurnContextTimestamp(message);
514
+ if (ts) return ts;
515
+ }
516
+ return null;
517
+ }
518
+
254
519
  // ---------------------------------------------------------------------------
255
520
  // Prior-retrospective remember extraction
256
521
  // ---------------------------------------------------------------------------
@@ -261,9 +526,23 @@ export async function memoryRetrospectiveJob(
261
526
  * array on first run (no prior retrospective) or when the prior run had no
262
527
  * `remember` calls (it found nothing to save).
263
528
  *
264
- * This is bounded a single retrospective conversation, however long the
265
- * source conversation has grown. Older retrospectives' saves are already
266
- * baked into the most recent one's `<already_remembered>` block transitively.
529
+ * Two artifact shapes exist depending on which path produced the prior
530
+ * retrospective:
531
+ *
532
+ * - **Legacy** (`source === MEMORY_RETROSPECTIVE_SOURCE`): empty bg
533
+ * conversation containing only the wake's tail (`remember` tool_use
534
+ * blocks). Scan everything.
535
+ * - **Fork** (`source === MEMORY_RETROSPECTIVE_FORK_SOURCE`): full source
536
+ * prefix forked in, followed by the retrospective's post-fork tail.
537
+ * The forked prefix contains the source conversation's own inline
538
+ * `remember` calls — scanning the whole row would dump source-inline
539
+ * saves into the dedup baseline and inflate it dramatically. Restrict
540
+ * to messages created **after** `forkParentMessageId` (the last copied
541
+ * message); only messages after that boundary came from this
542
+ * retrospective's own work.
543
+ *
544
+ * Older retrospectives' saves remain reflected transitively because each
545
+ * retrospective dedups against the one before it.
267
546
  */
268
547
  function collectPriorRetrospectiveRemembers(
269
548
  sourceConversationId: string,
@@ -280,9 +559,67 @@ function collectPriorRetrospectiveRemembers(
280
559
  );
281
560
  return [];
282
561
  }
562
+
563
+ const priorConv = getConversation(prior.id);
564
+ if (priorConv?.source === MEMORY_RETROSPECTIVE_FORK_SOURCE) {
565
+ // For fork-kind rows, prior `remember` calls live in the post-fork
566
+ // tail. `cloneForkMessageMetadata` stamps every copied message with
567
+ // `forkSourceMessageId` (preserving any existing value when the source
568
+ // was itself a fork), so the LAST message in the fork carrying
569
+ // `forkSourceMessageId` is the boundary — its value can point to any
570
+ // ancestor when the source was a nested fork, so we can't match it
571
+ // against `forkParentMessageId`. Everything strictly past that
572
+ // timestamp is post-fork.
573
+ const boundaryCreatedAt = findForkBoundaryCreatedAt(messages);
574
+ if (boundaryCreatedAt == null) {
575
+ log.warn(
576
+ { priorConversationId: prior.id },
577
+ "memory-retrospective: fork-kind prior has no message with forkSourceMessageId metadata; treating dedup as empty",
578
+ );
579
+ return [];
580
+ }
581
+ return extractRememberContents(
582
+ messages.filter((m) => m.createdAt > boundaryCreatedAt),
583
+ );
584
+ }
585
+
283
586
  return extractRememberContents(messages);
284
587
  }
285
588
 
589
+ /**
590
+ * Locate the boundary timestamp between the fork's prefix and its post-fork
591
+ * tail. Scans from the end for the last message whose metadata carries a
592
+ * `forkSourceMessageId` stamp (the last copied source message); its
593
+ * `createdAt` is the boundary. The stamp's value may point at any ancestor
594
+ * when the source was itself a fork (`cloneForkMessageMetadata` preserves
595
+ * pre-existing values), so we only check for presence, not equality.
596
+ * Returns `null` only if no copied messages remain (corrupted fork metadata
597
+ * or empty fork — caller logs + degrades).
598
+ */
599
+ function findForkBoundaryCreatedAt(
600
+ forkMessages: Array<{
601
+ id: string;
602
+ createdAt: number;
603
+ metadata: string | null;
604
+ }>,
605
+ ): number | null {
606
+ for (let i = forkMessages.length - 1; i >= 0; i--) {
607
+ const row = forkMessages[i]!;
608
+ if (!row.metadata) continue;
609
+ try {
610
+ const parsed = JSON.parse(row.metadata) as {
611
+ forkSourceMessageId?: unknown;
612
+ };
613
+ if (typeof parsed.forkSourceMessageId === "string") {
614
+ return row.createdAt;
615
+ }
616
+ } catch {
617
+ continue;
618
+ }
619
+ }
620
+ return null;
621
+ }
622
+
286
623
  interface MessageLike {
287
624
  role: string;
288
625
  content: string;
@@ -339,17 +676,17 @@ function neutralizeSentinels(s: string): string {
339
676
  );
340
677
  }
341
678
 
342
- interface PromptArgs {
679
+ interface LegacyPromptArgs {
343
680
  transcript: string;
344
681
  priorRemembers: string[];
345
682
  timeZone: string;
346
683
  }
347
684
 
348
- function buildPrompt({
685
+ function buildLegacyPrompt({
349
686
  transcript,
350
687
  priorRemembers,
351
688
  timeZone,
352
- }: PromptArgs): string {
689
+ }: LegacyPromptArgs): string {
353
690
  const safeTranscript = neutralizeSentinels(transcript);
354
691
  const renderedPrior =
355
692
  priorRemembers.length === 0
@@ -376,3 +713,56 @@ Two dedup sources to skip:
376
713
  For everything else, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. One \`remember\` call per fact. If nothing new is worth saving, say "Nothing new to save." and stop.
377
714
  `;
378
715
  }
716
+
717
+ // ---------------------------------------------------------------------------
718
+ // Fork-based retrospective instruction
719
+ // ---------------------------------------------------------------------------
720
+
721
+ interface ForkInstructionArgs {
722
+ windowStartTimestamp: string;
723
+ priorRemembers: string[];
724
+ timeZone: string;
725
+ /** True when this is the first retrospective pass over the source conversation. */
726
+ isFirstPass: boolean;
727
+ }
728
+
729
+ /**
730
+ * Build the user-role instruction message appended to the forked conversation.
731
+ * The agent reads the conversation natively (including any inherited compaction
732
+ * summary + tail messages), so the prompt is short — it just anchors the
733
+ * review window by `<turn_context>` timestamp and lists the prior
734
+ * retrospective's saves for cross-kind dedup (a legacy-kind prior's
735
+ * `remember` calls aren't visible inside the forked conversation history).
736
+ */
737
+ function buildForkInstruction({
738
+ windowStartTimestamp,
739
+ priorRemembers,
740
+ timeZone,
741
+ isFirstPass,
742
+ }: ForkInstructionArgs): string {
743
+ const renderedPrior =
744
+ priorRemembers.length === 0
745
+ ? "(none — this is your first retrospective over this conversation)"
746
+ : priorRemembers.map((c) => `- ${neutralizeSentinels(c)}`).join("\n");
747
+
748
+ const windowAnchor = isFirstPass
749
+ ? "Your review window is the full conversation above."
750
+ : `Your review window starts at the user turn with \`current_time: ${neutralizeSentinels(windowStartTimestamp)}\` (timezone: ${timeZone}) and ends at the most recent message.`;
751
+
752
+ return `This is a memory retrospective pass over the conversation above.
753
+
754
+ ${windowAnchor}
755
+
756
+ Here are the facts you saved in your previous retrospective pass over this conversation (so you don't restate them):
757
+
758
+ <already_remembered>
759
+ ${renderedPrior}
760
+ </already_remembered>
761
+
762
+ Two dedup sources to skip:
763
+ 1. Anything semantically captured in <already_remembered> above (from your prior retrospective pass).
764
+ 2. Anything you already called \`remember\` on inline within your review window — those appear as \`tool_use\` blocks with \`name: "remember"\` in your history.
765
+
766
+ For everything else in your review window, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. One \`remember\` call per fact. If nothing new is worth saving, say "Nothing new to save." and stop.
767
+ `;
768
+ }
@@ -45,7 +45,7 @@ import {
45
45
  import { getLogger } from "../util/logger.js";
46
46
  import { deleteConversation } from "./conversation-crud.js";
47
47
  import { getDb } from "./db-connection.js";
48
- import { MEMORY_RETROSPECTIVE_SOURCE } from "./memory-retrospective-constants.js";
48
+ import { MEMORY_RETROSPECTIVE_SOURCES } from "./memory-retrospective-constants.js";
49
49
  import { conversations, memoryJobs } from "./schema.js";
50
50
 
51
51
  const log = getLogger("memory-retrospective-startup-cleanup");
@@ -103,7 +103,7 @@ export function sweepOrphanMemoryRetrospectiveConversations(
103
103
  .from(conversations)
104
104
  .where(
105
105
  and(
106
- eq(conversations.source, MEMORY_RETROSPECTIVE_SOURCE),
106
+ inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
107
107
  isNotNull(conversations.forkParentConversationId),
108
108
  ),
109
109
  )
@@ -129,7 +129,7 @@ export function sweepOrphanMemoryRetrospectiveConversations(
129
129
  .from(conversations)
130
130
  .where(
131
131
  and(
132
- eq(conversations.source, MEMORY_RETROSPECTIVE_SOURCE),
132
+ inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
133
133
  // Conservative: only sweep rows that have had at least one message
134
134
  // AND haven't seen activity recently. Conversations without a
135
135
  // last_message_at value are too fresh to assess.
@@ -44,19 +44,37 @@ export interface MemoryV2ConceptRowRecord {
44
44
  * - `prior_state` — carried over from prior turn's activation state.
45
45
  * - `ann_top50` — entered via ANN top-K candidate pool.
46
46
  * - `both` — present in both prior state and ANN pool.
47
- * - `router` — selected by the Sonnet router (memory-v2 router
48
- * mode). Router-mode rows zero out all activation values
49
- * (`finalActivation`, `ownActivation`, `priorActivation`, channel
50
- * similarities, rerank boosts, `spreadContribution`) because the
51
- * router does not compute spreading-activation scores.
47
+ * - `router` — legacy tag for memory-v2 router selections written
48
+ * before tier-aware provenance landed. New rows never use this; old
49
+ * activation log rows still carry it and the inspector renders it
50
+ * as-is for backward compat.
51
+ * - `tier1` — router-mode, selected by the tier-1 (recently
52
+ * modified) batch.
53
+ * - `tier2` — router-mode, selected by the tier-2 (highest EMA)
54
+ * batch.
55
+ * - `tier3:<N>` — router-mode, selected by tier-3 batch N (0-indexed).
56
+ * A single-batch (no-tier carve-out) workspace produces `tier3:0`.
57
+ * The bucket index lets inspector queries attribute selections to
58
+ * specific hash-bucketed parallel calls.
52
59
  * - `carry_over` — router-mode row representing a slug carried over
53
60
  * from `priorEverInjected` that the router did NOT re-pick on this
54
61
  * turn. The cached attachment from a prior turn is still present
55
- * on a prior user message; emitting `source: "router"` for these
62
+ * on a prior user message; emitting one of the tier tags for these
56
63
  * rows would overcount router selections in inspector queries.
57
- * Same zeroed activation values as `router`.
64
+ *
65
+ * All router-mode rows (`tier*`, `router`, `carry_over`) zero out the
66
+ * activation values (`finalActivation`, `ownActivation`, etc.) because
67
+ * the router does not compute spreading-activation scores.
58
68
  */
59
- source: "prior_state" | "ann_top50" | "both" | "router" | "carry_over";
69
+ source:
70
+ | "prior_state"
71
+ | "ann_top50"
72
+ | "both"
73
+ | "router"
74
+ | "carry_over"
75
+ | "tier1"
76
+ | "tier2"
77
+ | `tier3:${number}`;
60
78
  /**
61
79
  * Per-turn outcome for this slug:
62
80
  * - `in_context` — already injected on a prior turn; cached attachment
@@ -205,6 +205,7 @@ export function createCoreTables(database: DrizzleDb): void {
205
205
  conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
206
206
  message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
207
207
  delivery_status TEXT NOT NULL DEFAULT 'pending',
208
+ delivery_attempts INTEGER NOT NULL DEFAULT 0,
208
209
  created_at INTEGER NOT NULL,
209
210
  updated_at INTEGER NOT NULL,
210
211
  UNIQUE (source_channel, external_chat_id, external_message_id)
@@ -11,6 +11,7 @@ export function createExternalConversationBindingsTables(
11
11
  conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
12
12
  source_channel TEXT NOT NULL,
13
13
  external_chat_id TEXT NOT NULL,
14
+ external_chat_name TEXT,
14
15
  external_thread_id TEXT,
15
16
  external_user_id TEXT,
16
17
  display_name TEXT,
@@ -0,0 +1,15 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateConversationLastNotifiedProfile(
5
+ database: DrizzleDb,
6
+ ): void {
7
+ const raw = getSqliteFrom(database);
8
+ try {
9
+ raw.exec(
10
+ `ALTER TABLE conversations ADD COLUMN last_notified_inference_profile TEXT DEFAULT NULL`,
11
+ );
12
+ } catch {
13
+ // Column already exists — nothing to do.
14
+ }
15
+ }
@@ -0,0 +1,47 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Create the `document_comments` table for inline and document-level comments.
6
+ *
7
+ * Supports threaded replies via `parent_comment_id` and resolution tracking
8
+ * via `status`, `resolved_by`, and `resolved_at`. The FK on `surface_id`
9
+ * cascades deletes from `documents` — when a document is removed, all its
10
+ * comments are cleaned up automatically.
11
+ *
12
+ * Idempotent — re-running is a no-op once the table and indices exist.
13
+ */
14
+ export function migrateCreateDocumentComments(database: DrizzleDb): void {
15
+ const raw = getSqliteFrom(database);
16
+
17
+ raw.exec(/*sql*/ `
18
+ CREATE TABLE IF NOT EXISTS document_comments (
19
+ id TEXT PRIMARY KEY,
20
+ surface_id TEXT NOT NULL,
21
+ conversation_id TEXT NOT NULL,
22
+ author TEXT NOT NULL,
23
+ content TEXT NOT NULL,
24
+ anchor_start INTEGER,
25
+ anchor_end INTEGER,
26
+ anchor_text TEXT,
27
+ parent_comment_id TEXT,
28
+ status TEXT NOT NULL DEFAULT 'open',
29
+ resolved_by TEXT,
30
+ resolved_at INTEGER,
31
+ created_at INTEGER NOT NULL,
32
+ updated_at INTEGER NOT NULL,
33
+ FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE,
34
+ FOREIGN KEY (parent_comment_id) REFERENCES document_comments(id) ON DELETE CASCADE
35
+ )
36
+ `);
37
+
38
+ raw.exec(/*sql*/ `
39
+ CREATE INDEX IF NOT EXISTS idx_document_comments_surface
40
+ ON document_comments(surface_id)
41
+ `);
42
+
43
+ raw.exec(/*sql*/ `
44
+ CREATE INDEX IF NOT EXISTS idx_document_comments_parent
45
+ ON document_comments(parent_comment_id)
46
+ `);
47
+ }