@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
@@ -301,7 +301,14 @@ export type ConversationSlackMessage = z.infer<
301
301
  */
302
302
  export const ConversationContentBlockSchema = z.discriminatedUnion("type", [
303
303
  z.object({ type: z.literal("text"), text: z.string() }),
304
- z.object({ type: z.literal("thinking"), thinking: z.string() }),
304
+ z.object({
305
+ type: z.literal("thinking"),
306
+ thinking: z.string(),
307
+ /** Unix ms when the model began emitting this reasoning block. */
308
+ startedAt: z.number().optional(),
309
+ /** Unix ms when this reasoning block completed. */
310
+ completedAt: z.number().optional(),
311
+ }),
305
312
  z.object({
306
313
  type: z.literal("tool_use"),
307
314
  toolCall: ConversationMessageToolCallSchema,
@@ -335,7 +342,20 @@ export const ConversationMessageSchema = z.object({
335
342
  * assistant messages were consolidated for history rendering.
336
343
  */
337
344
  mergedMessageIds: z.array(z.string()).optional(),
338
- role: z.string(),
345
+ /**
346
+ * Client-generated idempotency nonce echoed back on the persisted row.
347
+ * Present only for messages a client sent with one (user sends); lets the
348
+ * client correlate an optimistic row with its confirmed server row by
349
+ * identity instead of matching message text.
350
+ */
351
+ clientMessageId: z.string().optional(),
352
+ /**
353
+ * Renderable conversation roles only. The messages endpoint emits a
354
+ * UI-facing transcript, so it excludes the `system` rows that the
355
+ * underlying `MessageRole` column also permits — those are agent-context
356
+ * scaffolding, never a displayed turn.
357
+ */
358
+ role: z.enum(["user", "assistant"]),
339
359
  /**
340
360
  * Flat plain-text body (joined text segments). Redundant with
341
361
  * `textSegments`/`contentOrder` for clients that render from the positional
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Wire contract for the memory v3 selection set surfaced in the inspector's
3
3
  * Memory tab. Mirrors the return value of `getMemoryV3SelectionForInspector`
4
- * in `assistant/src/memory/v3/selection-log-store.ts`.
4
+ * in `assistant/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts`.
5
5
  *
6
6
  * Canonical wire-contract source. Assistant code imports the types directly
7
7
  * from this file via relative paths; external consumers (web client, gateway,
@@ -12,19 +12,27 @@
12
12
  * `live` / `shadow` reflect the CURRENT `memory-v3-live` / `memory-v3-shadow`
13
13
  * flag state, not the per-turn history (the selection rows don't record which
14
14
  * mode was active when they were written). When `live` is true the rendered
15
- * `injectedText` block was actually injected into the turn; when only `shadow`
16
- * is true it is the block that WOULD have been injected. For stable-flag
17
- * shadow validation these coincide; recording per-turn `injected` is a
18
- * documented follow-up.
15
+ * `injectedText` reflects the live selection; when only `shadow` is true it is
16
+ * the block that WOULD have been injected.
17
+ *
18
+ * `injectedText` is APPROXIMATE, not a verbatim record. It is re-rendered from
19
+ * the persisted selection rows, which store slug/source/pinned but NOT the
20
+ * matched-section identity. A page that live injection sent as a single matched
21
+ * section is re-rendered here as its FULL page (the section ordinal isn't
22
+ * persisted), so the text can differ from what was actually sent to the model.
23
+ * The selected slug set is exact — only the rendered text is full-page
24
+ * approximate. Persisting the per-turn injected section is a documented
25
+ * follow-up.
19
26
  */
20
27
 
21
28
  import { z } from "zod";
22
29
 
23
30
  /**
24
31
  * One selected page in the v3 set. `source` is the lane that surfaced it —
25
- * the daemon emits `l1+l2`, `core+l2`, `needle`, or `carry-forward` — but the
26
- * schema stays a permissive string so a new lane label doesn't break parsing
27
- * on the FE. `pinned` marks a slug carried forward across turns.
32
+ * the daemon emits `needle`, `dense`, `edge`, or `carry-forward` — but the
33
+ * schema stays a permissive string so a new lane label (or a historical
34
+ * pre-lane row) doesn't break parsing on the FE. `pinned` marks a slug carried
35
+ * forward across turns.
28
36
  */
29
37
  export const MemoryV3SelectionRowSchema = z.object({
30
38
  slug: z.string(),
@@ -36,8 +44,9 @@ export type MemoryV3SelectionRow = z.infer<typeof MemoryV3SelectionRowSchema>;
36
44
 
37
45
  /**
38
46
  * Memory v3 selection log shape. `injectedText` is the rendered
39
- * `<memory>…</memory>` block for the selection (actual when `live`, would-be
40
- * when `shadow`-only).
47
+ * `<memory>…</memory>` block for the selection — APPROXIMATE: re-rendered from
48
+ * the persisted rows, with full pages where live injection used a matched
49
+ * section (the section identity isn't persisted). See the file header.
41
50
  */
42
51
  export const MemoryV3SelectionLogSchema = z.object({
43
52
  turn: z.number(),
@@ -14,7 +14,7 @@
14
14
  import { answerCall } from "../calls/call-domain.js";
15
15
  import { findContactChannel } from "../contacts/contact-store.js";
16
16
  import { upsertContactChannel } from "../contacts/contacts-write.js";
17
- import { findConversation } from "../daemon/conversation-store.js";
17
+ import { findConversation } from "../daemon/conversation-registry.js";
18
18
  import {
19
19
  type CanonicalGuardianRequest,
20
20
  getCanonicalGuardianRequest,
@@ -196,6 +196,32 @@ const pendingInteractionResolver: GuardianRequestResolver = {
196
196
  // resolveConfirmation() owns pendingInteractions deregistration.
197
197
  const userDecision: UserDecision =
198
198
  decision.action === "reject" ? "deny" : "allow";
199
+
200
+ // Route-owned confirmations (e.g. the ACP spawn/steer approval gate in
201
+ // acp-routes.ts) carry a `directResolve` and are NOT owned by any
202
+ // Conversation.prompter, so handleConfirmationResponse below would no-op
203
+ // and the caller would block until timeout. Resolve them directly, exactly
204
+ // as the POST /v1/confirm route does (see approval-routes.ts).
205
+ if (interaction.directResolve) {
206
+ pendingInteractions.resolve(
207
+ request.id,
208
+ userDecision === "allow" ? "approved" : "rejected",
209
+ );
210
+ interaction.directResolve(userDecision);
211
+ log.info(
212
+ {
213
+ event: "resolver_tool_approval_applied",
214
+ requestId: request.id,
215
+ action: decision.action,
216
+ conversationId: request.conversationId,
217
+ toolName: request.toolName,
218
+ directResolve: true,
219
+ },
220
+ "Tool approval resolver: direct-resolve interaction resolved",
221
+ );
222
+ return { ok: true, applied: true };
223
+ }
224
+
199
225
  const conversation = findConversation(interaction.conversationId);
200
226
  if (!conversation) {
201
227
  return {
@@ -97,6 +97,7 @@ function getHeartbeatWakeSource(now: number): HeartbeatWakeSource | null {
97
97
 
98
98
  const service = HeartbeatService.getInstance();
99
99
  if (service?.isConsecutiveRunCapReached) return null;
100
+ if (service?.isDailyCapReached) return null;
100
101
 
101
102
  const serviceNextRunAt = service?.nextRunAt ?? null;
102
103
  let nextRunAt = serviceNextRunAt;
@@ -1,245 +1,226 @@
1
1
  /**
2
- * Tests for the `memory v3` CLI command group's daemon-side handlers.
2
+ * Tests for the `assistant memory v3` CLI subgroup (section-lane model).
3
3
  *
4
- * The CLI subcommands are thin IPC shells over the handlers in
5
- * `runtime/routes/memory-v3-routes.ts`; the logic worth testing lives there.
6
- * We exercise the handlers directly against a temp workspace + data dir
7
- * injected via their `deps` parameter. Generic taxonomy only
8
- * (domain-a/topic-x, etc.).
9
- *
10
- * Source-of-truth note: `health` and `set-core` build the leaf tree from each
11
- * concept page's `leaves:` frontmatter (via the page index) and union it over
12
- * `assignments.json`, matching the consolidation-injected health block. The
13
- * fixture below writes concept pages with `leaves:` frontmatter as the
14
- * authoritative membership and keeps a *stale* `assignments.json` to prove the
15
- * handlers read the frontmatter, not the stale assignments. The seeded
16
- * skill/CLI-command catalogs are mocked empty so `allSlugs` (derived from the
17
- * page index) is deterministic.
4
+ * Validates:
5
+ * - Subcommand registration (rebuild-index, backfill-sections) under
6
+ * `memory v3`.
7
+ * - Each subcommand maps to the right `cliIpcCall` method.
8
+ * - `backfill-sections` passes a long IPC timeout (the one-time full-corpus
9
+ * embed easily outlasts the default 60s), while `rebuild-index` does not.
10
+ * - IPC error paths return a non-zero exit code without throwing.
18
11
  */
19
12
 
20
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
21
- import { tmpdir } from "node:os";
22
- import { join } from "node:path";
23
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
24
-
25
- import * as cliCommandStore from "../../../memory/v2/cli-command-store.js";
26
- import { invalidatePageIndex } from "../../../memory/v2/page-index.js";
27
- import * as skillStore from "../../../memory/v2/skill-store.js";
28
- import {
29
- handleMemoryV3Health,
30
- handleMemoryV3Reconcile,
31
- handleMemoryV3SetCore,
32
- UnknownLeafError,
33
- } from "../../../runtime/routes/memory-v3-routes.js";
34
-
35
- // ---------------------------------------------------------------------------
36
- // Temp data-dir fixture
37
- // ---------------------------------------------------------------------------
38
-
39
- let workspaceDir: string;
40
- let dataDir: string;
41
-
42
- /** Write a leaf markdown file with the given frontmatter under leaves/. */
43
- async function writeLeaf(
44
- relPath: string,
45
- frontmatter: { path: string; in_core: boolean; id?: string },
46
- body = "description",
47
- ): Promise<void> {
48
- const file = join(dataDir, "leaves", `${relPath}.md`);
49
- await mkdir(join(file, ".."), { recursive: true });
50
- const fm = [
51
- "---",
52
- `path: ${frontmatter.path}`,
53
- `in_core: ${frontmatter.in_core}`,
54
- ...(frontmatter.id ? [`id: ${frontmatter.id}`] : []),
55
- "---",
56
- body,
57
- "",
58
- ].join("\n");
59
- await writeFile(file, fm);
13
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
14
+
15
+ import { Command } from "commander";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Mock state
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** The last `cliIpcCall` invocation captured for assertions. */
22
+ let lastIpcCall: {
23
+ method: string;
24
+
25
+ params?: any;
26
+ options?: { timeoutMs?: number; signal?: AbortSignal };
27
+ } | null = null;
28
+
29
+ /** The result that cliIpcCall will return. */
30
+ let mockIpcResult: {
31
+ ok: boolean;
32
+ result?: unknown;
33
+ error?: string;
34
+ } = { ok: true, result: { articles: 3, sections: 12, failures: 0 } };
35
+
36
+ /** Captured log output for assertion. */
37
+ let logOutput: string[] = [];
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Mocks
41
+ // ---------------------------------------------------------------------------
42
+
43
+ mock.module("../../../ipc/cli-client.js", () => ({
44
+ cliIpcCall: async (
45
+ method: string,
46
+ params?: any,
47
+ options?: { timeoutMs?: number; signal?: AbortSignal },
48
+ ) => {
49
+ lastIpcCall = { method, params, options };
50
+ return mockIpcResult;
51
+ },
52
+ }));
53
+
54
+ const capture = (...args: unknown[]) => {
55
+ logOutput.push(args.map(String).join(" "));
56
+ };
57
+ const fakeLogger = {
58
+ info: capture,
59
+ warn: capture,
60
+ error: capture,
61
+ debug: () => {},
62
+ };
63
+ mock.module("../../../util/logger.js", () => ({
64
+ getLogger: () => fakeLogger,
65
+ getCliLogger: () => fakeLogger,
66
+ }));
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Import modules under test (after mocks)
70
+ // ---------------------------------------------------------------------------
71
+
72
+ const { registerMemoryV3Command } = await import("../memory-v3.js");
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Test helpers
76
+ // ---------------------------------------------------------------------------
77
+
78
+ function buildProgram(): Command {
79
+ const program = new Command();
80
+ program.exitOverride();
81
+ program.configureOutput({
82
+ writeErr: () => {},
83
+ writeOut: () => {},
84
+ });
85
+ registerMemoryV3Command(program);
86
+ return program;
60
87
  }
61
88
 
62
- /** Write a concept page with `leaves:` frontmatter under memory/concepts/. */
63
- async function writePage(slug: string, leaves: string[]): Promise<void> {
64
- const file = join(workspaceDir, "memory", "concepts", `${slug}.md`);
65
- await mkdir(join(file, ".."), { recursive: true });
66
- const fm = [
67
- "---",
68
- `summary: ${slug} summary`,
69
- "leaves:",
70
- ...leaves.map((l) => ` - ${l}`),
71
- "---",
72
- `${slug} body`,
73
- "",
74
- ].join("\n");
75
- await writeFile(file, fm);
89
+ async function runCommand(
90
+ args: string[],
91
+ ): Promise<{ stdout: string; exitCode: number }> {
92
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
93
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
94
+ const stdoutChunks: string[] = [];
95
+
96
+ process.stdout.write = ((chunk: unknown) => {
97
+ stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
98
+ return true;
99
+ }) as typeof process.stdout.write;
100
+
101
+ process.stderr.write = (() => true) as typeof process.stderr.write;
102
+
103
+ process.exitCode = 0;
104
+
105
+ try {
106
+ const program = buildProgram();
107
+ await program.parseAsync(["node", "assistant", ...args]);
108
+ } catch {
109
+ if (process.exitCode === 0) process.exitCode = 1;
110
+ } finally {
111
+ process.stdout.write = originalStdoutWrite;
112
+ process.stderr.write = originalStderrWrite;
113
+ }
114
+
115
+ const exitCode = process.exitCode ?? 0;
116
+ process.exitCode = 0;
117
+
118
+ return { exitCode, stdout: stdoutChunks.join("") };
76
119
  }
77
120
 
78
- beforeEach(async () => {
79
- workspaceDir = await mkdtemp(join(tmpdir(), "mem-v3-cli-ws-"));
80
- dataDir = join(workspaceDir, "memory", "v3", "data");
81
- await mkdir(dataDir, { recursive: true });
82
-
83
- // Keep the page index deterministic regardless of the on-disk seeded
84
- // catalogs, and clear any module-cached index from a prior test.
85
- spyOn(skillStore, "listSkillEntries").mockReturnValue([]);
86
- spyOn(cliCommandStore, "listCliCommandEntries").mockReturnValue([]);
87
- invalidatePageIndex();
88
-
89
- // A small generic tree: two leaves under domain-a, one under domain-b.
90
- await writeLeaf("domain-a/topic-x", {
91
- path: "domain-a/topic-x",
92
- in_core: true,
93
- id: "leaf-x",
94
- });
95
- await writeLeaf("domain-a/topic-y", {
96
- path: "domain-a/topic-y",
97
- in_core: false,
98
- id: "leaf-y",
99
- });
100
- await writeLeaf("domain-b/topic-z", {
101
- path: "domain-b/topic-z",
102
- in_core: false,
103
- id: "leaf-z",
104
- });
121
+ // ---------------------------------------------------------------------------
122
+ // Setup
123
+ // ---------------------------------------------------------------------------
105
124
 
106
- // Authoritative membership lives in each page's `leaves:` frontmatter.
107
- await writePage("page-1", ["domain-a/topic-x"]);
108
- await writePage("page-2", ["domain-a/topic-x", "domain-a/topic-y"]);
109
- await writePage("page-3", ["domain-b/topic-z"]);
110
-
111
- // STALE assignments.json: deliberately divergent from the frontmatter above.
112
- // It re-maps the SAME slugs that already carry `leaves:` frontmatter to the
113
- // WRONG leaves. `loadLeafTree`'s per-page precedence only lets frontmatter win
114
- // when it is non-empty, so for these slugs the frontmatter wins and
115
- // assignments.json contributes no membership — proving the handlers read the
116
- // page index, not these stale assignments (the regression Defect A guards
117
- // against). We deliberately avoid a slug that exists ONLY in assignments.json:
118
- // such a slug has no frontmatter, falls back to its assignments entry, and
119
- // would inflate the cost preview below with phantom membership.
120
- await writeFile(
121
- join(dataDir, "assignments.json"),
122
- JSON.stringify({
123
- "page-1": ["domain-a/bogus-leaf"],
124
- "page-2": ["domain-b/topic-z"],
125
- }),
126
- );
127
- await writeFile(
128
- join(dataDir, "core.json"),
129
- JSON.stringify({ alwaysOn: ["domain-a/topic-x"] }),
130
- );
125
+ beforeEach(() => {
126
+ lastIpcCall = null;
127
+ mockIpcResult = {
128
+ ok: true,
129
+ result: { articles: 3, sections: 12, failures: 0 },
130
+ };
131
+ logOutput = [];
132
+ process.exitCode = 0;
131
133
  });
132
134
 
133
- afterEach(async () => {
134
- invalidatePageIndex();
135
- await rm(workspaceDir, { recursive: true, force: true });
135
+ // ---------------------------------------------------------------------------
136
+ // Subcommand registration
137
+ // ---------------------------------------------------------------------------
138
+
139
+ describe("subcommand registration", () => {
140
+ test("registers v3 under memory with the expected subcommands", () => {
141
+ const program = buildProgram();
142
+ const memory = program.commands.find((c) => c.name() === "memory");
143
+ expect(memory).toBeDefined();
144
+ const v3 = memory!.commands.find((c) => c.name() === "v3");
145
+ expect(v3).toBeDefined();
146
+ const subcommandNames = v3!.commands.map((c) => c.name()).sort();
147
+ expect(subcommandNames).toEqual(["backfill-sections", "rebuild-index"]);
148
+ });
136
149
  });
137
150
 
138
151
  // ---------------------------------------------------------------------------
139
- // set-core
152
+ // rebuild-index
140
153
  // ---------------------------------------------------------------------------
141
154
 
142
- describe("handleMemoryV3SetCore", () => {
143
- test("rejects an unknown leaf and does not write", async () => {
144
- const call = handleMemoryV3SetCore(
145
- { add: ["domain-a/does-not-exist"], write: true },
146
- { dataDir, workspaceDir },
147
- );
148
- await expect(call).rejects.toThrow(/domain-a\/does-not-exist/);
149
- await expect(
150
- handleMemoryV3SetCore(
151
- { add: ["domain-a/does-not-exist"], write: true },
152
- { dataDir, workspaceDir },
153
- ),
154
- ).rejects.toBeInstanceOf(UnknownLeafError);
155
+ describe("memory v3 rebuild-index", () => {
156
+ test("sends memory_v3_rebuild_index with the default (no) timeout", async () => {
157
+ mockIpcResult = { ok: true, result: { invalidated: true } };
155
158
 
156
- // core.json must be untouched (still just the original entry).
157
- const core = JSON.parse(await readFile(join(dataDir, "core.json"), "utf8"));
158
- expect(core.alwaysOn).toEqual(["domain-a/topic-x"]);
159
- });
159
+ const { exitCode } = await runCommand(["memory", "v3", "rebuild-index"]);
160
160
 
161
- test("previews the always-on page count from frontmatter membership", async () => {
162
- // Adding topic-y to core: core = {topic-x, topic-y}. Membership comes from
163
- // page `leaves:` frontmatter — topic-x → {page-1, page-2}, topic-y →
164
- // {page-2} ⇒ unique always-on slugs {page-1, page-2} = 2. If the handler
165
- // read the stale assignments.json instead (page-1 → bogus-leaf, page-2 →
166
- // topic-z), topic-x/topic-y would have no members and this would be 0.
167
- const result = await handleMemoryV3SetCore(
168
- { add: ["domain-a/topic-y"] },
169
- { dataDir, workspaceDir },
170
- );
171
- expect(result.written).toBe(false);
172
- expect(result.nextCore).toEqual(["domain-a/topic-x", "domain-a/topic-y"]);
173
- expect(result.alwaysOnPageCount).toBe(2);
174
-
175
- // No write happened — core.json still holds only the original entry.
176
- const core = JSON.parse(await readFile(join(dataDir, "core.json"), "utf8"));
177
- expect(core.alwaysOn).toEqual(["domain-a/topic-x"]);
161
+ expect(exitCode).toBe(0);
162
+ expect(lastIpcCall!.method).toBe("memory_v3_rebuild_index");
163
+ expect(lastIpcCall!.params!.body).toEqual({});
164
+ // rebuild-index is fast; it relies on the default call timeout.
165
+ expect(lastIpcCall!.options).toBeUndefined();
178
166
  });
179
167
 
180
- test("writes core.json on --yes (write: true)", async () => {
181
- const result = await handleMemoryV3SetCore(
182
- { add: ["domain-b/topic-z"], write: true },
183
- { dataDir, workspaceDir },
184
- );
185
- expect(result.written).toBe(true);
186
- expect(result.nextCore).toEqual(["domain-a/topic-x", "domain-b/topic-z"]);
168
+ test("exits with code 1 on IPC failure", async () => {
169
+ mockIpcResult = { ok: false, error: "Daemon down" };
187
170
 
188
- const core = JSON.parse(await readFile(join(dataDir, "core.json"), "utf8"));
189
- expect(core.alwaysOn).toEqual(["domain-a/topic-x", "domain-b/topic-z"]);
190
- });
171
+ const { exitCode } = await runCommand(["memory", "v3", "rebuild-index"]);
191
172
 
192
- test("remove is idempotent for an absent entry", async () => {
193
- const result = await handleMemoryV3SetCore(
194
- { remove: ["domain-a/topic-y"], write: true },
195
- { dataDir, workspaceDir },
196
- );
197
- // topic-y was never in core; result is the original core unchanged.
198
- expect(result.nextCore).toEqual(["domain-a/topic-x"]);
173
+ expect(exitCode).toBe(1);
199
174
  });
200
175
  });
201
176
 
202
177
  // ---------------------------------------------------------------------------
203
- // health
178
+ // backfill-sections
204
179
  // ---------------------------------------------------------------------------
205
180
 
206
- describe("handleMemoryV3Health", () => {
207
- test("computes the slug universe from page frontmatter, not assignments.json", async () => {
208
- const result = await handleMemoryV3Health({ dataDir, workspaceDir });
209
- // domain-b/topic-z has a single member ⇒ a tiny leaf, so the report is
210
- // non-empty and renders the memory-v3 health header.
211
- expect(result.rendered).toContain("memory-v3 health:");
212
- expect(result.counts.tinyLeaves).toBeGreaterThanOrEqual(1);
213
- // Every page leaf ref (from frontmatter) resolves to a real leaf file ⇒ no
214
- // dangling refs. If assignments.json were the source, page-1's bogus-leaf
215
- // would dangle.
216
- expect(result.counts.danglingRefs).toBe(0);
217
- // The slug universe is the page-index pages (page-1..3), all assigned via
218
- // frontmatter, so nothing is unassigned.
219
- expect(result.counts.unassigned).toBe(0);
181
+ describe("memory v3 backfill-sections", () => {
182
+ test("sends memory_v3_backfill_sections with a long (30-min) IPC timeout", async () => {
183
+ mockIpcResult = {
184
+ ok: true,
185
+ result: { articles: 50, sections: 400, failures: 0 },
186
+ };
187
+
188
+ const { exitCode } = await runCommand([
189
+ "memory",
190
+ "v3",
191
+ "backfill-sections",
192
+ ]);
193
+
194
+ expect(exitCode).toBe(0);
195
+ expect(lastIpcCall!.method).toBe("memory_v3_backfill_sections");
196
+ expect(lastIpcCall!.params!.body).toEqual({});
197
+ // The one-time full-corpus embed is long-running, so the CLI must override
198
+ // the default 60s timeout with a generous ceiling (30 min).
199
+ expect(lastIpcCall!.options!.timeoutMs).toBe(30 * 60 * 1000);
220
200
  });
221
201
 
222
- test("flags a page frontmatter ref with no backing leaf as dangling", async () => {
223
- await writePage("page-4", ["domain-a/missing-leaf"]);
224
- invalidatePageIndex();
225
- const result = await handleMemoryV3Health({ dataDir, workspaceDir });
226
- expect(result.counts.danglingRefs).toBeGreaterThanOrEqual(1);
202
+ test("summarizes embedded sections on success", async () => {
203
+ mockIpcResult = {
204
+ ok: true,
205
+ result: { articles: 7, sections: 42, failures: 0 },
206
+ };
207
+
208
+ await runCommand(["memory", "v3", "backfill-sections"]);
209
+
210
+ expect(
211
+ logOutput.some((line) => line.includes("42") && line.includes("7")),
212
+ ).toBe(true);
227
213
  });
228
- });
229
214
 
230
- // ---------------------------------------------------------------------------
231
- // reconcile
232
- // ---------------------------------------------------------------------------
215
+ test("exits with code 1 on IPC failure", async () => {
216
+ mockIpcResult = { ok: false, error: "Memory v3 not enabled" };
217
+
218
+ const { exitCode } = await runCommand([
219
+ "memory",
220
+ "v3",
221
+ "backfill-sections",
222
+ ]);
233
223
 
234
- describe("handleMemoryV3Reconcile", () => {
235
- test("no-op reconcile against an unchanged tree returns empty diffs", async () => {
236
- // v1: prevLeaves is derived from the current tree, so diffing
237
- // current-vs-current yields no renames/deletes — reconcile runs as a
238
- // convergence/prune pass. This still drives the full reconcileTree path
239
- // (snapshot → apply → validate → invalidate).
240
- const result = await handleMemoryV3Reconcile({ dataDir, workspaceDir });
241
- expect(result.renames).toEqual([]);
242
- expect(result.deleted).toEqual([]);
243
- expect(result.prunedCore).toEqual([]);
224
+ expect(exitCode).toBe(1);
244
225
  });
245
226
  });
@@ -79,7 +79,7 @@ export function registerChannelVerificationSessionsCommand(
79
79
  "after",
80
80
  `
81
81
  Verification sessions are used to verify guardian bindings and trusted
82
- contacts across channels (telegram, phone, slack). Three flows exist:
82
+ contacts across channels (telegram, phone, slack, email). Three flows exist:
83
83
 
84
84
  1. Inbound challenge — the assistant generates a secret code and waits
85
85
  for the guardian to send it back on the channel. Used when the
@@ -107,10 +107,10 @@ Examples:
107
107
  cvs
108
108
  .command("create")
109
109
  .description("Create a new verification session")
110
- .option("--channel <channel>", "Channel type (telegram, phone, slack)")
110
+ .option("--channel <channel>", "Channel type (telegram, phone, slack, email)")
111
111
  .option(
112
112
  "--destination <destination>",
113
- "Destination address for outbound verification (handle, phone number, or user ID)",
113
+ "Destination address for outbound verification (handle, phone number, user ID, or email address)",
114
114
  )
115
115
  .option("--rebind", "Replace existing guardian binding")
116
116
  .option(
@@ -140,7 +140,7 @@ Routes between three creation modes based on the provided options:
140
140
 
141
141
  2. Outbound: --channel <ch> --destination <dest>
142
142
  Sends a verification code to the given destination. Supports telegram
143
- (handle or chat ID), phone (E.164 number), and slack (user ID).
143
+ (handle or chat ID), phone (E.164 number), slack (user ID), and email.
144
144
  Use --rebind to replace an existing guardian binding.
145
145
 
146
146
  3. Inbound: --channel <ch> (no --destination)
@@ -240,7 +240,7 @@ Examples:
240
240
  )
241
241
  .requiredOption(
242
242
  "--channel <channel>",
243
- "Channel type (telegram, phone, slack)",
243
+ "Channel type (telegram, phone, slack, email)",
244
244
  )
245
245
  .option(
246
246
  "--origin-conversation-id <id>",
@@ -290,7 +290,7 @@ Examples:
290
290
  .description("Cancel all active verification sessions for a channel")
291
291
  .requiredOption(
292
292
  "--channel <channel>",
293
- "Channel type (telegram, phone, slack)",
293
+ "Channel type (telegram, phone, slack, email)",
294
294
  )
295
295
  .addHelpText(
296
296
  "after",
@@ -507,7 +507,9 @@ describe("assistant db repair — conversation-backfill step", () => {
507
507
  expect(parsed.steps[1].result.data.recovered).toBe(0);
508
508
  expect(parsed.steps[1].result.data.skipped).toBe(1);
509
509
  expect(parsed.steps[1].result.status).toBe("ok");
510
- });
510
+ // Two full repair passes (each walks the DB via integrity-check) exceed
511
+ // bun's 5s per-test default, so raise the ceiling to keep CI stable.
512
+ }, 30_000);
511
513
 
512
514
  test("reports nothing-to-backfill on an empty conversations dir", async () => {
513
515
  await initSchema();