@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -32,51 +32,6 @@ export function nonEmpty(value: string | undefined): string | undefined {
32
32
  return trimmed.length > 0 ? trimmed : undefined;
33
33
  }
34
34
 
35
- export function looksLikeIntermediaryInstruction(text: string): boolean {
36
- const normalized = text.replace(/\s+/g, " ").trim();
37
- const intermediaryAction =
38
- "(?:tell|telling|ask|asking|remind|reminding|nudge|nudging|prompt|prompting|notify|notifying|encourage|encouraging|prime|priming|brief|briefing|coach|coaching)";
39
- const target = "(?:the\\s+)?(?:guardian|recipient|user)";
40
- return (
41
- /\b(?:assistant|agent|system|model|watcher)\s+(?:should|needs?\s+to|must|can|could)\b/i.test(
42
- normalized,
43
- ) ||
44
- new RegExp(
45
- `\\b(?:consider|try|please)\\s+${intermediaryAction}\\s+${target}\\b`,
46
- "i",
47
- ).test(normalized) ||
48
- new RegExp(
49
- `\\b${intermediaryAction}\\s+${target}\\s+(?:to|that|about|with)\\b`,
50
- "i",
51
- ).test(normalized) ||
52
- new RegExp(
53
- `\\b${target}\\s+(?:should|needs?\\s+to|must|might\\s+want\\s+to)\\b`,
54
- "i",
55
- ).test(normalized) ||
56
- new RegExp(`\\b(?:for|to)\\s+${target}\\s+to\\b`, "i").test(normalized)
57
- );
58
- }
59
-
60
- function buildHeartbeatAlertCopy(
61
- payload: Record<string, unknown>,
62
- ): RenderedChannelCopy {
63
- const summary = str(
64
- payload.summary,
65
- str(payload.body, "Your assistant found something worth your attention."),
66
- ).trim();
67
- const safePopupBody = looksLikeIntermediaryInstruction(summary)
68
- ? "I found something worth your attention in a heartbeat check. Open the conversation for details."
69
- : summary;
70
-
71
- return {
72
- title: str(payload.title, "Heartbeat Alert"),
73
- body: safePopupBody,
74
- deliveryText: safePopupBody,
75
- conversationTitle: str(payload.conversationTitle, "Heartbeat"),
76
- conversationSeedMessage: summary,
77
- };
78
- }
79
-
80
35
  // ── Access-request copy contract ─────────────────────────────────────────────
81
36
  //
82
37
  // Deterministic helpers for building guardian-facing access-request copy.
@@ -550,8 +505,6 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
550
505
  body: str(payload.body, "A watcher event requires your attention"),
551
506
  }),
552
507
 
