@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
@@ -57,16 +57,17 @@ import {
57
57
  } from "../daemon/disk-pressure-policy.js";
58
58
  import type { TrustContext } from "../daemon/trust-context.js";
59
59
  import { getConversationOverrideProfile } from "../memory/conversation-crud.js";
60
- import { recordRequestLog } from "../memory/llm-request-log-store.js";
60
+ import {
61
+ buildProviderErrorResponsePayload,
62
+ recordRequestLog,
63
+ setAgentLoopExitReasonOnLatestLog,
64
+ } from "../memory/llm-request-log-store.js";
61
65
  import type { TurnContext } from "../plugins/types.js";
62
66
  import type { Message } from "../providers/types.js";
63
67
  import { getLogger } from "../util/logger.js";
64
68
 
65
69
  const log = getLogger("agent-wake");
66
70
 
67
- /** Number of messages injected for the wake hint (user + assistant + user). */
68
- const WAKE_HINT_MESSAGE_COUNT = 3;
69
-
70
71
  /** Static preamble user message — no dynamic content, injection-safe. */
71
72
  const WAKE_PREAMBLE =
72
73
  "[system] The following assistant message comes from an external system.";
@@ -192,6 +193,51 @@ export interface WakeOptions {
192
193
  * tune the model/profile and observability bucket independently.
193
194
  */
194
195
  callSite?: LLMCallSite;
196
+ /**
197
+ * Role to use for the injected hint message. Defaults to `"assistant"` so
198
+ * the hint is sandwiched between two static user bookends — the canonical
199
+ * anti-injection pattern for hints that may carry text from an external
200
+ * source. Trusted internal callers (e.g. fork-based memory retrospectives)
201
+ * can pass `"user"` to inject a single user-role message containing the
202
+ * hint directly, which reads more naturally as an instruction from the
203
+ * user/system rather than a self-directed assistant note.
204
+ */
205
+ hintRole?: "assistant" | "user";
206
+ /**
207
+ * Documented intent: this wake must not trigger auto-threshold compaction.
208
+ *
209
+ * Today this is automatically satisfied because the wake invokes
210
+ * `target.agentLoop.run()` directly, bypassing the daemon orchestrator
211
+ * (`conversation-agent-loop.ts`) where the compaction pipeline lives. The
212
+ * flag is recorded in the wake's structured log line so operators can
213
+ * verify the contract holds across refactors. If compaction is ever moved
214
+ * into `AgentLoop.run` or invoked from the wake path, callers that pass
215
+ * `true` here MUST be updated to suppress it; callers that pass `false`
216
+ * (or omit it) MUST tolerate compaction firing.
217
+ *
218
+ * Used by fork-based memory retrospectives: the wake operates on a
219
+ * freshly-forked conversation that may already be near (or past) the
220
+ * source's auto-threshold, but the goal is to operate on that exact
221
+ * context — running a compaction LLM call before the wake's own first
222
+ * call would waste tokens and defeat prompt-cache reuse.
223
+ */
224
+ suppressAutoCompaction?: boolean;
225
+ /**
226
+ * Skip injection of the hint sandwich entirely. Used when the caller has
227
+ * already persisted the instruction as a real message in the conversation
228
+ * (e.g. fork-based memory retrospectives that append a user message to the
229
+ * forked conversation before waking). When `true`, `hint` is ignored.
230
+ */
231
+ skipHintInjection?: boolean;
232
+ /**
233
+ * Skip injection of the "Conversation Woke" `ui_surface` card into the
234
+ * first assistant tail message and the corresponding live
235
+ * `onWakeProducedOutput` broadcast. Default false (existing behavior).
236
+ * Used by callers whose conversation context already makes it obvious
237
+ * that the agent's output came from a wake (e.g. fork-based memory
238
+ * retrospectives whose conversation title already says "(Retrospective)").
239
+ */
240
+ suppressWakeSurface?: boolean;
195
241
  }
196
242
 
197
243
  /**
@@ -220,13 +266,18 @@ export interface WakeResult {
220
266
  */
