@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -34,3 +34,29 @@ export function makeMockLogger(): unknown {
34
34
  get: (_target, prop) => (prop === "child" ? makeMockLogger : () => {}),
35
35
  });
36
36
  }
37
+
38
+ /**
39
+ * Returns a complete mock module object for `util/logger.js`. Use this when
40
+ * the consumer of `mock.module("../util/logger.js", ...)` needs full export
41
+ * coverage — i.e. when the transitive import graph reaches `getCliLogger`,
42
+ * `initLogger`, or other helpers in addition to `getLogger`.
43
+ *
44
+ * mock.module("../util/logger.js", () => createMockLoggerModule());
45
+ *
46
+ * Pass `overrides` to swap any specific export for a custom stub (e.g. an
47
+ * observable spy). Anything not overridden falls back to the recursive
48
+ * proxy logger / no-op defaults.
49
+ */
50
+ export function createMockLoggerModule(
51
+ overrides: Record<string, unknown> = {},
52
+ ): Record<string, unknown> {
53
+ return {
54
+ getLogger: () => makeMockLogger(),
55
+ getCliLogger: () => makeMockLogger(),
56
+ initLogger: () => {},
57
+ truncateForLog: (value: string) => value,
58
+ pruneOldLogFiles: () => 0,
59
+ LOG_FILE_PATTERN: /^assistant-(\d{4}-\d{2}-\d{2})\.log$/,
60
+ ...overrides,
61
+ };
62
+ }
@@ -76,6 +76,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
76
76
  getActorPrincipalIdForClient: (clientId: string) =>
77
77
  clientActorPrincipals.get(clientId),
78
78
  },
79
+ broadcastMessage: async () => {},
79
80
  }));
80
81
 
81
82
  // ── Real imports (after mocks) ───────────────────────────────────────
@@ -88,6 +88,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
88
88
  getActorPrincipalIdForClient: (clientId: string) =>
89
89
  actorPrincipalByClient.get(clientId),
90
90
  },
91
+ broadcastMessage: async () => {},
91
92
  }));
92
93
 
93
94
  // ── Real imports (after mocks) ──────────────────────────────────────────────
@@ -73,6 +73,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
73
73
  getActorPrincipalIdForClient: (clientId: string) =>
74
74
  clientActors.get(clientId),
75
75
  },
76
+ broadcastMessage: async () => {},
76
77
  }));
77
78
 
78
79
  // ── Real imports (after mocks) ──────────────────────────────────────────────
@@ -863,9 +863,10 @@ describe("host_bash — proxy delegation", () => {
863
863
 
864
864
  test("propagates VELLUM_UNTRUSTED_SHELL env to proxy under CES lockdown", async () => {
865
865
  // Enable CES shell lockdown via the override cache
866
- const { _setOverridesForTesting } =
867
- await import("../config/assistant-feature-flags.js");
868
- _setOverridesForTesting({
866
+ const { setOverridesForTesting } = await import(
867
+ "./feature-flag-test-helpers.js"
868
+ );
869
+ setOverridesForTesting({
869
870
  "ces-shell-lockdown": true,
870
871
  });
871
872
 
@@ -899,7 +900,7 @@ describe("host_bash — proxy delegation", () => {
899
900
  __CONVERSATION_ID: "test-conversation",
900
901
  });
901
902
  } finally {
902
- _setOverridesForTesting({});
903
+ setOverridesForTesting({});
903
904
  restoreEnv(envSnapshot);
904
905
  }
905
906
  });
@@ -85,6 +85,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
85
85
  getActorPrincipalIdForClient: (clientId: string) =>
86
86
  clientActors.get(clientId),
87
87
  },
88
+ broadcastMessage: async () => {},
88
89
  }));
89
90
 
90
91
  // ── Real imports (after mocks) ──────────────────────────────────────────────
@@ -35,10 +35,11 @@ import {
35
35
  createConversation,
36
36
  updateConversationTitle,
37
37
  } from "../memory/conversation-crud.js";
