@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -105,43 +105,15 @@ mock.module("../../jobs-store.js", () => ({
105
105
  isMemoryEnabled: () => true,
106
106
  }));
107
107
 
108
- // ── v3 health-injection mocks ───────────────────────────────────────
108
+ // ── v3 follow-up flag mock ──────────────────────────────────────────
109
109
  //
110
- // The consolidation handler optionally prepends a v3 health block to the
111
- // prompt when a v3 flag is on AND the rendered report is non-empty. These
112
- // mocks let each test (a) toggle the flag, (b) decide what health block the
113
- // renderer produces, and (c) force the health computation to throw — without
114
- // materializing a real v3 data dir. The tree/core/page-index loaders are
115
- // stubbed to inert values; `computeV3Health` is a pass-through whose result is
116
- // fed to the toggleable `renderV3Health`.
110
+ // A v3 flag being on appends `memory_v3_maintain` to the post-consolidation
111
+ // follow-up fan-out. This mock lets each test toggle the flag.
117
112
  let v3FlagOn = false;
118
113
  mock.module("../../../config/assistant-feature-flags.js", () => ({
119
114
  isAssistantFeatureFlagEnabled: () => v3FlagOn,
120
115
  }));
121
116
 
122
- let renderedHealth = "";
123
- let computeThrows = false;
124
- mock.module("../../v3/health.js", () => ({
125
- computeV3Health: () => {
126
- if (computeThrows) throw new Error("simulated health compute failure");
127
- return {};
128
- },
129
- renderV3Health: () => renderedHealth,
130
- }));
131
-
132
- mock.module("../../v3/tree.js", () => ({
133
- loadLeafTree: async () => ({ leaves: new Map(), byPage: new Map() }),
134
- resolveDataDir: () => "/tmp/v3-data-stub",
135
- }));
136
-
137
- mock.module("../../v3/core.js", () => ({
138
- loadCore: async () => new Set<string>(),
139
- }));
140
-
141
- mock.module("../../v2/page-index.js", () => ({
142
- getPageIndex: async () => ({ entries: [] }),
143
- }));
144
-
145
117
  // ── Workspace pin ───────────────────────────────────────────────────
146
118
  let tmpWorkspace: string;
147
119
  let previousWorkspaceEnv: string | undefined;
@@ -211,10 +183,8 @@ beforeEach(() => {
211
183
  enqueuedJobs.length = 0;
212
184
  nextJobIdCounter = 0;
213
185
 
214
- // v3 health injection defaults: flag off, no rendered block, no throw.
186
+ // v3 follow-up flag default: off.
215
187
  v3FlagOn = false;
216
- renderedHealth = "";
217
- computeThrows = false;
218
188
  });
219
189
 
220
190
  // ---------------------------------------------------------------------------
@@ -409,69 +379,6 @@ describe("memoryV2ConsolidateJob — non-empty buffer", () => {
409
379
  });
410
380
  });
411
381
 