553
- "heartbeat.alert": buildHeartbeatAlertCopy,
554
-
555
508
  "tool_confirmation.required_action": (payload) => ({
556
509
  title: "Tool Confirmation",
557
510
  body: str(payload.toolName, "A tool") + " requires your confirmation",
@@ -605,11 +558,11 @@ export function composeFallbackCopy(
605
558
 
606
559
  const baseCopy: RenderedChannelCopy = template
607
560
  ? template(signal.contextPayload)
608
- : buildGenericCopy(signal);
561
+ : buildGenericCopy();
609
562
 
610
563
  const result: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {};
611
564
  for (const ch of channels) {
612
- result[ch] = applyChannelDefaults(ch, baseCopy, signal);
565
+ result[ch] = applyChannelDefaults(ch, baseCopy);
613
566
  }
614
567
  return result;
615
568
  }
@@ -617,12 +570,11 @@ export function composeFallbackCopy(
617
570
  function applyChannelDefaults(
618
571
  channel: NotificationChannel,
619
572
  baseCopy: RenderedChannelCopy,
620
- signal: NotificationSignal,
621
573
  ): RenderedChannelCopy {
622
574
  const copy: RenderedChannelCopy = { ...baseCopy };
623
575
 
624
576
  if (channel === "telegram") {
625
- copy.deliveryText = buildChatSurfaceFallbackDeliveryText(baseCopy, signal);
577
+ copy.deliveryText = buildChatSurfaceFallbackDeliveryText(baseCopy);
626
578
  }
627
579
 
628
580
  return copy;
@@ -630,7 +582,6 @@ function applyChannelDefaults(
630
582
 
631
583
  function buildChatSurfaceFallbackDeliveryText(
632
584
  baseCopy: RenderedChannelCopy,
633
- signal: NotificationSignal,
634
585
  ): string {
635
586
  const explicit = nonEmpty(baseCopy.deliveryText);
636
587
  if (explicit) return explicit;
@@ -641,23 +592,25 @@ function buildChatSurfaceFallbackDeliveryText(
641
592
  const title = nonEmpty(baseCopy.title);
642
593
  if (title) return title;
643
594
 
644
- return signal.sourceEventName.replace(/[._]/g, " ");
595
+ // No usable text: return empty string. The broadcaster's empty-body skip in
596
+ // `broadcaster.ts` suppresses fallback-derived empty bodies; the
597
+ // deterministic `checkRenderedCopyQuality` (see deterministic-checks.ts)
598
+ // covers the same case when the empty body originates in
599
+ // `decision.renderedCopy`.
600
+ return "";
645
601
  }
646
602
 
647
603
  /**
648
- * Build generic copy when no template matches. Uses the signal's
649
- * sourceEventName and attention hints to produce something reasonable.
604
+ * Build generic copy when no template matches. Returns an empty body so the
605
+ * notification is suppressed rather than rendering an event-name placeholder.
606
+ * The broadcaster's empty-body skip in `broadcaster.ts` catches fallback-derived
607
+ * empty bodies; the deterministic `checkRenderedCopyQuality` (see
608
+ * deterministic-checks.ts) covers the same case when the empty body originates
609
+ * in `decision.renderedCopy`.
650
610
  */
651
- function buildGenericCopy(signal: NotificationSignal): RenderedChannelCopy {
652
- const humanName = signal.sourceEventName.replace(/[._]/g, " ");
653
- const urgencyPrefix =
654
- signal.attentionHints.urgency === "high" ? "Urgent: " : "";
655
- const actionSuffix = signal.attentionHints.requiresAction
656
- ? " — action required"
657
- : "";
658
-
611
+ function buildGenericCopy(): RenderedChannelCopy {
659
612
  return {
660
613
  title: "Notification",
661
- body: `${urgencyPrefix}${humanName}${actionSuffix}`,
614
+ body: "",
662
615
  };
663
616
  }
@@ -35,7 +35,6 @@ import {
35
35
  composeFallbackCopy,
36
36
  hasAccessRequestInstructions,
37
37
  hasInviteFlowDirective,
38
- looksLikeIntermediaryInstruction,
39
38
  } from "./copy-composer.js";
40
39
  import { createDecision } from "./decisions-store.js";
41
40
  import {
@@ -58,6 +57,21 @@ const log = getLogger("notification-decision-engine");
58
57
  const DECISION_TIMEOUT_MS = 15_000;
59
58
  const PROMPT_VERSION = "v4";
60
59
 
60
+ /**
61
+ * Derive a short notification title from a message body. Used when an
62
+ * assistant_tool-sourced signal supplies `requestedMessage` without an
63
+ * explicit `requestedTitle`: trims to the first sentence terminator when
64
+ * present, then caps the result at 60 characters with an ellipsis.
65
+ */
66
+ function deriveTitle(body: string): string {
67
+ const firstSentenceEnd = body.search(/[.!?](\s|$)/);
68
+ const candidate =
69
+ firstSentenceEnd > 0 ? body.slice(0, firstSentenceEnd + 1) : body;
70
+ return candidate.length > 60
71
+ ? candidate.slice(0, 60).trim() + "…"
72
+ : candidate.trim();
73
+ }
74
+
61
75
  /**
62
76
  * Maximum character budget for identity context injected into the notification
63
77
  * decision prompt. We truncate to prevent oversized prompts when SOUL.md /
@@ -326,8 +340,9 @@ function buildFallbackDecision(
326
340
  signal: NotificationSignal,
327
341
  availableChannels: NotificationChannel[],
328
342
  ): NotificationDecision {
343
+ const urgency = signal.attentionHints.urgency;
329
344
  const isHighUrgencyAction =
330
- signal.attentionHints.urgency === "high" &&
345
+ (urgency === "high" || urgency === "critical") &&
331
346
  signal.attentionHints.requiresAction;
332
347
 
333
348
  // Always include the vellum channel in the fallback — it's a local
@@ -667,47 +682,6 @@ function enforceAccessRequestInstructions(
667
682
  };
668
683
  }
669
684
 
670
- function enforceHeartbeatAlertCopy(
671
- decision: NotificationDecision,
672
- signal: NotificationSignal,
673
- ): NotificationDecision {
674
- if (signal.sourceEventName !== "heartbeat.alert") return decision;
675
- if (!decision.shouldNotify || decision.selectedChannels.length === 0)
676
- return decision;
677
-
678
- const fallbackCopy = composeFallbackCopy(signal, decision.selectedChannels);
679
- const nextCopy: Partial<Record<NotificationChannel, RenderedChannelCopy>> = {
680
- ...decision.renderedCopy,
681
- };
682
-
683
- for (const channel of decision.selectedChannels) {
684
- const currentCopy = nextCopy[channel];
685
- if (
686
- currentCopy &&
687
- !heartbeatCopyLooksLikeIntermediaryInstruction(currentCopy)
688
- ) {
689
- continue;
690
- }
691
- const safeCopy = fallbackCopy[channel];
692
- if (!safeCopy) continue;
693
- nextCopy[channel] = safeCopy;
694
- }
695
-
696
- return {
697
- ...decision,
698
- renderedCopy: nextCopy,
699
- };
700
- }
701
-
702
- function heartbeatCopyLooksLikeIntermediaryInstruction(
703
- copy: RenderedChannelCopy,
704
- ): boolean {
705
- return [copy.title, copy.body, copy.deliveryText].some(
706
- (value) =>
707
- typeof value === "string" && looksLikeIntermediaryInstruction(value),
708
- );
709
- }
710
-
711
685
  function ensureAccessRequestInstructionsInCopy(
712
686
  copy: RenderedChannelCopy,
713
687
  requestCode: string,
@@ -790,6 +764,102 @@ export async function evaluateSignal(
790
764
  );
791
765
  }
792
766
 
767
+ // Assistant-tool pass-through: when a producer hands us a verbatim
768
+ // message body via contextPayload.requestedMessage, skip the LLM
769
+ // classifier entirely. The producer has already done the routing and
770
+ // copy decisions — we just enforce the standard post-decision guards
771
+ // and persist the result.
772
+ if (
773
+ signal.sourceChannel === "assistant_tool" &&
774
+ typeof signal.contextPayload === "object" &&
775
+ signal.contextPayload != null &&
776
+ typeof (signal.contextPayload as Record<string, unknown>)
777
+ .requestedMessage === "string" &&
778
+ (
779
+ (signal.contextPayload as Record<string, unknown>)
780
+ .requestedMessage as string
781
+ ).trim().length > 0
782
+ ) {
783
+ const payload = signal.contextPayload as Record<string, unknown>;
784
+ const body = (payload.requestedMessage as string).trim();
785
+ const title =
786
+ typeof payload.requestedTitle === "string" &&
787
+ payload.requestedTitle.trim().length > 0
788
+ ? (payload.requestedTitle as string).trim()
789
+ : deriveTitle(body);
790
+ const isUrgent =
791
+ signal.attentionHints.urgency === "critical" ||
792
+ signal.attentionHints.urgency === "high";
793
+ const defaultChannels: NotificationChannel[] = isUrgent
794
+ ? [...availableChannels]
795
+ : availableChannels.includes("vellum")
796
+ ? ["vellum" as NotificationChannel]
797
+ : [];
798
+ // Honor `--preferred-channels` as ADDITIVE push targets on top of
799
+ // the default channel set. The notification center (vellum) is the
800
+ // always-on canonical inbox; preferred channels add push surfaces
801
+ // on top, they never replace vellum. Disconnected channels are
802
+ // filtered out so we never try to deliver on something unavailable.
803
+ const preferredChannelsRaw = Array.isArray(payload.preferredChannels)
804
+ ? (payload.preferredChannels as unknown[]).filter(
805
+ (c): c is string => typeof c === "string",
806
+ )
807
+ : undefined;
808
+ let selectedChannels = defaultChannels;
809
+ if (preferredChannelsRaw && preferredChannelsRaw.length > 0) {
810
+ const availableSet = new Set<string>(availableChannels);
811
+ const preferredAvailable = preferredChannelsRaw.filter((c) =>
812
+ availableSet.has(c),
813
+ ) as NotificationChannel[];
814
+ if (preferredAvailable.length > 0) {
815
+ selectedChannels = Array.from(
816
+ new Set<NotificationChannel>([
817
+ ...selectedChannels,
818
+ ...preferredAvailable,
819
+ ]),
820
+ );
821
+ }
822
+ }
823
+ // Thread `--deep-link-metadata` through when supplied as a plain object.
824
+ const deepLinkTarget =
825
+ payload.deepLinkMetadata != null &&
826
+ typeof payload.deepLinkMetadata === "object" &&
827
+ !Array.isArray(payload.deepLinkMetadata)
828
+ ? (payload.deepLinkMetadata as Record<string, unknown>)
829
+ : undefined;
830
+ // Populate renderedCopy and conversationActions for every available
831
+ // channel — not just `selectedChannels`. Downstream guards
832
+ // (routing-intent expansion in `enforceRoutingIntent`, urgency-forced
833
+ // vellum prepending in `emit-signal`) may widen `selectedChannels`
834
+ // beyond what we picked here. Pre-seeding copy for all channels ensures
835
+ // the verbatim message survives those expansions rather than falling
836
+ // back to an empty `composeFallbackCopy` body.
837
+ let decision: NotificationDecision = {
838
+ shouldNotify: selectedChannels.length > 0,
839
+ selectedChannels,
840
+ reasoningSummary: "assistant_tool pass-through",
841
+ renderedCopy: Object.fromEntries(
842
+ availableChannels.map((ch) => [ch, { title, body }]),
843
+ ) as NotificationDecision["renderedCopy"],
844
+ conversationActions: Object.fromEntries(
845
+ availableChannels.map((ch) => [ch, { action: "start_new" as const }]),
846
+ ) as NotificationDecision["conversationActions"],
847
+ dedupeKey: signal.signalId,
848
+ confidence: 1.0,
849
+ fallbackUsed: false,
850
+ ...(deepLinkTarget ? { deepLinkTarget } : {}),
851
+ };
852
+ decision = enforceGuardianRequestCode(decision, signal);
853
+ decision = enforceAccessRequestInstructions(decision, signal);
854
+ decision = enforceGuardianCallConversationAffinity(decision, signal);
855
+ decision = enforceConversationAffinity(
856
+ decision,
857
+ signal.conversationAffinityHint,
858
+ );
859
+ decision.persistedDecisionId = persistDecision(signal, decision);
860
+ return decision;
861
+ }
862
+
793
863
  const provider = await getConfiguredProvider("notificationDecision");
794
864
  if (!provider) {
795
865
  log.warn(
@@ -798,7 +868,6 @@ export async function evaluateSignal(
798
868
  let decision = buildFallbackDecision(signal, availableChannels);
799
869
  decision = enforceGuardianRequestCode(decision, signal);
800
870
  decision = enforceAccessRequestInstructions(decision, signal);
801
- decision = enforceHeartbeatAlertCopy(decision, signal);
802
871
  decision = enforceGuardianCallConversationAffinity(decision, signal);
803
872
  decision = enforceConversationAffinity(
804
873
  decision,
@@ -828,7 +897,6 @@ export async function evaluateSignal(
828
897
 
829
898
  decision = enforceGuardianRequestCode(decision, signal);
830
899
  decision = enforceAccessRequestInstructions(decision, signal);
831
- decision = enforceHeartbeatAlertCopy(decision, signal);
832
900
  decision = enforceGuardianCallConversationAffinity(decision, signal);
833
901
  decision = enforceConversationAffinity(
834
902
  decision,
@@ -12,6 +12,7 @@ import { and, eq } from "drizzle-orm";
12
12
  import { getDb } from "../memory/db-connection.js";
13
13
  import { notificationEvents } from "../memory/schema.js";
14
14
  import { getLogger } from "../util/logger.js";
15
+ import { composeFallbackCopy } from "./copy-composer.js";
15
16
  import type { NotificationSignal } from "./signal.js";
16
17
  import type { NotificationChannel, NotificationDecision } from "./types.js";
17
18
 
@@ -88,6 +89,16 @@ export async function runDeterministicChecks(
88
89
  return dedupeCheck;
89
90
  }
90
91
 
92
+ // Check 5: Rendered copy quality (fail-closed)
93
+ const copyCheck = checkRenderedCopyQuality(signal, decision);
94
+ if (!copyCheck.passed) {
95
+ log.info(
96
+ { signalId: signal.signalId, reason: copyCheck.reason },
97
+ "Deterministic check failed: rendered copy quality",
98
+ );
99
+ return copyCheck;
100
+ }
101
+
91
102
  return { passed: true };
92
103
  }
93
104
 
@@ -232,3 +243,88 @@ function checkDedupe(
232
243
 
233
244
  return { passed: true };
234
245
  }
246
+
247
+ /**
248
+ * Fail-closed check that the rendered copy is real text and not an
249
+ * accidental fallback leak (empty body, or body that is just the raw
250
+ * source event name like "user.send_notification").
251
+ *
252
+ * Only validates channels that the decision engine actually emitted
253
+ * copy for. Channels appended after the decision (urgency-forced
254
+ * `vellum` prepend, `enforceRoutingIntent` expansion) have no entry
255
+ * in `renderedCopy` and are left for the broadcaster's
256
+ * `composeFallbackCopy` rescue at delivery time.
257
+ *
258
+ * If `renderedCopy` is empty for every selected channel, the
259
+ * broadcaster's fallback must produce a usable body — otherwise the
260
+ * signal would be silently dropped at delivery (broadcaster skips
261
+ * empty-body channels, `dispatchDecision` reports 0/N sent). In that
262
+ * case, require `composeFallbackCopy` to yield a non-empty body for
263
+ * at least one selected channel; otherwise fail-closed.
264
+ *
265
+ * The event-name-match branch is skipped for `assistant_tool`
266
+ * pass-through decisions because the producer supplied the body
267
+ * verbatim — a coincidental match with the event name is the user's
268
+ * intent, not a fallback leak.
269
+ */
270
+ function checkRenderedCopyQuality(
271
+ signal: NotificationSignal,
272
+ decision: NotificationDecision,
273
+ ): CheckResult {
274
+ if (!decision.shouldNotify) {
275
+ return { passed: true };
276
+ }
277
+
278
+ const isAssistantToolPassthrough =
279
+ decision.reasoningSummary === "assistant_tool pass-through";
280
+ const normalizedEventName = signal.sourceEventName
281
+ .replace(/[._]/g, " ")
282
+ .toLowerCase()
283
+ .trim();
284
+ const rawEventName = signal.sourceEventName.toLowerCase();
285
+
286
+ let anyChannelHasCopy = false;
287
+ for (const channel of decision.selectedChannels) {
288
+ const copy = decision.renderedCopy[channel];
289
+ if (!copy) {
290
+ continue;
291
+ }
292
+ anyChannelHasCopy = true;
293
+ const trimmedBody = copy.body.trim();
294
+ if (trimmedBody.length === 0) {
295
+ return {
296
+ passed: false,
297
+ reason: "rendered copy body is empty",
298
+ };
299
+ }
300
+ if (isAssistantToolPassthrough) {
301
+ continue;
302
+ }
303
+ const normalizedBody = trimmedBody.toLowerCase();
304
+ if (
305
+ normalizedBody === normalizedEventName ||
306
+ normalizedBody === rawEventName
307
+ ) {
308
+ return {
309
+ passed: false,
310
+ reason: "rendered copy body is the source event name (fallback leak)",
311
+ };
312
+ }
313
+ }
314
+
315
+ if (!anyChannelHasCopy && decision.selectedChannels.length > 0) {
316
+ const fallback = composeFallbackCopy(signal, decision.selectedChannels);
317
+ const fallbackUsable = decision.selectedChannels.some(
318
+ (ch) => (fallback[ch]?.body ?? "").trim().length > 0,
319
+ );
320
+ if (!fallbackUsable) {
321
+ return {
322
+ passed: false,
323
+ reason:
324
+ "rendered copy missing for all selected channels and fallback body is empty (would silently drop)",
325
+ };
326
+ }
327
+ }
328
+
329
+ return { passed: true };
330
+ }
@@ -13,6 +13,7 @@ import { v4 as uuid } from "uuid";
13
13
 
14
14
  import { getDeliverableChannels } from "../channels/config.js";
15
15
  import { findGuardianForChannel } from "../contacts/contact-store.js";
16
+ import type { ConversationCreateType } from "../memory/conversation-crud.js";
16
17
  import { getLogger } from "../util/logger.js";
17
18
  import { type BroadcastFn, VellumAdapter } from "./adapters/macos.js";
18
19
  import { PlatformPushAdapter } from "./adapters/platform.js";
@@ -87,6 +88,7 @@ function getBroadcaster(): NotificationBroadcaster {
87
88
  targetGuardianPrincipalId: info.targetGuardianPrincipalId,
88
89
  groupId: info.groupId,
89
90
  source: info.source,
91
+ silent: info.silent,
90
92
  });
91
93
  log.info(
92
94
  {
@@ -204,6 +206,7 @@ export interface EmitSignalParams<TEventName extends string = string> {
204
206
  groupId?: string;
205
207
  scheduleJobId?: string;
206
208
  source?: string;
209
+ conversationType?: ConversationCreateType;
207
210
  };
208
211
  }
209
212
 
@@ -280,7 +283,24 @@ export async function emitNotificationSignal<TEventName extends string>(
280
283
 
281
284
  let decision = await evaluateSignal(signal, connectedChannels);
282
285
 
283
- // Step 2.5: Enforce routing intent policy (fire-time guard)
286
+ // Step 2.5a: High/critical urgency signals always get a system
287
+ // notification via the vellum channel, regardless of what the
288
+ // decision engine selected. This ensures macOS surfaces a banner
289
+ // even when the app is focused.
290
+ const urgency = signal.attentionHints.urgency;
291
+ if (
292
+ (urgency === "high" || urgency === "critical") &&
293
+ decision.shouldNotify &&
294
+ !decision.selectedChannels.includes("vellum")
295
+ ) {
296
+ decision = {
297
+ ...decision,
298
+ selectedChannels: ["vellum", ...decision.selectedChannels],
299
+ reasoningSummary: `${decision.reasoningSummary} (vellum forced: ${urgency} urgency)`,
300
+ };
301
+ }
302
+
303
+ // Step 2.5b: Enforce routing intent policy (fire-time guard)
284
304
  const preEnforcementDecision = decision;
285
305
  decision = enforceRoutingIntent(
286
306
  decision,