@vellumai/assistant 0.8.2 → 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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -33,11 +33,52 @@ let priorRetroMessages: Array<{ role: string; content: string }> = [];
33
33
 
34
34
  let mockWakeResult: { invoked: boolean; reason?: string } = { invoked: true };
35
35
  let mockWakeThrows: Error | null = null;
36
- let wakeCalls: Array<{ conversationId: string; hint: string }> = [];
36
+ let wakeCalls: Array<{
37
+ conversationId: string;
38
+ hint: string;
39
+ opts: Record<string, unknown>;
40
+ }> = [];
37
41
  let bootstrappedConversationId = "bg-conv-new";
38
42
  let bootstrapCalls: Array<{ forkParentConversationId?: string }> = [];
39
43
  let deletedConversationIds: string[] = [];
40
44
 
45
+ // Fork-path mocks. Flag off by default so legacy-path tests stay untouched.
46
+ let forkFlagEnabled = false;
47
+ let forkedConversationId = "fork-conv-1";
48
+ let forkCalls: Array<{
49
+ conversationId: string;
50
+ throughMessageId?: string;
51
+ source: string;
52
+ title: string;
53
+ conversationType?: string;
54
+ groupId?: string;
55
+ }> = [];
56
+ let addMessageCalls: Array<{
57
+ conversationId: string;
58
+ role: string;
59
+ content: string;
60
+ metadata: unknown;
61
+ }> = [];
62
+
63
+ // Per-conversation overrides for getConversation. Lets fork-path tests stage
64
+ // a fork-kind prior retrospective row alongside the default legacy stub.
65
+ type ConversationStub = {
66
+ source: string;
67
+ forkParentMessageId: string | null;
68
+ title?: string;
69
+ };
70
+ let conversationOverrides: Record<string, ConversationStub> = {};
71
+
72
+ // Per-conversation overrides for getMessages so fork-path tests can return
73
+ // fork-shaped message rows (with metadata stamps + createdAt boundaries).
74
+ type StubMessage = {
75
+ role: string;
76
+ content: string;
77
+ createdAt: number;
78
+ metadata: string | null;
79
+ };
80
+ let messagesByConversationId: Record<string, StubMessage[]> = {};
81
+
41
82
  mock.module("../memory-retrospective-state.js", () => ({
42
83
  getRetrospectiveState: (_id: string) => mockState,
43
84
  upsertRetrospectiveState: (args: {
@@ -55,20 +96,97 @@ mock.module("../memory-retrospective-state.js", () => ({
55
96
  mock.module("../conversation-crud.js", () => ({
56
97
  getMessagesAfter: (_id: string, _afterId: string | null) => newMessages,
57
98
  getMessages: (id: string) => {
99
+ if (messagesByConversationId[id]) return messagesByConversationId[id];
58
100
  if (id === priorRetroId) return priorRetroMessages;
59
101
  return [];
60
102
  },
61
103
  findMostRecentRetrospectiveFor: (_id: string) =>
62
104
  priorRetroId ? { id: priorRetroId } : null,
105
+ // The fork path calls `getConversation(sourceConversationId)` to read the
106
+ // source's title for the fork title. `collectPriorRetrospectiveRemembers`
107
+ // also calls it with the prior retro id to discriminate legacy vs fork
108
+ // sources — for that id return a legacy-shaped row by default so existing
109
+ // tests exercise the unchanged extract-everything code path.
110
+ // `conversationOverrides` lets per-test setup stage fork-kind priors.
111
+ getConversation: (id: string) => {
112
+ if (conversationOverrides[id]) return conversationOverrides[id];
113
+ if (id === priorRetroId) {
114
+ return {
115
+ source: "memory-retrospective",
116
+ forkParentMessageId: null,
117
+ };
118
+ }
119
+ return {
120
+ source: "user",
121
+ forkParentMessageId: null,
122
+ title: "Source conversation",
123
+ };
124
+ },
125
+ forkConversation: (params: {
126
+ conversationId: string;
127
+ throughMessageId?: string;
128
+ source: string;
129
+ title: string;
130
+ conversationType?: string;
131
+ groupId?: string;
132
+ }) => {
133
+ forkCalls.push(params);
134
+ return { id: forkedConversationId };
135
+ },
136
+ addMessage: async (
137
+ conversationId: string,
138
+ role: string,
139
+ content: string,
140
+ metadata: unknown,
141
+ ) => {
142
+ addMessageCalls.push({ conversationId, role, content, metadata });
143
+ },
63
144
  deleteConversation: (id: string) => {
64
145
  deletedConversationIds.push(id);
65
146
  },
66
147
  }));
67
148
 
149
+ mock.module("../../config/assistant-feature-flags.js", () => ({
150
+ isAssistantFeatureFlagEnabled: (flag: string) =>
151
+ flag === "memory-retrospective-fork" && forkFlagEnabled,
152
+ }));
153
+
154
+ let transcriptFormatterCalls: Array<{
155
+ messageIds: string[];
156
+ timeZone?: string;
157
+ assistantName?: string | null;
158
+ userName?: string | null;
159
+ }> = [];
160
+
68
161
  mock.module("../../export/transcript-formatter.js", () => ({
69
162
  formatMessageSliceForTranscript: (
70
163
  messages: Array<{ id: string; createdAt: number }>,
71
- ) => messages.map((m) => `[msg ${m.id}]`).join("\n"),
164
+ options: {
165
+ timeZone?: string;
166
+ assistantName?: string | null;
167
+ userName?: string | null;
168
+ } = {},
169
+ ) => {
170
+ transcriptFormatterCalls.push({
171
+ messageIds: messages.map((m) => m.id),
172
+ timeZone: options.timeZone,
173
+ assistantName: options.assistantName,
174
+ userName: options.userName,
175
+ });
176
+ return messages.map((m) => `[msg ${m.id}]`).join("\n");
177
+ },
178
+ }));
179
+
180
+ let mockAssistantName: string | null = "Bob";
181
+ let mockUserName: string | null = "Alice";
182
+
183
+ mock.module("../../daemon/identity-helpers.js", () => ({
184
+ getAssistantName: () => mockAssistantName,
185
+ resolveUserName: (_workspaceDir: string) => mockUserName,
186
+ }));
187
+
188
+ mock.module("../../util/platform.js", () => ({
189
+ getWorkspaceDir: () => "/tmp/test-workspace",
72
190
  }));
73
191
 
74
192
  mock.module("../conversation-bootstrap.js", () => ({
@@ -85,11 +203,14 @@ mock.module("../../daemon/trust-context.js", () => ({
85
203
  }));
86
204
 
87
205
  mock.module("../../runtime/agent-wake.js", () => ({
88
- wakeAgentForOpportunity: async (opts: {
89
- conversationId: string;
90
- hint: string;
91
- }) => {
92
- wakeCalls.push({ conversationId: opts.conversationId, hint: opts.hint });
206
+ wakeAgentForOpportunity: async (
207
+ opts: { conversationId: string; hint: string } & Record<string, unknown>,
208
+ ) => {
209
+ wakeCalls.push({
210
+ conversationId: opts.conversationId,
211
+ hint: opts.hint,
212
+ opts,
213
+ });
93
214
  if (mockWakeThrows) throw mockWakeThrows;
94
215
  return mockWakeResult;
95
216
  },
@@ -102,9 +223,19 @@ mock.module("../jobs-store.js", () => ({
102
223
  import type { MemoryJob } from "../jobs-store.js";
103
224
  import { memoryRetrospectiveJob } from "../memory-retrospective-job.js";
104
225
 
105
- const stubConfig = {
106
- memory: { v2: { enabled: true } },
107
- } as unknown as Parameters<typeof memoryRetrospectiveJob>[1];
226
+ function makeConfig(
227
+ overrides: { userTimezone?: string; detectedTimezone?: string } = {},
228
+ ): Parameters<typeof memoryRetrospectiveJob>[1] {
229
+ return {
230
+ memory: { v2: { enabled: true } },
231
+ ui: {
232
+ userTimezone: overrides.userTimezone,
233
+ detectedTimezone: overrides.detectedTimezone,
234
+ },
235
+ } as unknown as Parameters<typeof memoryRetrospectiveJob>[1];
236
+ }
237
+
238
+ const stubConfig = makeConfig();
108
239
 
109
240
  function makeJob(conversationId = "src-conv-1"): MemoryJob<{
110
241
  conversationId?: string;
@@ -155,6 +286,15 @@ describe("memoryRetrospectiveJob", () => {
155
286
  bootstrappedConversationId = "bg-conv-new";
156
287
  bootstrapCalls = [];
157
288
  deletedConversationIds = [];
289
+ transcriptFormatterCalls = [];
290
+ mockAssistantName = "Bob";
291
+ mockUserName = "Alice";
292
+ forkFlagEnabled = false;
293
+ forkedConversationId = "fork-conv-1";
294
+ forkCalls = [];
295
+ addMessageCalls = [];
296
+ conversationOverrides = {};
297
+ messagesByConversationId = {};
158
298
  });
159
299
 
160
300
  test("first-run happy path: no state row, no prior retrospective, both pointer fields set on success", async () => {
@@ -274,6 +414,44 @@ describe("memoryRetrospectiveJob", () => {
274
414
  expect(hint).toContain("- a real save");
275
415
  });
276
416
 
417
+ test("transcript is formatted in the configured user timezone and the prompt discloses it", async () => {
418
+ const config = makeConfig({ userTimezone: "America/Los_Angeles" });
419
+ await memoryRetrospectiveJob(makeJob(), config);
420
+
421
+ expect(transcriptFormatterCalls).toHaveLength(1);
422
+ expect(transcriptFormatterCalls[0]!.timeZone).toBe("America/Los_Angeles");
423
+
424
+ const hint = wakeCalls[0]!.hint;
425
+ expect(hint).toContain("Timestamps are in America/Los_Angeles.");
426
+ });
427
+
428
+ test("detected timezone is used when no manual override is set", async () => {
429
+ const config = makeConfig({ detectedTimezone: "Europe/Berlin" });
430
+ await memoryRetrospectiveJob(makeJob(), config);
431
+
432
+ expect(transcriptFormatterCalls[0]!.timeZone).toBe("Europe/Berlin");
433
+
434
+ const hint = wakeCalls[0]!.hint;
435
+ expect(hint).toContain("Timestamps are in Europe/Berlin.");
436
+ });
437
+
438
+ test("resolved assistant and user display names are passed to the transcript formatter", async () => {
439
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
440
+
441
+ expect(transcriptFormatterCalls).toHaveLength(1);
442
+ expect(transcriptFormatterCalls[0]!.assistantName).toBe("Bob");
443
+ expect(transcriptFormatterCalls[0]!.userName).toBe("Alice");
444
+ });
445
+
446
+ test("formatter receives null names when identity files are missing — formatter handles fallback", async () => {
447
+ mockAssistantName = null;
448
+ mockUserName = null;
449
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
450
+
451
+ expect(transcriptFormatterCalls[0]!.assistantName).toBeNull();
452
+ expect(transcriptFormatterCalls[0]!.userName).toBeNull();
453
+ });
454
+
277
455
  test("non-remember tool_use blocks in the prior retro are ignored", async () => {
278
456
  priorRetroId = "prior-retro-conv-1";
279
457
  priorRetroMessages = [
@@ -325,4 +503,223 @@ describe("memoryRetrospectiveJob", () => {
325
503
  const hint = wakeCalls[0]!.hint;
326
504
  expect(hint).toContain("<\u200B/already_remembered>");
327
505
  });
506
+
507
+ test("fork path: persisted instruction is stamped with hidden: true so the UI list serializer drops it", async () => {
508
+ forkFlagEnabled = true;
509
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
510
+
511
+ expect(addMessageCalls).toHaveLength(1);
512
+ expect(addMessageCalls[0]!.conversationId).toBe("fork-conv-1");
513
+ expect(addMessageCalls[0]!.role).toBe("user");
514
+ expect(addMessageCalls[0]!.metadata).toEqual({
515
+ kind: "memory_retrospective_instruction",
516
+ hidden: true,
517
+ });
518
+ });
519
+
520
+ test("fork path: forked retrospective is bucketed as background under the retrospective group", async () => {
521
+ forkFlagEnabled = true;
522
+ const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
523
+
524
+ expect(outcome.kind).toBe("invoked");
525
+ expect(forkCalls).toHaveLength(1);
526
+ expect(forkCalls[0]!.conversationType).toBe("background");
527
+ expect(forkCalls[0]!.groupId).toBe("system:background");
528
+ });
529
+
530
+ test("fork path: wake opts include suppressWakeSurface so clients don't render an empty wake card on top of the '(Retrospective)' fork", async () => {
531
+ forkFlagEnabled = true;
532
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
533
+
534
+ expect(forkCalls).toHaveLength(1);
535
+ expect(wakeCalls).toHaveLength(1);
536
+ expect(wakeCalls[0]!.conversationId).toBe("fork-conv-1");
537
+ const opts = wakeCalls[0]!.opts;
538
+ expect(opts.suppressWakeSurface).toBe(true);
539
+ // Sanity: the other fork-specific opts the handler relies on are still set.
540
+ expect(opts.skipHintInjection).toBe(true);
541
+ expect(opts.suppressAutoCompaction).toBe(true);
542
+ expect(opts.hintRole).toBe("user");
543
+ });
544
+
545
+ test("fork path: fork is pinned to the computed cutoffMessageId so late-arriving messages don't sneak into this run", async () => {
546
+ // Without `throughMessageId`, the fork snapshots the latest source
547
+ // message at fork time. If a new user/assistant turn lands between the
548
+ // slice read and the fork, this run would process the late turn while
549
+ // state advances only to `cutoffMessageId`, causing the next
550
+ // retrospective to reprocess it.
551
+ forkFlagEnabled = true;
552
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
553
+
554
+ expect(forkCalls).toHaveLength(1);
555
+ expect(forkCalls[0]!.throughMessageId).toBe("m3");
556
+ });
557
+
558
+ test("fork path: prior fork-kind retrospective with nested-fork ancestry still surfaces its post-fork remembers in <already_remembered>", async () => {
559
+ // The source conversation was itself a fork. Its assistant messages
560
+ // therefore carry `forkSourceMessageId` values pointing at the
561
+ // ANCESTOR's message ids — not at the new fork's `forkParentMessageId`.
562
+ // The boundary detector must locate the boundary by scanning for the
563
+ // last metadata stamp regardless of value, not by equality against
564
+ // `forkParentMessageId` (which would miss every copied row and lose
565
+ // dedup context).
566
+ forkFlagEnabled = true;
567
+ priorRetroId = "prior-fork-retro-1";
568
+
569
+ // The fork's `forkParentMessageId` is the source conv's tip ("m-src-2"),
570
+ // but the cloned messages preserve ancestor stamps ("m-ancestor-*").
571
+ conversationOverrides[priorRetroId] = {
572
+ source: "memory-retrospective-fork",
573
+ forkParentMessageId: "m-src-2",
574
+ };
575
+ messagesByConversationId[priorRetroId] = [
576
+ // Copied prefix — note metadata stamps point at the ANCESTOR, not
577
+ // `forkParentMessageId`. The old detector would return null here.
578
+ {
579
+ role: "user",
580
+ content: JSON.stringify([{ type: "text", text: "hi" }]),
581
+ createdAt: 1000,
582
+ metadata: JSON.stringify({ forkSourceMessageId: "m-ancestor-1" }),
583
+ },
584
+ {
585
+ role: "assistant",
586
+ // An inline `remember` from the source conv (should NOT leak into
587
+ // dedup baseline — it's part of the copied prefix, not the post-fork
588
+ // retrospective tail).
589
+ content: JSON.stringify([
590
+ {
591
+ type: "tool_use",
592
+ name: "remember",
593
+ input: { content: "source-inline save — must be excluded" },
594
+ },
595
+ ]),
596
+ createdAt: 2000,
597
+ metadata: JSON.stringify({ forkSourceMessageId: "m-ancestor-2" }),
598
+ },
599
+ // Post-fork instruction (no forkSourceMessageId) + the wake's tail
600
+ // assistant turn with the retrospective's own remember call.
601
+ {
602
+ role: "user",
603
+ content: JSON.stringify([
604
+ { type: "text", text: "Retrospective instruction" },
605
+ ]),
606
+ createdAt: 3000,
607
+ metadata: JSON.stringify({
608
+ kind: "memory_retrospective_instruction",
609
+ hidden: true,
610
+ }),
611
+ },
612
+ {
613
+ role: "assistant",
614
+ content: JSON.stringify([
615
+ {
616
+ type: "tool_use",
617
+ name: "remember",
618
+ input: { content: "retrospective save — must be included" },
619
+ },
620
+ ]),
621
+ createdAt: 4000,
622
+ metadata: null,
623
+ },
624
+ ];
625
+
626
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
627
+
628
+ // The fork path persists the prompt as a user-role message, not via the
629
+ // wake's hint. Pull the rendered text block out of the persisted JSON.
630
+ expect(addMessageCalls).toHaveLength(1);
631
+ const blocks = JSON.parse(addMessageCalls[0]!.content) as Array<{
632
+ type: string;
633
+ text: string;
634
+ }>;
635
+ const instructionText = blocks[0]!.text;
636
+ expect(instructionText).toContain(
637
+ "- retrospective save — must be included",
638
+ );
639
+ expect(instructionText).not.toContain("source-inline save");
640
+ // Sanity: the "first retrospective" sentinel should not appear — we
641
+ // located dedup context.
642
+ expect(instructionText).not.toContain(
643
+ "(none — this is your first retrospective over this conversation)",
644
+ );
645
+ });
646
+
647
+ test("fork path: prior fork-kind retrospective with no copied messages degrades to empty dedup", async () => {
648
+ // Corrupted/empty fork-kind prior: no message carries
649
+ // `forkSourceMessageId`. The detector should return null and the
650
+ // handler should treat dedup as empty rather than dumping everything
651
+ // (which would leak any pre-fork content into the baseline).
652
+ forkFlagEnabled = true;
653
+ priorRetroId = "prior-fork-retro-2";
654
+
655
+ conversationOverrides[priorRetroId] = {
656
+ source: "memory-retrospective-fork",
657
+ forkParentMessageId: "m-src-2",
658
+ };
659
+ messagesByConversationId[priorRetroId] = [
660
+ {
661
+ role: "assistant",
662
+ content: JSON.stringify([
663
+ {
664
+ type: "tool_use",
665
+ name: "remember",
666
+ input: { content: "would-be-leaked save" },
667
+ },
668
+ ]),
669
+ createdAt: 1000,
670
+ metadata: null,
671
+ },
672
+ ];
673
+
674
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
675
+
676
+ expect(addMessageCalls).toHaveLength(1);
677
+ const blocks = JSON.parse(addMessageCalls[0]!.content) as Array<{
678
+ type: string;
679
+ text: string;
680
+ }>;
681
+ const instructionText = blocks[0]!.text;
682
+ expect(instructionText).not.toContain("- would-be-leaked save");
683
+ expect(instructionText).toContain(
684
+ "(none — this is your first retrospective over this conversation)",
685
+ );
686
+ });
687
+
688
+ test("fork path: prompt anchors review window at first turn_context current_time and disambiguates first-pass vs incremental", async () => {
689
+ forkFlagEnabled = true;
690
+ // Stage a user turn whose content carries a turn_context current_time
691
+ // block — the handler should anchor the prompt at that timestamp.
692
+ newMessages = [
693
+ {
694
+ id: "m1",
695
+ createdAt: Date.parse("2026-05-11T10:00:00Z"),
696
+ role: "user",
697
+ content: JSON.stringify([
698
+ {
699
+ type: "text",
700
+ text: "<turn_context>\ncurrent_time: 2026-05-11T10:00:00-07:00\n</turn_context>\n\nhi",
701
+ },
702
+ ]),
703
+ },
704
+ // Wake's response — no turn_context, not used as anchor.
705
+ {
706
+ id: "m2",
707
+ createdAt: Date.parse("2026-05-11T10:05:00Z"),
708
+ role: "assistant",
709
+ content: JSON.stringify([{ type: "text", text: "hello" }]),
710
+ },
711
+ ] as Array<{ id: string; createdAt: number } & Record<string, unknown>>;
712
+
713
+ // Incremental run — `lastProcessedMessageId` already set.
714
+ mockState = {
715
+ conversationId: "src-conv-1",
716
+ lastProcessedMessageId: "prev-msg",
717
+ lastRunAt: Date.now() - 60 * 60 * 1000,
718
+ };
719
+ await memoryRetrospectiveJob(makeJob(), stubConfig);
720
+
721
+ expect(addMessageCalls).toHaveLength(1);
722
+ expect(forkCalls).toHaveLength(1);
723
+ expect(forkCalls[0]!.throughMessageId).toBe("m2");
724
+ });
328
725
  });