38
- import { getDb, resetDb } from "../memory/db-connection.js";
38
+ import { getDb } from "../memory/db-connection.js";
39
39
  import { initializeDb } from "../memory/db-init.js";
40
40
  import { rawRun } from "../memory/raw-query.js";
41
41
  import { RuntimeHttpServer } from "../runtime/http-server.js";
42
+ import { resetDbForTesting } from "./db-test-helpers.js";
42
43
 
43
44
  initializeDb();
44
45
 
@@ -65,7 +66,7 @@ describe("conversation lineage in HTTP reads", () => {
65
66
 
66
67
  afterAll(async () => {
67
68
  await server?.stop();
68
- resetDb();
69
+ resetDbForTesting();
69
70
  });
70
71
 
71
72
  test("GET /v1/conversations returns forkParent for surviving parents", async () => {
@@ -100,6 +100,7 @@ mock.module("../memory/conversation-crud.js", () => ({
100
100
  content: string,
101
101
  metadata?: Record<string, unknown>,
102
102
  ) => addMessageMock(conversationId, role, content, metadata),
103
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
103
104
  }));
104
105
 
105
106
  mock.module("../runtime/local-actor-identity.js", () => ({
@@ -218,7 +219,10 @@ function makeConversation(overrides: Record<string, unknown> = {}) {
218
219
  hasAnyPendingConfirmation: () => false,
219
220
  denyAllPendingConfirmations: () => {},
220
221
  enqueueMessage: () => ({ queued: true, requestId: "queued-id" }),
221
- persistUserMessage: mock(async () => "persisted-user-id"),
222
+ persistUserMessage: mock(async () => ({
223
+ id: "persisted-user-id",
224
+ deduplicated: false,
225
+ })),
222
226
  runAgentLoop: mock(async () => undefined),
223
227
  getMessages: () => [] as unknown[],
224
228
  assistantId: "self",
@@ -296,7 +300,10 @@ describe("HTTP POST /v1/messages does not intercept recording intents (by design
296
300
  // Dedicated /v1/recording/* endpoints handle recording lifecycle.
297
301
  // Text-based recording intent interception was retired with the
298
302
  // legacy handleUserMessage entry point.
299
- const persistUserMessage = mock(async () => "persisted-msg-id");
303
+ const persistUserMessage = mock(async () => ({
304
+ id: "persisted-msg-id",
305
+ deduplicated: false,
306
+ }));
300
307
  const runAgentLoop = mock(async () => undefined);
301
308
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
302
309
 
@@ -313,7 +320,10 @@ describe("HTTP POST /v1/messages does not intercept recording intents (by design
313
320
  // content, conversationKey, attachmentIds, sourceChannel, and interface.
314
321
  // This ensures a future regression that starts parsing commandIntent would
315
322
  // be caught.
316
- const persistUserMessage = mock(async () => "persisted-msg-id");
323
+ const persistUserMessage = mock(async () => ({
324
+ id: "persisted-msg-id",
325
+ deduplicated: false,
326
+ }));
317
327
  const runAgentLoop = mock(async () => undefined);
318
328
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
319
329
 
@@ -327,7 +337,10 @@ describe("HTTP POST /v1/messages does not intercept recording intents (by design
327
337
  });
328
338
 
329
339
  test("stop recording commands pass through to the agent loop", async () => {
330
- const persistUserMessage = mock(async () => "persisted-msg-id");
340
+ const persistUserMessage = mock(async () => ({
341
+ id: "persisted-msg-id",
342
+ deduplicated: false,
343
+ }));
331
344
  const runAgentLoop = mock(async () => undefined);
332
345
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
333
346
 
@@ -351,7 +364,10 @@ describe("HTTP POST /v1/messages clientTimezone transport metadata", () => {
351
364
  });
352
365
 
353
366
  test("passes canonical clientTimezone through host-proxy transport", async () => {
354
- const persistUserMessage = mock(async () => "persisted-msg-id");
367
+ const persistUserMessage = mock(async () => ({
368
+ id: "persisted-msg-id",
369
+ deduplicated: false,
370
+ }));
355
371
  const runAgentLoop = mock(async () => undefined);
356
372
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
357
373
  let capturedOptions: Record<string, unknown> | undefined;
@@ -378,7 +394,10 @@ describe("HTTP POST /v1/messages clientTimezone transport metadata", () => {
378
394
  });
379
395
 
380
396
  test("passes canonical clientTimezone through non-host-proxy transport", async () => {
381
- const persistUserMessage = mock(async () => "persisted-msg-id");
397
+ const persistUserMessage = mock(async () => ({
398
+ id: "persisted-msg-id",
399
+ deduplicated: false,
400
+ }));
382
401
  const runAgentLoop = mock(async () => undefined);
383
402
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
384
403
  let capturedOptions: Record<string, unknown> | undefined;
@@ -405,7 +424,10 @@ describe("HTTP POST /v1/messages clientTimezone transport metadata", () => {
405
424
  });
406
425
 
407
426
  test("drops invalid clientTimezone without rejecting the message", async () => {
408
- const persistUserMessage = mock(async () => "persisted-msg-id");
427
+ const persistUserMessage = mock(async () => ({
428
+ id: "persisted-msg-id",
429
+ deduplicated: false,
430
+ }));
409
431
  const runAgentLoop = mock(async () => undefined);
410
432
  const conversation = makeConversation({ persistUserMessage, runAgentLoop });
411
433
  let capturedOptions: Record<string, unknown> | undefined;
@@ -60,6 +60,8 @@ mock.module("../prompts/persona-resolver.js", () => ({
60
60
  import {
61
61
  computeIdentityContentHash,
62
62
  getCachedIntro,
63
+ parseGreetingsSection,
64
+ readWorkspaceGreetings,
63
65
  readWorkspaceIdentityIntro,
64
66
  setCachedIntro,
65
67
  } from "../runtime/routes/identity-intro-cache.js";
@@ -113,21 +115,21 @@ describe("identity intro cache", () => {
113
115
  expect(getCachedIntro()).toBeNull();
114
116
  });
115
117
 
116
- test("round-trip: set then get returns cached text", () => {
118
+ test("round-trip: set then get returns cached greetings array", () => {
117
119
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
118
120
  workspaceFiles["SOUL.md"] = "Be playful.";
119
121
  guardianPersonaContent = "The user likes coffee.";
120
122
 
121
- setCachedIntro("Hey, I'm Atlas.");
123
+ setCachedIntro(["Hey, I'm Atlas.", "What's up?"]);
122
124
  const cached = getCachedIntro();
123
125
  expect(cached).not.toBeNull();
124
- expect(cached!.text).toBe("Hey, I'm Atlas.");
126
+ expect(cached!.greetings).toEqual(["Hey, I'm Atlas.", "What's up?"]);
125
127
  });
126
128
 
127
129
  test("returns null when cache is expired (TTL exceeded)", () => {
128
130
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
129
131
 
130
- setCachedIntro("Hello!");
132
+ setCachedIntro(["Hello!"]);
131
133
 
132
134
  // Manually set the timestamp to 5 hours ago
133
135
  const fiveHoursAgo = String(Date.now() - 5 * 60 * 60 * 1000);
@@ -136,10 +138,10 @@ describe("identity intro cache", () => {
136
138
  expect(getCachedIntro()).toBeNull();
137
139
  });
138
140
 
139
- test("returns cached text when within TTL (3 hours ago)", () => {
141
+ test("returns cached greetings when within TTL (3 hours ago)", () => {
140
142
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
141
143
 
142
- setCachedIntro("Hello!");
144
+ setCachedIntro(["Hello!"]);
143
145
 
144
146
  // Set timestamp to 3 hours ago (within 4-hour TTL)
145
147
  const threeHoursAgo = String(Date.now() - 3 * 60 * 60 * 1000);
@@ -147,12 +149,12 @@ describe("identity intro cache", () => {
147
149
 
148
150
  const cached = getCachedIntro();
149
151
  expect(cached).not.toBeNull();
150
- expect(cached!.text).toBe("Hello!");
152
+ expect(cached!.greetings).toEqual(["Hello!"]);
151
153
  });
152
154
 
153
155
  test("busts cache when IDENTITY.md changes", () => {
154
156
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
155
- setCachedIntro("I'm Atlas!");
157
+ setCachedIntro(["I'm Atlas!"]);
156
158
 
157
159
  // Change IDENTITY.md
158
160
  workspaceFiles["IDENTITY.md"] = "- **Name:** Nova";
@@ -162,7 +164,7 @@ describe("identity intro cache", () => {
162
164
 
163
165
  test("busts cache when SOUL.md changes", () => {
164
166
  workspaceFiles["SOUL.md"] = "Be playful.";
165
- setCachedIntro("Hey there!");
167
+ setCachedIntro(["Hey there!"]);
166
168
 
167
169
  // Change SOUL.md
168
170
  workspaceFiles["SOUL.md"] = "Be serious and formal.";
@@ -172,7 +174,7 @@ describe("identity intro cache", () => {
172
174
 
173
175
  test("busts cache when guardian persona content changes", () => {
174
176
  guardianPersonaContent = "Likes coffee.";
175
- setCachedIntro("Good morning!");
177
+ setCachedIntro(["Good morning!"]);
176
178
 
177
179
  // Change guardian persona (e.g. user edited users/<slug>.md)
178
180
  guardianPersonaContent = "Likes tea.";
@@ -185,11 +187,10 @@ describe("identity intro cache", () => {
185
187
  workspaceFiles["SOUL.md"] = "Be chill.";
186
188
  guardianPersonaContent = "Likes sunsets.";
187
189
 
188
- setCachedIntro("Atlas here.");
190
+ setCachedIntro(["Atlas here.", "Hey friend."]);
189
191
 
190
- // Read twice with the same guardian persona — both should return the cached value
191
- expect(getCachedIntro()?.text).toBe("Atlas here.");
192
- expect(getCachedIntro()?.text).toBe("Atlas here.");
192
+ expect(getCachedIntro()?.greetings).toEqual(["Atlas here.", "Hey friend."]);
193
+ expect(getCachedIntro()?.greetings).toEqual(["Atlas here.", "Hey friend."]);
193
194
  });
194
195
 
195
196
  test("computeIdentityContentHash is deterministic", () => {
@@ -235,30 +236,140 @@ describe("identity intro cache", () => {
235
236
 
236
237
  test("handles missing workspace files gracefully", () => {
237
238
  // No files exist — should still work (empty content hashed)
238
- setCachedIntro("Hello!");
239
+ setCachedIntro(["Hello!"]);
239
240
  const cached = getCachedIntro();
240
241
  expect(cached).not.toBeNull();
241
- expect(cached!.text).toBe("Hello!");
242
+ expect(cached!.greetings).toEqual(["Hello!"]);
242
243
  });
243
244
 
244
- test("returns null when text checkpoint is missing", () => {
245
+ test("handles legacy single-string cache value", () => {
246
+ // Simulate a cache entry written by an older daemon version
247
+ const hash = computeIdentityContentHash();
248
+ checkpointStore.set("identity:intro:greetings", "Legacy greeting");
249
+ checkpointStore.set("identity:intro:content_hash", hash);
250
+ checkpointStore.set("identity:intro:cached_at", String(Date.now()));
251
+
252
+ const cached = getCachedIntro();
253
+ expect(cached).not.toBeNull();
254
+ expect(cached!.greetings).toEqual(["Legacy greeting"]);
255
+ });
256
+
257
+ test("returns null when greetings checkpoint is missing", () => {
245
258
  checkpointStore.set("identity:intro:content_hash", "abc");
246
259
  checkpointStore.set("identity:intro:cached_at", String(Date.now()));
247
- // Missing text — should return null
248
260
  expect(getCachedIntro()).toBeNull();
249
261
  });
250
262
 
251
263
  test("returns null when hash checkpoint is missing", () => {
252
- checkpointStore.set("identity:intro:text", "Hello");
264
+ checkpointStore.set("identity:intro:greetings", '["Hello"]');
253
265
  checkpointStore.set("identity:intro:cached_at", String(Date.now()));
254
- // Missing hash — should return null
255
266
  expect(getCachedIntro()).toBeNull();
256
267
  });
257
268
 
258
269
  test("returns null when timestamp checkpoint is missing", () => {
259
- checkpointStore.set("identity:intro:text", "Hello");
270
+ checkpointStore.set("identity:intro:greetings", '["Hello"]');
260
271
  checkpointStore.set("identity:intro:content_hash", "abc");
261
- // Missing timestamp — should return null
262
272
  expect(getCachedIntro()).toBeNull();
263
273
  });
264
274
  });
275
+
276
+ describe("parseGreetingsSection", () => {
277
+ test("parses bullet list from ## Greetings section", () => {
278
+ const content = [
279
+ "# Soul",
280
+ "",
281
+ "## Greetings",
282
+ "- Hey there, friend!",
283
+ "- What's on your mind?",
284
+ "- Ready to roll!",
285
+ "",
286
+ "## Other Section",
287
+ "Some other content.",
288
+ ].join("\n");
289
+
290
+ expect(parseGreetingsSection(content)).toEqual([
291
+ "Hey there, friend!",
292
+ "What's on your mind?",
293
+ "Ready to roll!",
294
+ ]);
295
+ });
296
+
297
+ test("handles asterisk bullets", () => {
298
+ const content = [
299
+ "## Greetings",
300
+ "* Hello!",
301
+ "* Hi there.",
302
+ ].join("\n");
303
+
304
+ expect(parseGreetingsSection(content)).toEqual(["Hello!", "Hi there."]);
305
+ });
306
+
307
+ test("returns null when section is missing", () => {
308
+ const content = [
309
+ "# Soul",
310
+ "## Personality",
311
+ "Be friendly.",
312
+ ].join("\n");
313
+
314
+ expect(parseGreetingsSection(content)).toBeNull();
315
+ });
316
+
317
+ test("returns null when section is empty", () => {
318
+ const content = [
319
+ "## Greetings",
320
+ "",
321
+ "## Next Section",
322
+ ].join("\n");
323
+
324
+ expect(parseGreetingsSection(content)).toBeNull();
325
+ });
326
+
327
+ test("ignores non-bullet lines in section", () => {
328
+ const content = [
329
+ "## Greetings",
330
+ "Some intro text that's not a bullet",
331
+ "- Actual greeting",
332
+ "Another non-bullet line",
333
+ "- Second greeting",
334
+ ].join("\n");
335
+
336
+ expect(parseGreetingsSection(content)).toEqual([
337
+ "Actual greeting",
338
+ "Second greeting",
339
+ ]);
340
+ });
341
+
342
+ test("stops at next heading", () => {
343
+ const content = [
344
+ "## Greetings",
345
+ "- First",
346
+ "- Second",
347
+ "### Sub-heading",
348
+ "- Should not appear",
349
+ ].join("\n");
350
+
351
+ expect(parseGreetingsSection(content)).toEqual(["First", "Second"]);
352
+ });
353
+ });
354
+
355
+ describe("readWorkspaceGreetings", () => {
356
+ test("reads greetings from SOUL.md", () => {
357
+ workspaceFiles["SOUL.md"] = [
358
+ "# Soul",
359
+ "## Greetings",
360
+ "- Hey!",
361
+ "- What's up?",
362
+ ].join("\n");
363
+
364
+ expect(readWorkspaceGreetings()).toEqual(["Hey!", "What's up?"]);
365
+ });
366
+
367
+ test("returns null when SOUL.md has no greetings section", () => {
368
+ workspaceFiles["SOUL.md"] = "# Soul\nBe friendly.";
369
+ expect(readWorkspaceGreetings()).toBeNull();
370
+ });
371
+
372
+ test("returns null when SOUL.md does not exist", () => {
373
+ expect(readWorkspaceGreetings()).toBeNull();
374
+ });
375
+ });
@@ -48,6 +48,7 @@ mock.module("../memory/conversation-crud.js", () => ({
48
48
  provenanceFromTrustContext: () => ({}),
49
49
  setConversationOriginChannelIfUnset: () => {},
50
50
  setConversationOriginInterfaceIfUnset: () => {},
51
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
51
52
  }));
52
53
 
53
54
  mock.module("../memory/conversation-disk-view.js", () => ({
@@ -140,12 +141,10 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
140
141
  assistantMessageChannel: "slack",
141
142
  });
142
143
 
143
- await persistQueuedMessageBody(
144
- ctx,
145
- "Reply inside a thread",
146
- [],
147
- "req-thread",
148
- {
144
+ await persistQueuedMessageBody(ctx, {
145
+ content: "Reply inside a thread",
146
+ requestId: "req-thread",
147
+ metadata: {
149
148
  slackInbound: {
150
149
  channelId: "C0123CHANNEL",
151
150
  channelName: "engineering",
@@ -155,8 +154,7 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
155
154
  actorExternalUserId: "U_ALICE",
156
155
  },
157
156
  },
158
- undefined,
159
- );
157
+ });
160
158
 
161
159
  const slackMeta = readPersistedSlackMeta();
162
160
  expect(slackMeta).not.toBeNull();
@@ -176,20 +174,17 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
176
174
  assistantMessageChannel: "slack",
177
175
  });
178
176
 
179
- await persistQueuedMessageBody(
180
- ctx,
181
- "Top-level channel post",
182
- [],
183
- "req-top",
184
- {
177
+ await persistQueuedMessageBody(ctx, {
178
+ content: "Top-level channel post",
179
+ requestId: "req-top",
180
+ metadata: {
185
181
  slackInbound: {
186
182
  channelId: "C0123CHANNEL",
187
183
  channelTs: "1700000010.222222",
188
184
  displayName: "Bob",
189
185
  },
190
186
  },
191
- undefined,
192
- );
187
+ });
193
188
 
194
189
  const slackMeta = readPersistedSlackMeta();
195
190
  expect(slackMeta).not.toBeNull();
@@ -205,20 +200,17 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
205
200
  assistantMessageChannel: "slack",
206
201
  });
207
202
 
208
- await persistQueuedMessageBody(
209
- ctx,
210
- "@leo can you check this?",
211
- [],
212
- "req-normalized-content",
213
- {
203
+ await persistQueuedMessageBody(ctx, {
204
+ content: "@leo can you check this?",
205
+ requestId: "req-normalized-content",
206
+ metadata: {
214
207
  slackInbound: {
215
208
  channelId: "C0123CHANNEL",
216
209
  channelTs: "1700000015.123456",
217
210
  displayName: "Alice",
218
211
  },
219
212
  },
220
- undefined,
221
- );
213
+ });
222
214
 
223
215
  expect(JSON.parse(addMessageCalls.at(-1)!.content)).toEqual([
224
216
  { type: "text", text: "@leo can you check this?" },
@@ -236,19 +228,16 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
236
228
  assistantMessageChannel: "slack",
237
229
  });
238
230
 
239
- await persistQueuedMessageBody(
240
- ctx,
241
- "Anonymous channel post",
242
- [],
243
- "req-anon",
244
- {
231
+ await persistQueuedMessageBody(ctx, {
232
+ content: "Anonymous channel post",
233
+ requestId: "req-anon",
234
+ metadata: {
245
235
  slackInbound: {
246
236
  channelId: "C0123CHANNEL",
247
237
  channelTs: "1700000020.333333",
248
238
  },
249
239
  },
250
- undefined,
251
- );
240
+ });
252
241
 
253
242
  const slackMeta = readPersistedSlackMeta();
254
243
  expect(slackMeta).not.toBeNull();
@@ -261,14 +250,10 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
261
250
  assistantMessageChannel: "telegram",
262
251
  });
263
252
 
264
- await persistQueuedMessageBody(
265
- ctx,
266
- "Telegram message",
267
- [],
268
- "req-tg",
269
- undefined,
270
- undefined,
271
- );
253
+ await persistQueuedMessageBody(ctx, {
254
+ content: "Telegram message",
255
+ requestId: "req-tg",
256
+ });
272
257
 
273
258
  const metadata = lastPersistedMetadata();
274
259
  expect("slackMeta" in metadata).toBe(false);
@@ -284,19 +269,16 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
284
269
  assistantMessageChannel: "telegram",
285
270
  });
286
271
 
287
- await persistQueuedMessageBody(
288
- ctx,
289
- "Telegram message with stray slackInbound",
290
- [],
291
- "req-tg-stray",
292
- {
272
+ await persistQueuedMessageBody(ctx, {
273
+ content: "Telegram message with stray slackInbound",
274
+ requestId: "req-tg-stray",
275
+ metadata: {
293
276
  slackInbound: {
294
277
  channelId: "C0_DOES_NOT_APPLY",
295
278
  channelTs: "1700000030.444444",
296
279
  },
297
280
  },
298
- undefined,
299
- );
281
+ });
300
282
 
301
283
  const metadata = lastPersistedMetadata();
302
284
  expect("slackMeta" in metadata).toBe(false);
@@ -312,14 +294,10 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
312
294
  assistantMessageChannel: "slack",
313
295
  });
314
296
 
315
- await persistQueuedMessageBody(
316
- ctx,
317
- "Slack wake without inbound metadata",
318
- [],
319
- "req-no-slack-inbound",
320
- undefined,
321
- undefined,
322
- );
297
+ await persistQueuedMessageBody(ctx, {
298
+ content: "Slack wake without inbound metadata",
299
+ requestId: "req-no-slack-inbound",
300
+ });
323
301
 
324
302
  const metadata = lastPersistedMetadata();
325
303
  expect("slackMeta" in metadata).toBe(false);
@@ -331,19 +309,16 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
331
309
  assistantMessageChannel: "slack",
332
310
  });
333
311
 
334
- await persistQueuedMessageBody(
335
- ctx,
336
- "Malformed inbound payload",
337
- [],
338
- "req-malformed",
339
- {
312
+ await persistQueuedMessageBody(ctx, {
313
+ content: "Malformed inbound payload",
314
+ requestId: "req-malformed",
315
+ metadata: {
340
316
  slackInbound: {
341
317
  // channelTs intentionally missing — simulates a bug upstream.
342
318
  channelId: "C0123CHANNEL",
343
319
  } as unknown as Record<string, unknown>,
344
320
  },
345
- undefined,
346
- );
321
+ });
347
322
 
348
323
  const metadata = lastPersistedMetadata();
349
324
  expect("slackMeta" in metadata).toBe(false);
@@ -355,19 +330,16 @@ describe("PR 11 — inbound Slack message metadata persistence", () => {
355
330
  assistantMessageChannel: "slack",
356
331
  });
357
332
 
358
- await persistQueuedMessageBody(
359
- ctx,
360
- "Verify carrier is stripped",
361
- [],
362
- "req-strip",
363
- {
333
+ await persistQueuedMessageBody(ctx, {
334
+ content: "Verify carrier is stripped",
335
+ requestId: "req-strip",
336
+ metadata: {
364
337
  slackInbound: {
365
338
  channelId: "C0123CHANNEL",
366
339
  channelTs: "1700000040.555555",
367
340
  },
368
341
  },
369
- undefined,
370
- );
342
+ });
371
343
 
372
344
  const metadata = lastPersistedMetadata();
373
345
  expect("slackInbound" in metadata).toBe(false);