221
267
  export interface WakeDeps {
222
268
  /**
223
- * Resolve the wake target for a conversationId.
269
+ * Resolve the wake target for a wake invocation.
224
270
  * Returns `null` if the conversation doesn't exist, `"archived"` if it
225
271
  * exists but is archived, or a `WakeTarget` to proceed with the wake.
272
+ *
273
+ * Receives the full {@link WakeOptions} so the default resolver can
274
+ * thread `trustContext` into `getOrCreateConversation`. Without that
275
+ * threading, the conversation hydrates with `trustContext === undefined`
276
+ * and `loadFromDb` fail-closes to `trustClass: "unknown"`, which filters
277
+ * out every guardian-provenance message — fatal for fork-based memory
278
+ * retrospectives.
226
279
  */
227
- resolveTarget: (
228
- conversationId: string,
229
- ) => Promise<WakeTarget | null | "archived">;
280
+ resolveTarget: (opts: WakeOptions) => Promise<WakeTarget | null | "archived">;
230
281
  /** Timestamp source (for deterministic tests). */
231
282
  now?: () => number;
232
283
  }
@@ -238,8 +289,9 @@ export interface WakeDeps {
238
289
  // `getOrCreateConversation`, and `conversationToWakeTarget`.
239
290
 
240
291
  async function defaultResolveTarget(
241
- conversationId: string,
292
+ opts: WakeOptions,
242
293
  ): Promise<WakeTarget | null | "archived"> {
294
+ const { conversationId } = opts;
243
295
  // Lazy-import daemon modules to avoid pulling heavyweight transitive
244
296
  // deps (conversation store → config/loader → provider catalogs) at
245
297
  // module-evaluation time. Callers that only import agent-wake for
@@ -260,7 +312,15 @@ async function defaultResolveTarget(
260
312
  );
261
313
  return "archived";
262
314
  }
263
- const conversation = await getOrCreateConversation(conversationId);
315
+ // Thread trustContext through to getOrCreateConversation so the
316
+ // hydration path applies setTrustContext + ensureActorScopedHistory
317
+ // (conversation-store.ts:281-289) BEFORE the agent loop's per-turn
318
+ // snapshot reads. Without this, fork-based memory retrospectives see
319
+ // an empty history because loadFromDb ran with trustClass="unknown"
320
+ // and filtered out every guardian-provenance message.
321
+ const conversation = await getOrCreateConversation(conversationId, {
322
+ trustContext: opts.trustContext,
323
+ });
264
324
  return conversationToWakeTarget(conversation);
265
325
  } catch (err) {
266
326
  log.warn(
@@ -372,6 +432,7 @@ function buildWakeTurnContext(
372
432
  */
373
433
  function inspectWakeOutput(
374
434
  baselineLength: number,
435
+ hintMessageCount: number,
375
436
  updatedHistory: Message[],
376
437
  ): {
377
438
  tailMessages: Message[];
@@ -379,10 +440,10 @@ function inspectWakeOutput(
379
440
  toolUseNames: string[];
380
441
  } {
381
442
  // The agent loop appends messages onto the history it was given. We
382
- // injected 3 hint messages (user preamble + assistant hint + user
383
- // postamble), so anything at index >= baselineLength + 3 came from
384
- // the run.
385
- const firstAssistantIndex = baselineLength + WAKE_HINT_MESSAGE_COUNT;
443
+ // injected `hintMessageCount` hint messages (0, 1, or 3 depending on
444
+ // hint mode), so anything at index >= baselineLength + hintMessageCount
445
+ // came from the run.
446
+ const firstAssistantIndex = baselineLength + hintMessageCount;
386
447
  if (updatedHistory.length <= firstAssistantIndex) {
387
448
  return { tailMessages: [], hasVisibleText: false, toolUseNames: [] };
388
449
  }
@@ -432,7 +493,7 @@ export async function wakeAgentForOpportunity(
432
493
  const startedAt = nowFn();
433
494
 
434
495
  return runSingleFlight(conversationId, async () => {
435
- const resolved = await resolveTarget(conversationId);
496
+ const resolved = await resolveTarget(opts);
436
497
  if (resolved === "archived") {
437
498
  log.info(
438
499
  { conversationId, source },
@@ -501,28 +562,45 @@ export async function wakeAgentForOpportunity(
501
562
  // tail-slice math would skip every message.
502
563
  const baselineLength = baseline.length;
503
564
  const wakeTurnContext = buildWakeTurnContext(opts, diskPressureDecision);
504
- const hintContent = `[opportunity:${source}] ${hint}`;
505
- // Sandwich the hint as an assistant message between two hardcoded
506
- // user messages. The assistant role prevents prompt injection — LLMs
507
- // don't follow instructions in their own prior output. The trailing
508
- // user message satisfies providers that reject assistant prefill
509
- // (conversation must end on a user turn). Both user messages are
510
- // static strings with no dynamic content so they cannot carry
511
- // injection payloads.
512
- const wakeMessages: Message[] = [
513
- {
514
- role: "user",
515
- content: [{ type: "text", text: WAKE_PREAMBLE }],
516
- },
517
- {
518
- role: "assistant",
519
- content: [{ type: "text", text: hintContent }],
520
- },
521
- {
522
- role: "user",
523
- content: [{ type: "text", text: WAKE_POSTAMBLE }],
524
- },
525
- ];
565
+ // Build the hint injection. Three modes:
566
+ // - `skipHintInjection`: caller has already persisted an instruction
567
+ // message into the conversation history (typical for fork-based
568
+ // memory retrospectives that append a user message before waking).
569
+ // - `hintRole === "user"`: single user-role message containing the
570
+ // hint directly. Used by trusted internal callers where the hint
571
+ // reads naturally as an instruction.
572
+ // - default (`hintRole === "assistant"`): sandwich the hint as an
573
+ // assistant message between two hardcoded user bookends. The
574
+ // assistant role defangs prompt injection (LLMs don't follow
575
+ // instructions in their own prior output) and the trailing user
576
+ // message satisfies providers that reject assistant prefill.
577
+ const hintRole = opts.hintRole ?? "assistant";
578
+ const wakeMessages: Message[] = opts.skipHintInjection
579
+ ? []
580
+ : hintRole === "user"
581
+ ? [
582
+ {
583
+ role: "user",
584
+ content: [{ type: "text", text: hint }],
585
+ },
586
+ ]
587
+ : [
588
+ {
589
+ role: "user",
590
+ content: [{ type: "text", text: WAKE_PREAMBLE }],
591
+ },
592
+ {
593
+ role: "assistant",
594
+ content: [
595
+ { type: "text", text: `[opportunity:${source}] ${hint}` },
596
+ ],
597
+ },
598
+ {
599
+ role: "user",
600
+ content: [{ type: "text", text: WAKE_POSTAMBLE }],
601
+ },
602
+ ];
603
+ const wakeHintMessageCount = wakeMessages.length;
526
604
  const runInput: Message[] = [...baseline, ...wakeMessages];
527
605
 
528
606
  // Event handling runs in two modes. While `mode === "buffering"`,
@@ -548,6 +626,12 @@ export async function wakeAgentForOpportunity(
548
626
  provider?: string;
549
627
  };
550
628
  const pendingLogs: PendingLog[] = [];
629
+ // Exit reason deferred alongside pendingLogs. Same drop-on-silent-
630
+ // wake guarantee: if the wake never goes live, this stays null and
631
+ // no DB row is touched. Applied after pendingLogs flush in goLive
632
+ // so the latest-row lookup in `setAgentLoopExitReasonOnLatestLog`
633
+ // can see the freshly-persisted final usage row.
634
+ let pendingExitReason: string | null = null;
551
635
  const persistLog = (record: PendingLog): void => {
552
636
  try {
553
637
  recordRequestLog(
@@ -564,6 +648,16 @@ export async function wakeAgentForOpportunity(
564
648
  );
565
649
  }
566
650
  };
651
+ const persistExitReason = (reason: string): void => {
652
+ try {
653
+ setAgentLoopExitReasonOnLatestLog(conversationId, reason);
654
+ } catch (err) {
655
+ log.warn(
656
+ { err, conversationId, source, reason },
657
+ "agent-wake: failed to persist agent_loop_exit_reason (non-fatal)",
658
+ );
659
+ }
660
+ };
567
661
  const safeEmit = (event: AgentEvent): void => {
568
662
  try {
569
663
  target.emitAgentEvent(event);
@@ -590,6 +684,38 @@ export async function wakeAgentForOpportunity(
590
684
  persistLog(record);
591
685
  }
592
686
  }
687
+ // Mirror the same recording side-effect for provider-rejected calls.
688
+ // `handleProviderError` in the daemon dispatcher persists these on the
689
+ // normal turn path; the wake path owns its own onEvent and bypasses
690
+ // that dispatcher entirely, so we replicate here. Buffering rules
691
+ // match the success path: if the wake never goes live (silent no-op),
692
+ // the rows are dropped so a stale `messageId IS NULL` row doesn't get
693
+ // mis-backfilled onto an unrelated future assistant message.
694
+ if (event.type === "provider_error") {
695
+ const record: PendingLog = {
696
+ rawRequest: event.rawRequest,
697
+ rawResponse: buildProviderErrorResponsePayload(event.error),
698
+ provider: event.actualProvider,
699
+ };
700
+ if (mode === "buffering") {
701
+ pendingLogs.push(record);
702
+ } else {
703
+ persistLog(record);
704
+ }
705
+ }
706
+ // Replicates the setAgentLoopExitReasonOnLatestLog side-effect that
707
+ // `dispatchAgentEvent` does for the normal path. In live mode the
708
+ // final usage event of the run has already landed its row, so the
709
+ // latest-row lookup hits the right target. In buffering mode the
710
+ // reason is stashed and applied in `goLive` after pendingLogs are
711
+ // persisted, preserving the same ordering guarantee.
712
+ if (event.type === "agent_loop_exit") {
713
+ if (mode === "buffering") {
714
+ pendingExitReason = event.reason;
715
+ } else {
716
+ persistExitReason(event.reason);
717
+ }
718
+ }
593
719
  if (mode === "buffering") {
594
720
  buffered.push(event);
595
721
  return;
@@ -611,26 +737,28 @@ export async function wakeAgentForOpportunity(
611
737
  const goLive = (currentHistory: Message[]): void => {
612
738
  if (mode === "live") return;
613
739
  if (!surfaceInjected) {
614
- const tailStart = baselineLength + WAKE_HINT_MESSAGE_COUNT;
615
- const tail = currentHistory.slice(tailStart);
616
- const firstAssistant = tail.find((m) => m.role === "assistant");
617
- if (firstAssistant && Array.isArray(firstAssistant.content)) {
618
- firstAssistant.content.unshift({
619
- type: "ui_surface",
620
- surfaceId: wakeSurfaceId,
621
- surfaceType: "card",
622
- title: "Conversation Woke",
623
- data: {
740
+ if (!opts.suppressWakeSurface) {
741
+ const tailStart = baselineLength + wakeHintMessageCount;
742
+ const tail = currentHistory.slice(tailStart);
743
+ const firstAssistant = tail.find((m) => m.role === "assistant");
744
+ if (firstAssistant && Array.isArray(firstAssistant.content)) {
745
+ firstAssistant.content.unshift({
746
+ type: "ui_surface",
747
+ surfaceId: wakeSurfaceId,
748
+ surfaceType: "card",
624
749
  title: "Conversation Woke",
625
- body: hint,
626
- metadata: [{ label: "Source", value: source }],
627
- },
628
- display: "inline",
629
- } as never);
750
+ data: {
751
+ title: "Conversation Woke",
752
+ body: hint,
753
+ metadata: [{ label: "Source", value: source }],
754
+ },
755
+ display: "inline",
756
+ } as never);
757
+ }
630
758
  }
631
759
  surfaceInjected = true;
632
760
  }
633
- if (target.onWakeProducedOutput) {
761
+ if (!opts.suppressWakeSurface && target.onWakeProducedOutput) {
634
762
  try {
635
763
  target.onWakeProducedOutput(source, hint, wakeSurfaceId);
636
764
  } catch (err) {
@@ -648,6 +776,14 @@ export async function wakeAgentForOpportunity(
648
776
  persistLog(record);
649
777
  }
650
778
  pendingLogs.length = 0;
779
+ // Apply the deferred exit reason after pendingLogs are persisted —
780
+ // the latest-row lookup in `setAgentLoopExitReasonOnLatestLog`
781
+ // needs the final usage row to already exist. Cleared after use so
782
+ // an extremely unlikely double-goLive can't double-stamp.
783
+ if (pendingExitReason !== null) {
784
+ persistExitReason(pendingExitReason);
785
+ pendingExitReason = null;
786
+ }
651
787
  mode = "live";
652
788
  };
653
789
 
@@ -661,8 +797,7 @@ export async function wakeAgentForOpportunity(
661
797
  const flushPendingTail = async (
662
798
  currentHistory: Message[],
663
799
  ): Promise<void> => {
664
- const start =
665
- baselineLength + WAKE_HINT_MESSAGE_COUNT + persistedTailIndex;
800
+ const start = baselineLength + wakeHintMessageCount + persistedTailIndex;
666
801
  if (start >= currentHistory.length) return;
667
802
  const newMessages = currentHistory.slice(start);
668
803
  for (const msg of newMessages) {
@@ -765,7 +900,11 @@ export async function wakeAgentForOpportunity(
765
900
  tailMessages,
766
901
  hasVisibleText,
767
902
  toolUseNames: names,
768
- } = inspectWakeOutput(baselineLength, updatedHistory);
903
+ } = inspectWakeOutput(
904
+ baselineLength,
905
+ wakeHintMessageCount,
906
+ updatedHistory,
907
+ );
769
908
  toolUseNames = names;
770
909
  producedToolCalls = names.length > 0;
771
910
  const producedOutput = producedToolCalls || hasVisibleText;
@@ -844,9 +983,19 @@ export async function wakeAgentForOpportunity(
844
983
  }
845
984
 
846
985
  const durationMs = nowFn() - startedAt;
986
+ const suppressAutoCompaction = opts.suppressAutoCompaction === true;
987
+ const suppressWakeSurface = opts.suppressWakeSurface === true;
847
988
  if (runError) {
848
989
  log.error(
849
- { conversationId, source, durationMs, err: runError },
990
+ {
991
+ conversationId,
992
+ source,
993
+ durationMs,
994
+ suppressAutoCompaction,
995
+ suppressWakeSurface,
996
+ hintRole,
997
+ err: runError,
998
+ },
850
999
  "agent-wake: agent loop threw; treating as no-op",
851
1000
  );
852
1001
  } else if (tailMessageCount === 0) {
@@ -855,6 +1004,9 @@ export async function wakeAgentForOpportunity(
855
1004
  source,
856
1005
  conversationId,
857
1006
  durationMs,
1007
+ suppressAutoCompaction,
1008
+ suppressWakeSurface,
1009
+ hintRole,
858
1010
  producedToolCalls: false,
859
1011
  toolNamesCalled: [],
860
1012
  },
@@ -866,6 +1018,9 @@ export async function wakeAgentForOpportunity(
866
1018
  source,
867
1019
  conversationId,
868
1020
  durationMs,
1021
+ suppressAutoCompaction,
1022
+ suppressWakeSurface,
1023
+ hintRole,
869
1024
  producedToolCalls,
870
1025
  toolNamesCalled: toolUseNames,
871
1026
  tailMessageCount,
@@ -153,6 +153,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
153
153
  { endpoint: "conversations/undo", scopes: ["chat.write"] },
154
154
  { endpoint: "conversations/regenerate", scopes: ["chat.write"] },
155
155
  { endpoint: "conversations/attention", scopes: ["chat.read"] },
156
+ { endpoint: "conversations/slack-channel/resolve", scopes: ["chat.read"] },
156
157
  { endpoint: "conversations/seen", scopes: ["chat.write"] },
157
158
  { endpoint: "conversations/unread", scopes: ["chat.write"] },
158
159
  { endpoint: "conversations/import", scopes: ["chat.write"] },
@@ -243,6 +244,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
243
244
  endpoint: "integrations/slack/channel/config:DELETE",
244
245
  scopes: ["settings.write"],
245
246
  },
247
+ { endpoint: "integrations/a2a/invite", scopes: ["settings.write"] },
246
248
  { endpoint: "channel-verification-sessions", scopes: ["settings.write"] },
247
249
  {
248
250
  endpoint: "channel-verification-sessions:DELETE",
@@ -450,9 +452,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
450
452
  { endpoint: "bookmarks:POST", scopes: ["chat.write"] },
451
453
  { endpoint: "bookmarks/by-message:DELETE", scopes: ["chat.write"] },
452
454
 
453
- // Interfaces
454
- { endpoint: "interfaces", scopes: ["settings.read"] },
455
-
456
455
  // Skills
457
456
  { endpoint: "skills:GET", scopes: ["settings.read"] },
458
457
  { endpoint: "skills:POST", scopes: ["settings.write"] },
@@ -470,6 +469,8 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
470
469
  { endpoint: "memory/v2/list-concept-pages:POST", scopes: ["settings.read"] },
471
470
  { endpoint: "memory/v2/reembed-skills:POST", scopes: ["settings.write"] },
472
471
  { endpoint: "memory/v2/concept-frequency:POST", scopes: ["settings.read"] },
472
+ { endpoint: "memory/v2/ema-scores:POST", scopes: ["settings.read"] },
473
+ { endpoint: "memory/v2/simulate-router:POST", scopes: ["settings.read"] },
473
474
 
474
475
  // Trust rule listing
475
476
  { endpoint: "trust-rules/manage:GET", scopes: ["settings.read"] },
@@ -686,6 +687,18 @@ for (const endpoint of INTERNAL_ENDPOINTS) {
686
687
  });
687
688
  }
688
689
 
690
+ // A2A invite completion: gateway-only (platform-orchestrated)
691
+ registerPolicy("integrations/a2a/invite/complete", {
692
+ requiredScopes: ["internal.write"],
693
+ allowedPrincipalTypes: ["svc_gateway"],
694
+ });
695
+
696
+ // A2A invite redemption: gateway-only (platform-orchestrated)
697
+ registerPolicy("integrations/a2a/invite/redeem", {
698
+ requiredScopes: ["internal.write"],
699
+ allowedPrincipalTypes: ["svc_gateway"],
700
+ });
701
+
689
702
  // Admin control-plane endpoints: gateway-only
690
703
  registerPolicy("admin/upgrade-broadcast", {
691
704
  requiredScopes: ["internal.write"],
@@ -979,6 +992,10 @@ registerPolicy("conversations/cli/export", {
979
992
  requiredScopes: ["settings.read"],
980
993
  allowedPrincipalTypes: ["local"],
981
994
  });
995
+ registerPolicy("conversations/cli/slack/detach", {
996
+ requiredScopes: ["settings.write"],
997
+ allowedPrincipalTypes: ["local"],
998
+ });
982
999
  // `conversations/cli/clear` wipes every conversation + message + vector
983
1000
  // collection. Elevated to settings.write and locked to local callers,
984
1001
  // mirroring the `conversations/clear-all` and `conversations/wipe` gates.
@@ -24,6 +24,11 @@ import type { TrustContext } from "../daemon/trust-context.js";
24
24
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
25
25
  import { addMessage } from "../memory/conversation-crud.js";
26
26
  import type { TitleOrigin } from "../memory/conversation-title-service.js";
27
+ import {
28
+ commitDeferredConversation,
29
+ discardDeferredConversation,
30
+ registerDeferredConversation,
31
+ } from "../notifications/deferred-emit.js";
27
32
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
28
33
  import type { AttentionHints } from "../notifications/signal.js";
29
34
  import { getLogger } from "../util/logger.js";
@@ -121,6 +126,11 @@ export interface RunBackgroundJobOptions {
121
126
  * the `assistant` role and cannot override the action prompt.
122
127
  */
123
128
  assistantSandwich?: { preamble: string; content: string; postamble: string };
129
+ /**
130
+ * Buffer in-band `notifications send` calls and only flush them after the
131
+ * run completes successfully. See `notifications/deferred-emit.ts`.
132
+ */
133
+ deferNotifications?: boolean;
124
134
  }
125
135
 
126
136
  export interface RunBackgroundJobResult {
@@ -205,6 +215,10 @@ export async function runBackgroundJob(
205
215
  ...(opts.scheduleJobId ? { scheduleJobId: opts.scheduleJobId } : {}),
206
216
  });
207
217
 
218
+ if (opts.deferNotifications) {
219
+ registerDeferredConversation(conversation.id);
220
+ }
221
+
208
222
  // Fire the sidebar-creation callback synchronously after bootstrap so
209
223
  // connected clients (macOS sidebar, etc.) see the conversation appear
210
224
  // immediately rather than after `processMessage` returns. Wrapped so a
@@ -273,6 +287,14 @@ export async function runBackgroundJob(
273
287
  });
274
288
 
275
289
  await Promise.race([work, timeout]);
290
+ // Symmetric with the `work.catch` above: once `work` has won the race,
291
+ // the orphan timeout promise can still reject during the await below
292
+ // (commitDeferredConversation). Swallow so it doesn't surface as an
293
+ // unhandled rejection that Bun can use to terminate the process.
294
+ timeout.catch(() => {});
295
+ if (opts.deferNotifications) {
296
+ await commitDeferredConversation(conversation.id);
297
+ }
276
298
  return { conversationId: conversation.id, ok: true };
277
299
  } catch (err) {
278
300
  const errorKind = classifyError(err);
@@ -281,6 +303,10 @@ export async function runBackgroundJob(
281
303
  // so the structured failure result still flows to the caller.
282
304
  const conversationId = conversation?.id ?? "";
283
305
 
306
+ if (opts.deferNotifications && conversationId) {
307
+ discardDeferredConversation(conversationId);
308
+ }
309
+
284
310
  log.error(
285
311
  {
286
312
  err: error.message,