@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) 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 +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -20,6 +20,10 @@ export const acpSessionHistory = sqliteTable(
20
20
  stopReason: text("stop_reason"),
21
21
  error: text("error"),
22
22
  eventLogJson: text("event_log_json").notNull().default("[]"),
23
+ // Working directory the agent process was spawned with. Required to
24
+ // resume a persisted session; null for rows written before migration
25
+ // 272 (those sessions are not resumable).
26
+ cwd: text("cwd"),
23
27
  },
24
28
  (table) => [
25
29
  index("idx_acp_session_history_started_at").on(table.startedAt),
@@ -277,6 +277,11 @@ export const onboardingEvents = sqliteTable("onboarding_events", {
277
277
  googleScopesJson: text("google_scopes_json"),
278
278
  priorAssistantsJson: text("prior_assistants_json"),
279
279
  abVariant: text("ab_variant"),
280
+ sessionId: text("session_id"),
281
+ stepName: text("step_name"),
282
+ stepIndex: integer("step_index"),
283
+ completedAt: text("completed_at"),
284
+ funnelVersion: text("funnel_version"),
280
285
  });
281
286
 
282
287
  // Aggregated legacy-loopback auth-fallback counts forwarded by the gateway.
@@ -294,6 +299,14 @@ export const authFallbackEvents = sqliteTable("auth_fallback_events", {
294
299
  windowEnd: integer("window_end").notNull(),
295
300
  });
296
301
 
302
+ // One row per conversation started on the activation-rail bootstrap template.
303
+ // Lets the activation funnel telemetry scope its events to activation
304
+ // conversations without inspecting the bootstrap template at emit time.
305
+ export const activationSessions = sqliteTable("activation_sessions", {
306
+ conversationId: text("conversation_id").primaryKey(),
307
+ createdAt: integer("created_at").notNull(),
308
+ });
309
+
297
310
  export const traceEvents = sqliteTable(
298
311
  "trace_events",
299
312
  {
@@ -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
+ });