412
- describe("memoryV2ConsolidateJob — v3 health injection", () => {
413
- beforeEach(() => {
414
- writeFileSync(
415
- bufferPath(),
416
- "- [Apr 27, 9:00 AM] Alice prefers VS Code over Vim.\n",
417
- );
418
- });
419
-
420
- test("prepends the health block when a v3 flag is on and the report is actionable", async () => {
421
- v3FlagOn = true;
422
- renderedHealth = "memory-v3 health:\n- 2 unassigned slug(s): a, b";
423
-
424
- const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
425
-
426
- expect(result.kind).toBe("invoked");
427
- const prompt = runnerLastArgs?.prompt as string;
428
- // The health block is PREPENDED, separated by a blank line, with the
429
- // base consolidation prompt still present underneath it.
430
- expect(prompt.startsWith(`${renderedHealth}\n\n`)).toBe(true);
431
- expect(prompt).toContain("memory consolidation");
432
- expect(prompt).not.toContain(CUTOFF_PLACEHOLDER);
433
- });
434
-
435
- test("leaves the prompt unchanged when the report is all-green (empty render)", async () => {
436
- v3FlagOn = true;
437
- renderedHealth = "";
438
-
439
- const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
440
-
441
- expect(result.kind).toBe("invoked");
442
- const prompt = runnerLastArgs?.prompt as string;
443
- expect(prompt).not.toContain("memory-v3 health:");
444
- // Prompt is exactly the resolved consolidation body.
445
- expect(prompt).toContain("memory consolidation");
446
- });
447
-
448
- test("leaves the prompt unchanged when v3 flags are off even if a block would render", async () => {
449
- v3FlagOn = false;
450
- renderedHealth = "memory-v3 health:\n- 1 unassigned slug(s): a";
451
-
452
- const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
453
-
454
- expect(result.kind).toBe("invoked");
455
- const prompt = runnerLastArgs?.prompt as string;
456
- expect(prompt).not.toContain("memory-v3 health:");
457
- expect(prompt).toContain("memory consolidation");
458
- });
459
-
460
- test("falls back to the base prompt when health computation throws", async () => {
461
- v3FlagOn = true;
462
- computeThrows = true;
463
- renderedHealth = "memory-v3 health:\n- should not appear";
464
-
465
- const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
466
-
467
- // A health-compute failure must NEVER break consolidation.
468
- expect(result.kind).toBe("invoked");
469
- const prompt = runnerLastArgs?.prompt as string;
470
- expect(prompt).not.toContain("memory-v3 health:");
471
- expect(prompt).toContain("memory consolidation");
472
- });
473
- });
474
-
475
382
  describe("memoryV2ConsolidateJob — concurrent invocations", () => {
476
383
  beforeEach(() => {
477
384
  writeFileSync(bufferPath(), "- [Apr 27, 9:00 AM] Alice prefers VS Code.\n");
@@ -186,6 +186,28 @@ describe("writePage + readPage round-trip", () => {
186
186
  expect(read!.body).toBe(page.body);
187
187
  });
188
188
 
189
+ test("round-trips authored links: frontmatter (memory-v3 edge lane)", async () => {
190
+ const page = makePage({
191
+ frontmatter: {
192
+ edges: [],
193
+ ref_files: [],
194
+ ref_urls: [],
195
+ links: ["bob-handoff — the partner this hands off to"],
196
+ },
197
+ });
198
+ await writePage(workspaceDir, page);
199
+
200
+ // A page using the optional `links:` frontmatter must NOT be rejected by the
201
+ // strict schema (it would otherwise be dropped from memory-v3 entirely), and
202
+ // the curated links must survive the render → read round-trip so the edge
203
+ // graph can parse them back from the rendered frontmatter.
204
+ const read = await readPage(workspaceDir, page.slug);
205
+ expect(read).not.toBeNull();
206
+ expect(read!.frontmatter.links).toEqual([
207
+ "bob-handoff — the partner this hands off to",
208
+ ]);
209
+ });
210
+
189
211
  test("renders frontmatter at the top with --- delimiters", async () => {
190
212
  const page = makePage();
191
213
  await writePage(workspaceDir, page);
@@ -73,11 +73,6 @@ import {
73
73
  type MemoryJob,
74
74
  type MemoryJobType,
75
75
  } from "../jobs-store.js";
76
- import { getPageIndex } from "../v2/page-index.js";
77
- import { loadCore } from "../v3/core.js";
78
- import { computeV3Health, renderV3Health } from "../v3/health.js";
79
- import { loadLeafTree, resolveDataDir } from "../v3/tree.js";
80
- import type { LeafPath, Slug } from "../v3/types.js";
81
76
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "./constants.js";
82
77
  import { resolveConsolidationPrompt } from "./prompts/consolidation.js";
83
78
 
@@ -87,8 +82,7 @@ const log = getLogger("memory-v2-consolidate");
87
82
  const JOB_NAME = "memory.consolidate";
88
83
 
89
84
  /**
90
- * v3 plugin flags. Either being on (a) prepends a v3 health block to the
91
- * consolidation prompt and (b) enqueues `memory_v3_maintain` as a
85
+ * v3 plugin flags. Either being on enqueues `memory_v3_maintain` as a
92
86
  * post-consolidation follow-up. These gate the v3 plugin itself.
93
87
  */
94
88
  const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
@@ -182,15 +176,10 @@ export async function memoryV2ConsolidateJob(
182
176
  // it to a regular file under 1 MiB before substitution so a stray path
183
177
  // (or a `/dev/zero`-style pseudo-file) cannot exfiltrate megabytes of
184
178
  // bytes through the wake hint.
185
- //
186
- // That resolved prompt is the base; `maybePrependV3Health` prepends a
187
- // freshly computed v3 health block when a v3 flag is on and the tree has
188
- // actionable drift, leaving it untouched otherwise.
189
- const basePrompt = resolveConsolidationPrompt(
179
+ const prompt = resolveConsolidationPrompt(
190
180
  config.memory.v2.consolidation_prompt_path,
191
181
  cutoff,
192
182
  );
193
- const prompt = await maybePrependV3Health(basePrompt, config);
194
183
 
195
184
  const runResult = await runBackgroundJob({
196
185
  jobName: JOB_NAME,
@@ -261,56 +250,6 @@ export async function memoryV2ConsolidateJob(
261
250
  }
262
251
  }
263
252
 
264
- /**
265
- * When a v3 flag is enabled and the v3 tree has actionable structural drift,
266
- * prepend a rendered health block to `basePrompt` so the consolidation run can
267
- * fold tree maintenance into its pass. Returns `basePrompt` UNCHANGED when:
268
- * - neither `memory-v3-shadow` nor `memory-v3-live` is enabled,
269
- * - the health report is all-green (`renderV3Health` returns ""), or
270
- * - computing the report throws for any reason.
271
- *
272
- * The block is computed here (not baked into the prompt template) so an
273
- * operator's custom consolidation prompt stays untouched and the block reflects
274
- * the tree state at run time. Health computation is wrapped so a load/compute
275
- * failure can never break consolidation — the run proceeds on the base prompt.
276
- */
277
- async function maybePrependV3Health(
278
- basePrompt: string,
279
- config: AssistantConfig,
280
- ): Promise<string> {
281
- const v3Enabled =
282
- isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config) ||
283
- isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
284
- if (!v3Enabled) return basePrompt;
285
-
286
- try {
287
- const pageIndex = await getPageIndex(getWorkspaceDir());
288
- const pageLeaves = new Map<Slug, LeafPath[]>();
289
- const allSlugs: Slug[] = [];
290
- for (const entry of pageIndex.entries) {
291
- pageLeaves.set(entry.slug, entry.leaves);
292
- allSlugs.push(entry.slug);
293
- }
294
-
295
- const dataDir = resolveDataDir();
296
- const [tree, core] = await Promise.all([
297
- loadLeafTree(dataDir, pageLeaves),
298
- loadCore(dataDir),
299
- ]);
300
-
301
- const report = computeV3Health({ tree, allSlugs, core });
302
- const healthBlock = renderV3Health(report);
303
- if (healthBlock.length === 0) return basePrompt;
304
- return `${healthBlock}\n\n${basePrompt}`;
305
- } catch (err) {
306
- log.warn(
307
- { err: err instanceof Error ? err.message : String(err) },
308
- "consolidation: v3 health computation failed; using base prompt",
309
- );
310
- return basePrompt;
311
- }
312
- }
313
-
314
253
  /**
315
254
  * Read `memory/buffer.md`. Missing file → empty string so the skip-on-empty
316
255
  * branch doesn't have to distinguish "no file" from "blank file".
@@ -41,6 +41,11 @@ export const ConceptPageFrontmatterSchema = z
41
41
  ref_urls: z.array(z.string().url()).default([]),
42
42
  summary: z.string().optional(),
43
43
  leaves: z.array(z.string()).optional(),
44
+ // Optional authored `"<target-slug> — <why>"` cross-links. Curated, first-class
45
+ // edges for the memory-v3 edge lane. Declared here so `.strict()` does not
46
+ // reject a page that uses `links:`, and so `renderPageContent` round-trips the
47
+ // field (the edge graph reads it back from the rendered frontmatter).
48
+ links: z.array(z.string()).optional(),
44
49
  })
45
50
  .strict();
46
51
 
@@ -8,7 +8,8 @@
8
8
  */
9
9
 
10
10
  import { credentialKey } from "../../../security/credential-key.js";
11
- import { getSecureKeyAsync } from "../../../security/secure-keys.js";
11
+ import { getSecureKeyResultAsync } from "../../../security/secure-keys.js";
12
+ import { BackendUnavailableError, ConfigError } from "../../../util/errors.js";
12
13
  import { getLogger } from "../../../util/logger.js";
13
14
 
14
15
  const log = getLogger("telegram-api");
@@ -197,13 +198,21 @@ async function retryableFetch<T>(
197
198
  // ---------------------------------------------------------------------------
198
199
 
199
200
  async function resolveBotToken(): Promise<string> {
200
- const botToken = await getSecureKeyAsync(
201
+ const result = await getSecureKeyResultAsync(
201
202
  credentialKey("telegram", "bot_token"),
202
203
  );
203
- if (!botToken) {
204
- throw new Error("Telegram bot token not configured");
204
+ if (result.value) {
205
+ return result.value;
205
206
  }
206
- return botToken;
207
+ // Distinguish a transient credential-store outage from a token that was
208
+ // genuinely never set. Both surface as an absent value, but only the
209
+ // latter is an operator misconfiguration; the former is retryable
210
+ // infrastructure unavailability that callers should not treat as
211
+ // "not configured".
212
+ if (result.unreachable) {
213
+ throw new BackendUnavailableError("Telegram credential store unreachable");
214
+ }
215
+ throw new ConfigError("Telegram bot token not configured");
207
216
  }
208
217
 
209
218
  // ---------------------------------------------------------------------------
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Tests for the notification copy-composer — specifically the fallback
3
+ * path that composeFallbackCopy uses when the LLM is unavailable, and
4
+ * the shared deriveTitle utility.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import { composeFallbackCopy, deriveTitle } from "../copy-composer.js";
10
+ import type { NotificationSignal } from "../signal.js";
11
+ import type { NotificationChannel } from "../types.js";
12
+
13
+ // ── Helpers ───────────────────────────────────────────────────────────
14
+
15
+ function makeSignal(
16
+ overrides?: Partial<NotificationSignal>,
17
+ ): NotificationSignal {
18
+ return {
19
+ signalId: "sig-copy-test-1",
20
+ createdAt: Date.now(),
21
+ sourceChannel: "scheduler",
22
+ sourceContextId: "ctx-1",
23
+ sourceEventName: "user.send_notification",
24
+ contextPayload: {},
25
+ attentionHints: {
26
+ requiresAction: false,
27
+ urgency: "low",
28
+ isAsyncBackground: false,
29
+ visibleInSourceNow: false,
30
+ },
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ const CHANNELS: NotificationChannel[] = ["vellum" as NotificationChannel];
36
+
37
+ // ── composeFallbackCopy with requestedMessage ─────────────────────────
38
+
39
+ describe("composeFallbackCopy honors requestedMessage / requestedTitle", () => {
40
+ test("uses requestedMessage as body and requestedTitle as title", () => {
41
+ const signal = makeSignal({
42
+ contextPayload: {
43
+ requestedMessage: "Take out the trash",
44
+ requestedTitle: "Household Reminder",
45
+ },
46
+ });
47
+ const copy = composeFallbackCopy(signal, CHANNELS);
48
+
49
+ expect(copy.vellum?.body).toBe("Take out the trash");
50
+ expect(copy.vellum?.title).toBe("Household Reminder");
51
+ expect(copy.vellum?.conversationSeedMessage).toBe("Take out the trash");
52
+ });
53
+
54
+ test("derives title from body when requestedTitle is absent", () => {
55
+ const signal = makeSignal({
56
+ contextPayload: {
57
+ requestedMessage: "First sentence. Second sentence follows.",
58
+ },
59
+ });
60
+ const copy = composeFallbackCopy(signal, CHANNELS);
61
+
62
+ expect(copy.vellum?.body).toBe("First sentence. Second sentence follows.");
63
+ expect(copy.vellum?.title).toBe("First sentence.");
64
+ });
65
+
66
+ test("derives title from body when requestedTitle is empty string", () => {
67
+ const signal = makeSignal({
68
+ contextPayload: {
69
+ requestedMessage: "Some message body here",
70
+ requestedTitle: "",
71
+ },
72
+ });
73
+ const copy = composeFallbackCopy(signal, CHANNELS);
74
+
75
+ expect(copy.vellum?.body).toBe("Some message body here");
76
+ expect(copy.vellum?.title).toBe("Some message body here");
77
+ });
78
+
79
+ test("derives title from body when requestedTitle is whitespace", () => {
80
+ const signal = makeSignal({
81
+ contextPayload: {
82
+ requestedMessage: "Whitespace title test",
83
+ requestedTitle: " ",
84
+ },
85
+ });
86
+ const copy = composeFallbackCopy(signal, CHANNELS);
87
+
88
+ expect(copy.vellum?.body).toBe("Whitespace title test");
89
+ expect(copy.vellum?.title).toBe("Whitespace title test");
90
+ });
91
+
92
+ test("trims whitespace from requestedMessage", () => {
93
+ const signal = makeSignal({
94
+ contextPayload: {
95
+ requestedMessage: " padded message ",
96
+ requestedTitle: " padded title ",
97
+ },
98
+ });
99
+ const copy = composeFallbackCopy(signal, CHANNELS);
100
+
101
+ expect(copy.vellum?.body).toBe("padded message");
102
+ expect(copy.vellum?.title).toBe("padded title");
103
+ });
104
+
105
+ test("populates copy for all requested channels", () => {
106
+ const channels = [
107
+ "vellum" as NotificationChannel,
108
+ "telegram" as NotificationChannel,
109
+ ];
110
+ const signal = makeSignal({
111
+ contextPayload: {
112
+ requestedMessage: "Multi-channel test",
113
+ requestedTitle: "Multi Title",
114
+ },
115
+ });
116
+ const copy = composeFallbackCopy(signal, channels);
117
+
118
+ expect(copy.vellum?.body).toBe("Multi-channel test");
119
+ expect(copy.vellum?.title).toBe("Multi Title");
120
+ expect(copy.telegram?.body).toBe("Multi-channel test");
121
+ expect(copy.telegram?.title).toBe("Multi Title");
122
+ });
123
+
124
+ test("falls through to template when requestedMessage is absent", () => {
125
+ const signal = makeSignal({
126
+ sourceEventName: "schedule.notify",
127
+ contextPayload: {
128
+ message: "Standup time",
129
+ label: "Daily Standup",
130
+ },
131
+ });
132
+ const copy = composeFallbackCopy(signal, CHANNELS);
133
+
134
+ expect(copy.vellum?.title).toBe("Daily Standup");
135
+ expect(copy.vellum?.body).toBe("Standup time");
136
+ });
137
+
138
+ test("falls through to template when requestedMessage is empty string", () => {
139
+ const signal = makeSignal({
140
+ sourceEventName: "schedule.notify",
141
+ contextPayload: {
142
+ requestedMessage: "",
143
+ message: "Standup time",
144
+ },
145
+ });
146
+ const copy = composeFallbackCopy(signal, CHANNELS);
147
+
148
+ expect(copy.vellum?.title).toBe("Reminder");
149
+ expect(copy.vellum?.body).toBe("Standup time");
150
+ });
151
+
152
+ test("falls through to template when requestedMessage is whitespace", () => {
153
+ const signal = makeSignal({
154
+ sourceEventName: "schedule.notify",
155
+ contextPayload: {
156
+ requestedMessage: " ",
157
+ message: "Standup time",
158
+ },
159
+ });
160
+ const copy = composeFallbackCopy(signal, CHANNELS);
161
+
162
+ expect(copy.vellum?.title).toBe("Reminder");
163
+ expect(copy.vellum?.body).toBe("Standup time");
164
+ });
165
+
166
+ test("falls through to generic copy when no requestedMessage and no template match", () => {
167
+ const signal = makeSignal({
168
+ sourceEventName: "unknown.event",
169
+ contextPayload: {},
170
+ });
171
+ const copy = composeFallbackCopy(signal, CHANNELS);
172
+
173
+ expect(copy.vellum?.title).toBe("Notification");
174
+ expect(copy.vellum?.body).toBe("");
175
+ });
176
+
177
+ test("requestedMessage takes priority over event-name template", () => {
178
+ const signal = makeSignal({
179
+ sourceEventName: "schedule.notify",
180
+ contextPayload: {
181
+ requestedMessage: "User-supplied content",
182
+ requestedTitle: "User Title",
183
+ message: "Template message field",
184
+ label: "Template label field",
185
+ },
186
+ });
187
+ const copy = composeFallbackCopy(signal, CHANNELS);
188
+
189
+ expect(copy.vellum?.body).toBe("User-supplied content");
190
+ expect(copy.vellum?.title).toBe("User Title");
191
+ });
192
+
193
+ test("works with non-assistant_tool source channels", () => {
194
+ for (const sourceChannel of ["scheduler", "watcher", "slack"] as const) {
195
+ const signal = makeSignal({
196
+ sourceChannel,
197
+ contextPayload: {
198
+ requestedMessage: `Message from ${sourceChannel}`,
199
+ },
200
+ });
201
+ const copy = composeFallbackCopy(signal, CHANNELS);
202
+
203
+ expect(copy.vellum?.body).toBe(`Message from ${sourceChannel}`);
204
+ }
205
+ });
206
+ });
207
+
208
+ // ── deriveTitle ───────────────────────────────────────────────────────
209
+
210
+ describe("deriveTitle", () => {
211
+ test("extracts first sentence when period is present", () => {
212
+ expect(deriveTitle("First sentence. Second sentence.")).toBe(
213
+ "First sentence.",
214
+ );
215
+ });
216
+
217
+ test("extracts first sentence on exclamation mark", () => {
218
+ expect(deriveTitle("Alert! More details follow.")).toBe("Alert!");
219
+ });
220
+
221
+ test("extracts first sentence on question mark", () => {
222
+ expect(deriveTitle("Ready? Let me know.")).toBe("Ready?");
223
+ });
224
+
225
+ test("uses full body when no sentence terminator", () => {
226
+ expect(deriveTitle("No terminator here")).toBe("No terminator here");
227
+ });
228
+
229
+ test("truncates to 60 characters with ellipsis", () => {
230
+ const long = "A".repeat(80);
231
+ const result = deriveTitle(long);
232
+ expect(result.length).toBeLessThanOrEqual(61); // 60 + ellipsis char
233
+ expect(result.endsWith("\u2026")).toBe(true);
234
+ });
235
+
236
+ test("does not truncate at exactly 60 characters", () => {
237
+ const exact = "A".repeat(60);
238
+ expect(deriveTitle(exact)).toBe(exact);
239
+ });
240
+
241
+ test("trims whitespace", () => {
242
+ expect(deriveTitle(" trimmed ")).toBe("trimmed");
243
+ });
244
+ });