@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
@@ -32,5 +32,7 @@ export function getVisibleProviderCatalog(
32
32
  if (visibleModels.length === entry.models.length) return entry;
33
33
  return { ...entry, models: visibleModels };
34
34
  })
35
- .filter((entry) => entry.models.length > 0);
35
+ .filter(
36
+ (entry) => entry.models.length > 0 || entry.defaultModel === "",
37
+ );
36
38
  }
@@ -7,8 +7,10 @@
7
7
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
8
8
  import { getConfig } from "../config/loader.js";
9
9
  import type { LLMCallSite } from "../config/schemas/llm.js";
10
+ import { getDb } from "../memory/db-connection.js";
10
11
  import { getLogger } from "../util/logger.js";
11
12
  import { tryResolveProviderForConnectionName } from "./connection-resolution.js";
13
+ import { listConnections } from "./inference/connections.js";
12
14
  import { initializeProviders, listProviders } from "./registry.js";
13
15
  import type {
14
16
  ContentBlock,
@@ -110,22 +112,35 @@ export async function resolveConfiguredProvider(
110
112
 
111
113
  const resolved = resolveCallSiteConfig(callSite, config.llm, opts);
112
114
  const inferenceProvider = resolved.provider;
113
- const connectionName = resolved.provider_connection;
115
+ let connectionName = resolved.provider_connection;
114
116
 
115
117
  // Connection-aware path: every dispatch goes through `provider_connection`.
116
118
  // The boot-time backfill ensures every profile has one in production.
117
- // When unset (test envs that skip backfill, freshly-installed configs
118
- // not yet backfilled, or users who manually cleared the field), we
119
- // return null so callsites with deterministic fallbacks (invite
120
- // instructions, telegram username resolution, etc.) keep working.
121
- // Hard config errors — connection lookup failure, provider mismatch —
122
- // still throw via `tryResolveProviderForConnectionName` below.
119
+ // When unset (profile set provider with "Any active" connection, test envs
120
+ // that skip backfill, freshly-installed configs not yet backfilled, or
121
+ // users who manually cleared the field), try to auto-resolve from the
122
+ // provider before falling back to null.
123
123
  if (!connectionName) {
124
- log.debug(
125
- { callSite, inferenceProvider },
126
- "resolveCallSiteConfig yielded no provider_connection — returning null so callsite can fall back",
127
- );
128
- return null;
124
+ if (inferenceProvider) {
125
+ try {
126
+ const candidates = listConnections(getDb(), {
127
+ provider: inferenceProvider,
128
+ });
129
+ const active = candidates.find((c) => c.status === "active");
130
+ if (active) {
131
+ connectionName = active.name;
132
+ }
133
+ } catch {
134
+ // DB not available — fall through to the existing null-return path.
135
+ }
136
+ }
137
+ if (!connectionName) {
138
+ log.debug(
139
+ { callSite, inferenceProvider },
140
+ "resolveCallSiteConfig yielded no provider_connection — returning null so callsite can fall back",
141
+ );
142
+ return null;
143
+ }
129
144
  }
130
145
 
131
146
  const connectionProvider = await tryResolveProviderForConnectionName(
@@ -1,4 +1,6 @@
1
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
1
2
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
3
+ import type { AssistantConfig } from "../config/schema.js";
2
4
  import { type LLMConfig } from "../config/schemas/llm.js";
3
5
  import { getProviderKeyAsync } from "../security/secure-keys.js";
4
6
  import { ProviderNotConfiguredError } from "../util/errors.js";
@@ -26,6 +28,7 @@ const log = getLogger("provider-registry");
26
28
 
27
29
  const providers = new Map<string, Provider>();
28
30
  const routingSources = new Map<string, "user-key" | "managed-proxy">();
31
+ const OPENAI_COMPATIBLE_ENDPOINTS_FLAG = "openai-compatible-endpoints";
29
32
 
30
33
  /** Per-connection provider cache, keyed by connection name. */
31
34
  const connectionProviders = new Map<string, Provider>();
@@ -69,6 +72,16 @@ export interface ProvidersConfig {
69
72
  timeouts?: { providerStreamTimeoutSec?: number };
70
73
  }
71
74
 
75
+ function isProviderFeatureFlagEnabled(
76
+ key: string,
77
+ config: ProvidersConfig,
78
+ ): boolean {
79
+ return isAssistantFeatureFlagEnabled(
80
+ key,
81
+ config as unknown as AssistantConfig,
82
+ );
83
+ }
84
+
72
85
  function resolveModel(config: ProvidersConfig, providerName: string): string {
73
86
  const resolved = resolveCallSiteConfig("mainAgent", config.llm);
74
87
  const inferenceProvider = resolved.provider;
@@ -130,6 +143,13 @@ export async function initializeProviders(
130
143
  ).provider;
131
144
 
132
145
  for (const entry of PROVIDER_CATALOG) {
146
+ if (
147
+ entry.featureFlag &&
148
+ !isProviderFeatureFlagEnabled(entry.featureFlag, config)
149
+ ) {
150
+ continue;
151
+ }
152
+
133
153
  const isKeyless = entry.setupMode === "keyless";
134
154
 
135
155
  // Credential resolution: user key first, managed proxy second. Keyless
@@ -202,10 +222,19 @@ export async function resolveProviderFromConnection(
202
222
  connection: ProviderConnection,
203
223
  config: ProvidersConfig,
204
224
  ): Promise<Provider | null> {
225
+ if (
226
+ connection.provider === "openai-compatible" &&
227
+ !isProviderFeatureFlagEnabled(OPENAI_COMPATIBLE_ENDPOINTS_FLAG, config)
228
+ ) {
229
+ return null;
230
+ }
231
+
205
232
  const cached = connectionProviders.get(connection.name);
206
233
  if (cached) return cached;
207
234
 
208
- const authResult = await resolveAuth(connection.auth, connection.provider);
235
+ const authResult = await resolveAuth(connection.auth, connection.provider, {
236
+ baseUrl: connection.baseUrl,
237
+ });
209
238
  if (!authResult.ok) {
210
239
  const err = authResult.error;
211
240
  if (err.code === "not_implemented") {
@@ -27,6 +27,15 @@ export interface FileContent {
27
27
  filename: string;
28
28
  };
29
29
  extracted_text?: string;
30
+ /**
31
+ * Internal id linking this block to a row in the attachments table.
32
+ * Set when the file block originates from a persisted user-message
33
+ * attachment so downstream consumers (DB joins, inline-chip
34
+ * positioning) can correlate the block back to its attachment id.
35
+ * Stripped by `daemon/handlers/shared.ts` before sending to the
36
+ * model.
37
+ */
38
+ _attachmentId?: string;
30
39
  }
31
40
 
32
41
  export interface ToolUseContent {
@@ -137,6 +146,22 @@ export type ProviderEvent =
137
146
  toolUseId: string;
138
147
  isError: boolean;
139
148
  content?: unknown[];
149
+ /**
150
+ * Finalized input for the server tool call (e.g. the actual query).
151
+ * Anthropic streams `server_tool_use` block input via `input_json_delta`
152
+ * events, so consumers reading the input at `server_tool_start` see `{}`.
153
+ * The provider accumulates the JSON and surfaces it here once the block
154
+ * stops, so downstream handlers can build accurate activity metadata.
155
+ */
156
+ resolvedInput?: Record<string, unknown>;
157
+ /**
158
+ * Provider-specific error code when `isError` is true (e.g. Anthropic's
159
+ * `max_uses_exceeded`, `query_too_long`). Surfaced so user-facing
160
+ * messages can be specific instead of a generic "Search failed".
161
+ */
162
+ errorCode?: string;
163
+ /** Optional human-readable error message from the provider. */
164
+ errorMessage?: string;
140
165
  };
141
166
 
142
167
  export interface SendMessageConfig {
@@ -22,8 +22,29 @@ import type { DiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
22
22
  // Stub the DB-backed override-profile read so unit tests don't need a
23
23
  // real SQLite database. The wake helper calls this on every invocation
24
24
  // to honor the conversation's pinned inference profile.
25
+ // `getConversation` is consumed by `defaultResolveTarget` — most tests
26
+ // pass explicit `deps.resolveTarget` and bypass it, but the
27
+ // trust-context threading test below drives the default resolver and
28
+ // needs the existence/archived check to pass.
25
29
  mock.module("../../memory/conversation-crud.js", () => ({
26
30
  getConversationOverrideProfile: () => undefined,
31
+ getConversation: () => ({ archivedAt: null }),
32
+ }));
33
+
34
+ const mockGetOrCreateConversationCalls: Array<{
35
+ conversationId: string;
36
+ options: unknown;
37
+ }> = [];
38
+ mock.module("../../daemon/conversation-store.js", () => ({
39
+ getOrCreateConversation: (conversationId: string, options?: unknown) => {
40
+ mockGetOrCreateConversationCalls.push({ conversationId, options });
41
+ return Promise.resolve({ __mockConversation: true });
42
+ },
43
+ }));
44
+
45
+ let mockResolverTarget: unknown = null;
46
+ mock.module("../../daemon/wake-target-adapter.js", () => ({
47
+ conversationToWakeTarget: () => mockResolverTarget,
27
48
  }));
28
49
 
29
50
  mock.module("../../config/loader.js", () => ({
@@ -248,6 +269,8 @@ function makeTarget(options: {
248
269
  beforeEach(() => {
249
270
  __resetWakeChainForTests();
250
271
  recordRequestLogCalls.length = 0;
272
+ mockGetOrCreateConversationCalls.length = 0;
273
+ mockResolverTarget = null;
251
274
  mockDiskPressureStatus = {
252
275
  enabled: false,
253
276
  state: "disabled",
@@ -1587,4 +1610,195 @@ describe("wakeAgentForOpportunity", () => {
1587
1610
  // No log row was inserted because JSON.stringify threw.
1588
1611
  expect(recordRequestLogCalls).toHaveLength(0);
1589
1612
  });
1613
+
1614
+ // Regression guard for fork-based memory retrospectives: PR #31260
1615
+ // forked a conversation and waked it with a guardian trustContext,
1616
+ // but the wake's default resolver called
1617
+ // `getOrCreateConversation(conversationId)` with no options, so the
1618
+ // store hydrated with `trustContext === undefined`. `loadFromDb`
1619
+ // fail-closes to `trustClass: "unknown"` and filters out every
1620
+ // guardian-provenance message — so the LLM saw an empty history and
1621
+ // every fork sent `messages: []`. Threading trustContext through
1622
+ // ensures `setTrustContext` + `ensureActorScopedHistory` run during
1623
+ // hydration.
1624
+ const makeDefaultResolverTarget = (conversationId: string): WakeTarget => {
1625
+ const history: Message[] = [];
1626
+ let processing = false;
1627
+ return {
1628
+ conversationId,
1629
+ agentLoop: { run: async (input) => input },
1630
+ getMessages: () => history,
1631
+ pushMessage: () => {},
1632
+ emitAgentEvent: () => {},
1633
+ isProcessing: () => processing,
1634
+ markProcessing: (on) => {
1635
+ processing = on;
1636
+ },
1637
+ persistTailMessage: async () => {},
1638
+ };
1639
+ };
1640
+
1641
+ test("default resolver threads WakeOptions.trustContext into getOrCreateConversation", async () => {
1642
+ mockResolverTarget = makeDefaultResolverTarget("conv-thread-trust");
1643
+ const trustContext = {
1644
+ sourceChannel: "vellum",
1645
+ trustClass: "guardian",
1646
+ } as const;
1647
+
1648
+ await wakeAgentForOpportunity({
1649
+ conversationId: "conv-thread-trust",
1650
+ hint: "consolidate",
1651
+ source: "memory_v2_consolidation",
1652
+ trustContext,
1653
+ });
1654
+
1655
+ expect(mockGetOrCreateConversationCalls).toEqual([
1656
+ { conversationId: "conv-thread-trust", options: { trustContext } },
1657
+ ]);
1658
+ });
1659
+
1660
+ test("default resolver passes trustContext: undefined when WakeOptions.trustContext is omitted", async () => {
1661
+ // Inbound user-turn wakes get trust via processMessage(); the wake
1662
+ // must not synthesize a trust context out of thin air.
1663
+ mockResolverTarget = makeDefaultResolverTarget("conv-no-trust-default");
1664
+
1665
+ await wakeAgentForOpportunity({
1666
+ conversationId: "conv-no-trust-default",
1667
+ hint: "x",
1668
+ source: "unit-test",
1669
+ });
1670
+
1671
+ expect(mockGetOrCreateConversationCalls).toEqual([
1672
+ {
1673
+ conversationId: "conv-no-trust-default",
1674
+ options: { trustContext: undefined },
1675
+ },
1676
+ ]);
1677
+ });
1678
+
1679
+ describe("suppressWakeSurface option", () => {
1680
+ function makeCheckpointTarget(): {
1681
+ target: WakeTarget;
1682
+ persistedTailCalls: Message[];
1683
+ wakeProducedOutputCalls: string[];
1684
+ } {
1685
+ const firstAssistant: Message = {
1686
+ role: "assistant",
1687
+ content: [
1688
+ { type: "tool_use", id: "tu-1", name: "some_tool", input: {} },
1689
+ ],
1690
+ };
1691
+ const toolResult: Message = {
1692
+ role: "user",
1693
+ content: [{ type: "tool_result", tool_use_id: "tu-1", content: "ok" }],
1694
+ };
1695
+ const persistedTailCalls: Message[] = [];
1696
+ const baseline: Message[] = [
1697
+ { role: "user", content: [{ type: "text", text: "hi" }] },
1698
+ ];
1699
+ const history: Message[] = [...baseline];
1700
+ let processing = false;
1701
+ const wakeProducedOutputCalls: string[] = [];
1702
+
1703
+ const target: WakeTarget = {
1704
+ conversationId: "conv-suppress-surface",
1705
+ agentLoop: {
1706
+ run: async (_input, _onEvent, _signal, _requestId, onCheckpoint) => {
1707
+ const runHistory: Message[] = [..._input];
1708
+ runHistory.push(firstAssistant);
1709
+ runHistory.push(toolResult);
1710
+ await onCheckpoint!({
1711
+ turnIndex: 0,
1712
+ toolCount: 1,
1713
+ hasToolUse: true,
1714
+ history: runHistory,
1715
+ });
1716
+ return runHistory;
1717
+ },
1718
+ },
1719
+ getMessages: () => history,
1720
+ pushMessage: (msg) => {
1721
+ history.push(msg);
1722
+ },
1723
+ emitAgentEvent: () => {},
1724
+ isProcessing: () => processing,
1725
+ markProcessing: (on) => {
1726
+ processing = on;
1727
+ },
1728
+ persistTailMessage: async (msg) => {
1729
+ persistedTailCalls.push(msg);
1730
+ },
1731
+ onWakeProducedOutput: (_source, _hint, surfaceId) => {
1732
+ wakeProducedOutputCalls.push(surfaceId);
1733
+ },
1734
+ };
1735
+ return { target, persistedTailCalls, wakeProducedOutputCalls };
1736
+ }
1737
+
1738
+ test(
1739
+ "default (suppressWakeSurface omitted) still injects the ui_surface " +
1740
+ "card and calls onWakeProducedOutput",
1741
+ async () => {
1742
+ const { target, persistedTailCalls, wakeProducedOutputCalls } =
1743
+ makeCheckpointTarget();
1744
+
1745
+ await wakeAgentForOpportunity(
1746
+ {
1747
+ conversationId: "conv-suppress-surface",
1748
+ hint: "do the thing",
1749
+ source: "memory_v2_consolidation",
1750
+ },
1751
+ { resolveTarget: async () => target },
1752
+ );
1753
+
1754
+ // Existing behavior: card injected, broadcast fired exactly once.
1755
+ expect(wakeProducedOutputCalls).toHaveLength(1);
1756
+ const persistedFirst = persistedTailCalls[0];
1757
+ expect(persistedFirst).toBeDefined();
1758
+ const blocks = Array.isArray(persistedFirst!.content)
1759
+ ? persistedFirst!.content
1760
+ : [];
1761
+ const uiBlock = blocks.find(
1762
+ (b: { type?: string }) => b.type === "ui_surface",
1763
+ );
1764
+ expect(uiBlock).toBeDefined();
1765
+ },
1766
+ );
1767
+
1768
+ test(
1769
+ "suppressWakeSurface: true produces output but skips the ui_surface " +
1770
+ "card injection and the onWakeProducedOutput broadcast",
1771
+ async () => {
1772
+ const { target, persistedTailCalls, wakeProducedOutputCalls } =
1773
+ makeCheckpointTarget();
1774
+
1775
+ await wakeAgentForOpportunity(
1776
+ {
1777
+ conversationId: "conv-suppress-surface",
1778
+ hint: "do the thing",
1779
+ source: "memory_v2_consolidation",
1780
+ suppressWakeSurface: true,
1781
+ },
1782
+ { resolveTarget: async () => target },
1783
+ );
1784
+
1785
+ // Tail still persisted (wake produced real output).
1786
+ const persistedFirst = persistedTailCalls[0];
1787
+ expect(persistedFirst).toBeDefined();
1788
+ // First assistant tail message should NOT have a ui_surface block
1789
+ // prepended at the front.
1790
+ const blocks = Array.isArray(persistedFirst!.content)
1791
+ ? persistedFirst!.content
1792
+ : [];
1793
+ const firstBlock = blocks[0] as { type?: string } | undefined;
1794
+ expect(firstBlock?.type).not.toBe("ui_surface");
1795
+ const uiBlock = blocks.find(
1796
+ (b: { type?: string }) => b.type === "ui_surface",
1797
+ );
1798
+ expect(uiBlock).toBeUndefined();
1799
+ // Live broadcast was suppressed.
1800
+ expect(wakeProducedOutputCalls).toHaveLength(0);
1801
+ },
1802
+ );
1803
+ });
1590
1804
  });
@@ -94,6 +94,8 @@ mock.module("../pre-first-message-gate.js", () => ({
94
94
 
95
95
  // Import after mocks are in place.
96
96
  const { runBackgroundJob } = await import("../background-job-runner.js");
97
+ const { bufferIfDeferred, resetDeferredForTest } =
98
+ await import("../../notifications/deferred-emit.js");
97
99
 
98
100
  // ── Shared fixtures ──────────────────────────────────────────────────
99
101
 
@@ -121,6 +123,7 @@ beforeEach(() => {
121
123
  processMessageCalls.length = 0;
122
124
  emitCalls.length = 0;
123
125
  addMessageCalls.length = 0;
126
+ resetDeferredForTest();
124
127
  preFirstMessageGateOpen = true;
125
128
  processMessageImpl = async () => ({ messageId: "msg-1" });
126
129
  emitImpl = async () => ({
@@ -354,4 +357,129 @@ describe("runBackgroundJob", () => {
354
357
  expect(processMessageCalls).toHaveLength(1);
355
358
  });
356
359
  });
360
+
361
+ describe("deferNotifications", () => {
362
+ function buildSkillNotificationParams(message: string) {
363
+ return {
364
+ sourceEventName: "assistant.share",
365
+ sourceChannel: "assistant_tool" as const,
366
+ sourceContextId: "skill-ctx",
367
+ contextPayload: { requestedMessage: message },
368
+ attentionHints: {
369
+ requiresAction: false,
370
+ urgency: "low" as const,
371
+ isAsyncBackground: false,
372
+ visibleInSourceNow: false,
373
+ },
374
+ };
375
+ }
376
+
377
+ test("success path commits buffered in-band notifications", async () => {
378
+ processMessageImpl = async () => {
379
+ // Stand in for the IPC route's bufferIfDeferred call when the model
380
+ // invokes `notifications send` mid-turn.
381
+ const buffered = bufferIfDeferred(
382
+ STUB_CONVERSATION_ID,
383
+ buildSkillNotificationParams("all green"),
384
+ );
385
+ expect(buffered).not.toBeNull();
386
+ expect(buffered!.dispatched).toBe(false);
387
+ return { messageId: "msg-success" };
388
+ };
389
+
390
+ const result = await runBackgroundJob(
391
+ baseOpts({ deferNotifications: true }),
392
+ );
393
+
394
+ expect(result.ok).toBe(true);
395
+ // Commit flushed the buffered notification through emitNotificationSignal.
396
+ const successEmits = emitCalls.filter(
397
+ (e) => e.sourceEventName !== "activity.failed",
398
+ );
399
+ expect(successEmits).toHaveLength(1);
400
+ expect(successEmits[0].sourceEventName).toBe("assistant.share");
401
+ });
402
+
403
+ // Regression for the PR #31216 Codex P1 finding: a heartbeat that calls
404
+ // the notifications skill and then times out must not leave a "success"
405
+ // notification standing alongside the runner's `activity.failed` emit.
406
+ test("timeout drops buffered in-band notifications; only activity.failed emits", async () => {
407
+ processMessageImpl = () => {
408
+ bufferIfDeferred(
409
+ STUB_CONVERSATION_ID,
410
+ buildSkillNotificationParams("premature success"),
411
+ );
412
+ return new Promise(() => {});
413
+ };
414
+
415
+ const result = await runBackgroundJob(
416
+ baseOpts({ deferNotifications: true, timeoutMs: 30 }),
417
+ );
418
+
419
+ expect(result.ok).toBe(false);
420
+ expect(result.errorKind).toBe("timeout");
421
+ // Only the runner's failure signal makes it out — the buffered
422
+ // "success" notification is discarded.
423
+ expect(emitCalls).toHaveLength(1);
424
+ expect(emitCalls[0].sourceEventName).toBe("activity.failed");
425
+ });
426
+
427
+ test("thrown exception also drops buffered notifications", async () => {
428
+ processMessageImpl = async () => {
429
+ bufferIfDeferred(
430
+ STUB_CONVERSATION_ID,
431
+ buildSkillNotificationParams("doomed"),
432
+ );
433
+ throw new Error("kaboom");
434
+ };
435
+
436
+ const result = await runBackgroundJob(
437
+ baseOpts({ deferNotifications: true }),
438
+ );
439
+
440
+ expect(result.ok).toBe(false);
441
+ expect(emitCalls).toHaveLength(1);
442
+ expect(emitCalls[0].sourceEventName).toBe("activity.failed");
443
+ });
444
+
445
+ // Regression: after timeout, `processMessage` keeps running and may
446
+ // emit a late skill call. The tombstone must swallow it instead of
447
+ // letting it bypass the buffer and reach the dispatch pipeline.
448
+ test("late skill call after timeout is swallowed by the tombstone", async () => {
449
+ processMessageImpl = () => new Promise(() => {});
450
+
451
+ const result = await runBackgroundJob(
452
+ baseOpts({ deferNotifications: true, timeoutMs: 20 }),
453
+ );
454
+ expect(result.ok).toBe(false);
455
+
456
+ const late = bufferIfDeferred(
457
+ STUB_CONVERSATION_ID,
458
+ buildSkillNotificationParams("post-timeout"),
459
+ );
460
+ expect(late).not.toBeNull();
461
+ expect(late!.dispatched).toBe(false);
462
+ expect(late!.reason).toMatch(/did not complete/);
463
+
464
+ // Only the runner's failure signal made it out.
465
+ expect(emitCalls).toHaveLength(1);
466
+ expect(emitCalls[0].sourceEventName).toBe("activity.failed");
467
+ });
468
+
469
+ test("without deferNotifications, bufferIfDeferred is a no-op", async () => {
470
+ processMessageImpl = async () => {
471
+ const buffered = bufferIfDeferred(
472
+ STUB_CONVERSATION_ID,
473
+ buildSkillNotificationParams("immediate"),
474
+ );
475
+ // Buffer was never armed, so the call returns null and the IPC
476
+ // handler would emit directly.
477
+ expect(buffered).toBeNull();
478
+ return { messageId: "msg-success" };
479
+ };
480
+
481
+ const result = await runBackgroundJob(baseOpts());
482
+ expect(result.ok).toBe(true);
483
+ });
484
+ });
357
485
  });