@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -13,7 +13,8 @@ mock.module("../../util/logger.js", () => ({
13
13
  }),
14
14
  }));
15
15
 
16
- // Stub config loader — returns a config with memory enabled by default
16
+ // Stub config loader — return a config with memory.v2.enabled=false so the
17
+ // v1 paths under test stay active.
17
18
  mock.module("../../config/loader.js", () => ({
18
19
  loadConfig: () => mockConfig,
19
20
  getConfig: () => mockConfig,
@@ -21,7 +22,7 @@ mock.module("../../config/loader.js", () => ({
21
22
  }));
22
23
 
23
24
  // ── Controllable mocks for semantic search ─────────────────────────────
24
- const mockConfig: unknown = {};
25
+ const mockConfig: unknown = { memory: { v2: { enabled: false } } };
25
26
 
26
27
  let mockBackendStatus: {
27
28
  enabled: boolean;
@@ -68,11 +69,7 @@ import { eq } from "drizzle-orm";
68
69
  import { getDb } from "../../memory/db-connection.js";
69
70
  import { initializeDb } from "../../memory/db-init.js";
70
71
  import { memoryGraphNodes, memoryJobs } from "../../memory/schema.js";
71
- import {
72
- BadRequestError,
73
- ConflictError,
74
- NotFoundError,
75
- } from "./errors.js";
72
+ import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
76
73
  import { ROUTES } from "./memory-item-routes.js";
77
74
  import type { RouteDefinition } from "./types.js";
78
75
 
@@ -269,7 +266,9 @@ describe("Memory Item Routes", () => {
269
266
  content: "s2\nst2",
270
267
  });
271
268
 
272
- const res = await callHandler(route, { queryParams: { kind: "semantic" } });
269
+ const res = await callHandler(route, {
270
+ queryParams: { kind: "semantic" },
271
+ });
273
272
  const body = (await res.json()) as {
274
273
  items: Array<{ id: string }>;
275
274
  total: number;
@@ -319,7 +318,9 @@ describe("Memory Item Routes", () => {
319
318
  lastAccessed: 3000,
320
319
  });
321
320
 
322
- const res = await callHandler(route, { queryParams: { limit: "1", offset: "1" } });
321
+ const res = await callHandler(route, {
322
+ queryParams: { limit: "1", offset: "1" },
323
+ });
323
324
  const body = (await res.json()) as {
324
325
  items: Array<{ id: string }>;
325
326
  total: number;
@@ -344,7 +345,9 @@ describe("Memory Item Routes", () => {
344
345
  created: 1000,
345
346
  });
346
347
 
347
- const res = await callHandler(route, { queryParams: { sort: "firstSeenAt", order: "asc" } });
348
+ const res = await callHandler(route, {
349
+ queryParams: { sort: "firstSeenAt", order: "asc" },
350
+ });
348
351
  const body = (await res.json()) as {
349
352
  items: Array<{ id: string }>;
350
353
  };
@@ -366,7 +369,9 @@ describe("Memory Item Routes", () => {
366
369
  significance: 0.9,
367
370
  });
368
371
 
369
- const res = await callHandler(route, { queryParams: { sort: "importance", order: "desc" } });
372
+ const res = await callHandler(route, {
373
+ queryParams: { sort: "importance", order: "desc" },
374
+ });
370
375
  const body = (await res.json()) as {
371
376
  items: Array<{ id: string }>;
372
377
  };
@@ -418,7 +423,9 @@ describe("Memory Item Routes", () => {
418
423
  },
419
424
  ];
420
425
 
421
- const res = await callHandler(route, { queryParams: { search: "alice" } });
426
+ const res = await callHandler(route, {
427
+ queryParams: { search: "alice" },
428
+ });
422
429
  const body = (await res.json()) as {
423
430
  items: Array<{ id: string }>;
424
431
  total: number;
@@ -503,7 +510,9 @@ describe("Memory Item Routes", () => {
503
510
  ];
504
511
 
505
512
  // Request page 2 (offset=1, limit=1)
506
- const res = await callHandler(route, { queryParams: { search: "item", limit: "1", offset: "1" } });
513
+ const res = await callHandler(route, {
514
+ queryParams: { search: "item", limit: "1", offset: "1" },
515
+ });
507
516
  const body = (await res.json()) as {
508
517
  items: Array<{ id: string }>;
509
518
  total: number;
@@ -563,7 +572,10 @@ describe("Memory Item Routes", () => {
563
572
  content: "dark mode\nPrefers dark mode",
564
573
  });
565
574
 
566
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "i1" } });
575
+ const res = await callHandler(route, {
576
+ queryParams: {},
577
+ pathParams: { id: "i1" },
578
+ });
567
579
  expect(res.status).toBe(200);
568
580
  const body = (await res.json()) as {
569
581
  item: { id: string; subject: string };
@@ -573,7 +585,10 @@ describe("Memory Item Routes", () => {
573
585
  });
574
586
 
575
587
  test("returns 404 for non-existent item", async () => {
576
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "nonexistent" } });
588
+ const res = await callHandler(route, {
589
+ queryParams: {},
590
+ pathParams: { id: "nonexistent" },
591
+ });
577
592
  expect(res.status).toBe(404);
578
593
  });
579
594
 
@@ -584,7 +599,10 @@ describe("Memory Item Routes", () => {
584
599
  content: "some content\nsome statement",
585
600
  });
586
601
 
587
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "i1" } });
602
+ const res = await callHandler(route, {
603
+ queryParams: {},
604
+ pathParams: { id: "i1" },
605
+ });
588
606
  const body = (await res.json()) as {
589
607
  item: { supersedes: unknown; supersededBy: unknown };
590
608
  };
@@ -862,7 +880,9 @@ describe("Memory Item Routes", () => {
862
880
  });
863
881
 
864
882
  test("returns 404 for non-existent item", async () => {
865
- const res = await callHandler(route, { pathParams: { id: "nonexistent" } });
883
+ const res = await callHandler(route, {
884
+ pathParams: { id: "nonexistent" },
885
+ });
866
886
  expect(res.status).toBe(404);
867
887
  });
868
888
 
@@ -25,7 +25,6 @@ import {
25
25
  import { z } from "zod";
26
26
 
27
27
  import { getConfig } from "../../config/loader.js";
28
- import { isMemoryV2ReadActive } from "../../memory/context-search/sources/memory-v2.js";
29
28
  import { getDb } from "../../memory/db-connection.js";
30
29
  import {
31
30
  embedWithBackend,
@@ -176,11 +175,11 @@ async function searchNodesSemantic(
176
175
  ): Promise<{ ids: string[]; total: number } | null> {
177
176
  try {
178
177
  const config = getConfig();
179
- // v2 owns the read path when both gates are on. Fall back to SQL search
180
- // (the caller's `null` branch) instead of querying the v1 collection,
181
- // which is in active retirement and a corrupted sparse segment can
182
- // OOM-crash the shared Qdrant process.
183
- if (isMemoryV2ReadActive(config)) return null;
178
+ // v2 owns the read path when enabled. Fall back to SQL search (the
179
+ // caller's `null` branch) instead of querying the v1 collection, which
180
+ // is in active retirement and a corrupted sparse segment can OOM-crash
181
+ // the shared Qdrant process.
182
+ if (config.memory.v2.enabled) return null;
184
183
  const backendStatus = await getMemoryBackendStatus(config);
185
184
  if (!backendStatus.provider) return null;
186
185
 
@@ -4,9 +4,11 @@
4
4
  * Migrated from `ipc/routes/memory-v2-backfill.ts` and
5
5
  * `ipc/routes/memory-v2-validate.ts` into the shared ROUTES array.
6
6
  */
7
+ import { stat } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+
7
10
  import { z } from "zod";
8
11
 
9
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
10
12
  import { loadConfig } from "../../config/loader.js";
11
13
  import {
12
14
  applyCorrectionIfCalibrated,
@@ -22,12 +24,17 @@ import {
22
24
  enqueueMemoryJob,
23
25
  type MemoryJobType,
24
26
  } from "../../memory/jobs-store.js";
27
+ import {
28
+ type ConceptFrequencyResponse,
29
+ getConceptFrequencySummary,
30
+ } from "../../memory/memory-v2-concept-frequency.js";
25
31
  import {
26
32
  getEdgeIndex,
27
33
  totalEdgeCount,
28
34
  validateEdgeTargets,
29
35
  } from "../../memory/v2/edge-index.js";
30
36
  import {
37
+ getConceptsDir,
31
38
  listPages,
32
39
  readPage,
33
40
  renderPageContent,
@@ -43,11 +50,37 @@ import {
43
50
  getConceptPageCorpusStats,
44
51
  rebuildConceptPageCorpusStats,
45
52
  } from "../../memory/v2/sparse-bm25.js";
53
+ import { getLogger } from "../../util/logger.js";
46
54
  import { getWorkspaceDir } from "../../util/platform.js";
47
55
  import { RouteError } from "./errors.js";
48
56
  import type { RouteDefinition } from "./types.js";
49
57
  import type { RouteHandlerArgs } from "./types.js";
50
58
 
59
+ const log = getLogger("memory-v2-routes");
60
+
61
+ /**
62
+ * Wire-format error code emitted when v2 routes reject a request because
63
+ * `memory.v2.enabled` is false. Exported so tests and the macOS client can
64
+ * reference the same string without drift.
65
+ */
66
+ export const MEMORY_V2_DISABLED_CODE = "MEMORY_V2_DISABLED";
67
+
68
+ /**
69
+ * Reject the request when memory v2 is not active. Returning 409 (rather
70
+ * than serving a partial response) keeps clients honest — the desktop
71
+ * Memories panel reads this code to render an explicit "disabled in
72
+ * config" empty state.
73
+ */
74
+ function requireMemoryV2Enabled(): void {
75
+ if (!loadConfig().memory.v2.enabled) {
76
+ throw new RouteError(
77
+ "Memory v2 is not enabled — set memory.v2.enabled to true to use this command.",
78
+ MEMORY_V2_DISABLED_CODE,
79
+ 409,
80
+ );
81
+ }
82
+ }
83
+
51
84
  // ── Backfill ────────────────────────────────────────────────────────────
52
85
 
53
86
  const MemoryV2BackfillParams = z
@@ -72,6 +105,7 @@ const OP_TO_JOB_TYPE: Record<MemoryV2BackfillOp, MemoryJobType> = {
72
105
  async function handleBackfill({
73
106
  body = {},
74
107
  }: RouteHandlerArgs): Promise<MemoryV2BackfillResult> {
108
+ requireMemoryV2Enabled();
75
109
  const { op, force } = MemoryV2BackfillParams.parse(body);
76
110
  const payload: Record<string, unknown> =
77
111
  op === "migrate" && force === true ? { force: true } : {};
@@ -98,6 +132,7 @@ export type MemoryV2ValidateResult = {
98
132
  async function handleValidate({
99
133
  body = {},
100
134
  }: RouteHandlerArgs): Promise<MemoryV2ValidateResult> {
135
+ requireMemoryV2Enabled();
101
136
  MemoryV2ValidateParams.parse(body);
102
137
 
103
138
  const workspaceDir = getWorkspaceDir();
@@ -154,6 +189,7 @@ export type MemoryV2GetConceptPageResult = {
154
189
  async function handleGetConceptPage({
155
190
  body = {},
156
191
  }: RouteHandlerArgs): Promise<MemoryV2GetConceptPageResult> {
192
+ requireMemoryV2Enabled();
157
193
  const { slug } = MemoryV2GetConceptPageParams.parse(body);
158
194
  const workspaceDir = getWorkspaceDir();
159
195
  let page;
@@ -176,6 +212,59 @@ async function handleGetConceptPage({
176
212
  return { slug, rendered: renderPageContent(page) };
177
213
  }
178
214
 
215
+ // ── List concept pages ──────────────────────────────────────────────────
216
+
217
+ const MemoryV2ListConceptPagesParams = z.object({}).strict();
218
+
219
+ export type MemoryV2ListConceptPagesResult = {
220
+ pages: Array<{
221
+ slug: string;
222
+ bodyBytes: number;
223
+ edgeCount: number;
224
+ updatedAtMs: number;
225
+ }>;
226
+ };
227
+
228
+ async function handleListConceptPages({
229
+ body = {},
230
+ }: RouteHandlerArgs): Promise<MemoryV2ListConceptPagesResult> {
231
+ requireMemoryV2Enabled();
232
+ MemoryV2ListConceptPagesParams.parse(body);
233
+
234
+ const workspaceDir = getWorkspaceDir();
235
+ const conceptsDir = getConceptsDir(workspaceDir);
236
+ const slugs = await listPages(workspaceDir);
237
+
238
+ const settled = await Promise.all(
239
+ slugs.map(async (slug) => {
240
+ try {
241
+ const page = await readPage(workspaceDir, slug);
242
+ if (!page) return null;
243
+ const stats = await stat(join(conceptsDir, `${slug}.md`));
244
+ return {
245
+ slug,
246
+ bodyBytes: Buffer.byteLength(page.body, "utf8"),
247
+ edgeCount: page.frontmatter.edges.length,
248
+ updatedAtMs: Math.floor(stats.mtimeMs),
249
+ };
250
+ } catch (err) {
251
+ // A single corrupt page (bad YAML, schema mismatch, etc.) shouldn't
252
+ // poison the whole listing — the validate route is the place to
253
+ // surface those; this one is read-only and best-effort.
254
+ log.warn(
255
+ `Skipping concept page '${slug}' in list-concept-pages: ${err instanceof Error ? err.message : String(err)}`,
256
+ );
257
+ return null;
258
+ }
259
+ }),
260
+ );
261
+ const pages = settled.filter(
262
+ (p): p is MemoryV2ListConceptPagesResult["pages"][number] => p !== null,
263
+ );
264
+
265
+ return { pages };
266
+ }
267
+
179
268
  // ── Rebuild BM25 corpus stats ───────────────────────────────────────────
180
269
 
181
270
  const MemoryV2RebuildCorpusStatsParams = z.object({}).strict();
@@ -190,6 +279,7 @@ export interface MemoryV2RebuildCorpusStatsResult {
190
279
  async function handleRebuildCorpusStats({
191
280
  body = {},
192
281
  }: RouteHandlerArgs): Promise<MemoryV2RebuildCorpusStatsResult> {
282
+ requireMemoryV2Enabled();
193
283
  MemoryV2RebuildCorpusStatsParams.parse(body);
194
284
  const workspaceDir = getWorkspaceDir();
195
285
  await rebuildConceptPageCorpusStats(workspaceDir);
@@ -221,23 +311,9 @@ export type MemoryV2ReembedSkillsResult = {
221
311
  async function handleReembedSkills({
222
312
  body = {},
223
313
  }: RouteHandlerArgs): Promise<MemoryV2ReembedSkillsResult> {
314
+ requireMemoryV2Enabled();
224
315
  MemoryV2ReembedSkillsParams.parse(body);
225
316
 
226
- // Gate the route on both the feature flag and the per-workspace config
227
- // toggle so the v2 skill collection never gets re-seeded against a
228
- // workspace whose v2 subsystem is intentionally off.
229
- const config = loadConfig();
230
- if (
231
- !isAssistantFeatureFlagEnabled("memory-v2-enabled", config) ||
232
- !config.memory.v2.enabled
233
- ) {
234
- throw new RouteError(
235
- "Memory v2 is not enabled — flip both the memory-v2-enabled feature flag and memory.v2.enabled to use this command.",
236
- "MEMORY_V2_DISABLED",
237
- 409,
238
- );
239
- }
240
-
241
317
  // Unlike the queued backfill jobs above, this is a CLI-driven sync
242
318
  // request: the operator wants the cache replaced before the next prompt
243
319
  // assembly, so we await the seed inline rather than enqueueing it.
@@ -412,6 +488,7 @@ async function scoreChannel(
412
488
  async function handleExplainSimilarity({
413
489
  body = {},
414
490
  }: RouteHandlerArgs): Promise<MemoryV2ExplainSimilarityResult> {
491
+ requireMemoryV2Enabled();
415
492
  const params = MemoryV2ExplainSimilarityParams.parse(body);
416
493
  const config = loadConfig();
417
494
  const { dense_weight: denseWeight, sparse_weight: sparseWeight } =
@@ -459,6 +536,25 @@ async function handleExplainSimilarity({
459
536
  };
460
537
  }
461
538
 
539
+ // ── Concept injection frequency (debug-only) ────────────────────────────
540
+
541
+ const MemoryV2ConceptFrequencyParams = z
542
+ .object({
543
+ conversationId: z.string().min(1).optional(),
544
+ sinceMs: z.number().int().nonnegative().optional(),
545
+ })
546
+ .strict();
547
+
548
+ async function handleConceptFrequency({
549
+ body = {},
550
+ }: RouteHandlerArgs): Promise<ConceptFrequencyResponse> {
551
+ requireMemoryV2Enabled();
552
+ const { conversationId, sinceMs } =
553
+ MemoryV2ConceptFrequencyParams.parse(body);
554
+ const workspaceDir = getWorkspaceDir();
555
+ return getConceptFrequencySummary(workspaceDir, { conversationId, sinceMs });
556
+ }
557
+
462
558
  // ── Fit anisotropy calibration ──────────────────────────────────────────
463
559
 
464
560
  const MemoryV2FitAnisotropyParams = z
@@ -495,6 +591,7 @@ export interface MemoryV2FitAnisotropyResult {
495
591
  async function handleFitAnisotropy({
496
592
  body = {},
497
593
  }: RouteHandlerArgs): Promise<MemoryV2FitAnisotropyResult> {
594
+ requireMemoryV2Enabled();
498
595
  const { k, sample } = MemoryV2FitAnisotropyParams.parse(body);
499
596
  const config = loadConfig();
500
597
 
@@ -581,6 +678,17 @@ export const ROUTES: RouteDefinition[] = [
581
678
  tags: ["memory"],
582
679
  requestBody: MemoryV2GetConceptPageParams,
583
680
  },
681
+ {
682
+ operationId: "memory_v2_list_concept_pages",
683
+ method: "POST",
684
+ endpoint: "memory/v2/list-concept-pages",
685
+ handler: handleListConceptPages,
686
+ summary: "List all memory v2 concept pages with metadata",
687
+ description:
688
+ "Returns slugs, body sizes, edge counts, and last-modified timestamps for every concept page on disk. Read-only; used by the desktop About → Memories surface to render a browse-able list.",
689
+ tags: ["memory"],
690
+ requestBody: MemoryV2ListConceptPagesParams,
691
+ },
584
692
  {
585
693
  operationId: "memory_v2_reembed_skills",
586
694
  method: "POST",
@@ -588,7 +696,7 @@ export const ROUTES: RouteDefinition[] = [
588
696
  handler: handleReembedSkills,
589
697
  summary: "Re-seed v2 skill entries from the current skill catalog",
590
698
  description:
591
- "Synchronously re-runs seedV2SkillEntries against the current skill catalog. Gated on memory-v2-enabled flag and config.memory.v2.enabled.",
699
+ "Synchronously re-runs seedV2SkillEntries against the current skill catalog. Gated on config.memory.v2.enabled.",
592
700
  tags: ["memory"],
593
701
  requestBody: MemoryV2ReembedSkillsParams,
594
702
  },
@@ -614,6 +722,17 @@ export const ROUTES: RouteDefinition[] = [
614
722
  tags: ["memory"],
615
723
  requestBody: MemoryV2RebuildCorpusStatsParams,
616
724
  },
725
+ {
726
+ operationId: "memory_v2_concept_frequency",
727
+ method: "POST",
728
+ endpoint: "memory/v2/concept-frequency",
729
+ handler: handleConceptFrequency,
730
+ summary: "Aggregate per-concept injection frequency from activation logs",
731
+ description:
732
+ "Debug-only. Aggregates the existing memory_v2_activation_logs table by (slug, status) and cross-references on-disk concept pages so an operator can see which concepts get injected often, which get scored but rejected, and which on-disk pages never even surface as candidates. Optional filters: conversationId narrows to a single conversation; sinceMs restricts to logs created at-or-after the given epoch ms timestamp.",
733
+ tags: ["memory"],
734
+ requestBody: MemoryV2ConceptFrequencyParams,
735
+ },
617
736
  {
618
737
  operationId: "memory_v2_fit_anisotropy",
619
738
  method: "POST",
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Internal routes for daemon-owned OAuth connect flows (CLI gateway transport fix).
3
+ *
4
+ * POST internal/oauth/connect/start — starts the flow in the daemon, returns auth URL
5
+ * GET internal/oauth/connect/status/:state — polls current flow status
6
+ */
7
+
8
+ import { z } from "zod";
9
+
10
+ import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
11
+ import {
12
+ getOAuthConnectState,
13
+ setOAuthConnectComplete,
14
+ setOAuthConnectError,
15
+ setOAuthConnectPending,
16
+ } from "../../oauth/oauth-connect-state.js";
17
+ import { getLogger } from "../../util/logger.js";
18
+ import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
19
+ import type { RouteDefinition } from "./types.js";
20
+
21
+ const log = getLogger("oauth-connect-routes");
22
+
23
+ async function handleOAuthConnectStart({
24
+ body,
25
+ }: {
26
+ body?: Record<string, unknown>;
27
+ }): Promise<{ auth_url: string; state: string }> {
28
+ const {
29
+ service,
30
+ clientId,
31
+ clientSecret,
32
+ callbackTransport,
33
+ requestedScopes,
34
+ } = (body ?? {}) as {
35
+ service: string;
36
+ clientId: string;
37
+ clientSecret?: string;
38
+ callbackTransport?: string;
39
+ requestedScopes?: string[];
40
+ };
41
+
42
+ if (!service) throw new BadRequestError("service is required");
43
+ if (!clientId) throw new BadRequestError("clientId is required");
44
+ if (callbackTransport !== "loopback" && callbackTransport !== "gateway") {
45
+ throw new BadRequestError(
46
+ 'callbackTransport must be "loopback" or "gateway"',
47
+ );
48
+ }
49
+
50
+ // Capture resolvedState separately so the onDeferredComplete closure can
51
+ // reference it without a reference-before-assignment risk (the fire-and-forget
52
+ // tail only fires after the await resolves, by which point resolvedState is set).
53
+ // eslint-disable-next-line prefer-const -- intentional forward-declared binding
54
+ let resolvedState: string | undefined;
55
+
56
+ let result: Awaited<ReturnType<typeof orchestrateOAuthConnect>>;
57
+ try {
58
+ result = await orchestrateOAuthConnect({
59
+ service,
60
+ clientId,
61
+ clientSecret,
62
+ callbackTransport,
63
+ ...(requestedScopes ? { requestedScopes } : {}),
64
+ isInteractive: false,
65
+ onDeferredComplete: (r) => {
66
+ if (!resolvedState) return;
67
+ if (r.success) {
68
+ setOAuthConnectComplete(resolvedState, r.service, r.accountInfo, r.grantedScopes);
69
+ } else {
70
+ setOAuthConnectError(resolvedState, r.service, r.error ?? "OAuth connect failed");
71
+ }
72
+ },
73
+ });
74
+ } catch (err) {
75
+ throw new InternalError(err instanceof Error ? err.message : String(err));
76
+ }
77
+
78
+ if (!result.success) {
79
+ throw new InternalError(result.error);
80
+ }
81
+ if (!result.deferred) {
82
+ throw new InternalError("Orchestrator returned non-deferred result");
83
+ }
84
+
85
+ resolvedState = result.state;
86
+ setOAuthConnectPending(result.state, service);
87
+ log.info({ state: result.state, service }, "oauth connect flow started");
88
+ return { auth_url: result.authorizeUrl, state: result.state };
89
+ }
90
+
91
+ function handleOAuthConnectStatus({
92
+ pathParams,
93
+ }: {
94
+ pathParams?: Record<string, string>;
95
+ }): {
96
+ status: "pending" | "complete" | "error";
97
+ service: string;
98
+ account_info?: string;
99
+ granted_scopes?: string[];
100
+ error?: string;
101
+ } {
102
+ const { state } = pathParams as { state: string };
103
+ const flowState = getOAuthConnectState(state);
104
+
105
+ if (flowState === null) {
106
+ throw new NotFoundError(`No active OAuth connect flow for state "${state}"`);
107
+ }
108
+
109
+ if (flowState.status === "pending") return { status: "pending", service: flowState.service };
110
+ if (flowState.status === "complete") {
111
+ return {
112
+ status: "complete",
113
+ service: flowState.service,
114
+ ...(flowState.accountInfo ? { account_info: flowState.accountInfo } : {}),
115
+ ...(flowState.grantedScopes ? { granted_scopes: flowState.grantedScopes } : {}),
116
+ };
117
+ }
118
+ return { status: "error", service: flowState.service, error: flowState.error };
119
+ }
120
+
121
+ export const ROUTES: RouteDefinition[] = [
122
+ {
123
+ operationId: "internal_oauth_connect_start",
124
+ endpoint: "internal/oauth/connect/start",
125
+ method: "POST",
126
+ summary: "Start daemon-owned OAuth connect flow",
127
+ description:
128
+ "Starts an OAuth connect flow in the daemon and returns the authorization URL for the CLI to open in the browser.",
129
+ tags: ["internal"],
130
+ requestBody: z.object({
131
+ service: z.string(),
132
+ clientId: z.string(),
133
+ clientSecret: z.string().optional(),
134
+ callbackTransport: z.enum(["loopback", "gateway"]),
135
+ requestedScopes: z.array(z.string()).optional(),
136
+ }),
137
+ handler: handleOAuthConnectStart,
138
+ },
139
+ {
140
+ operationId: "internal_oauth_connect_status",
141
+ endpoint: "internal/oauth/connect/status/:state",
142
+ method: "GET",
143
+ summary: "Poll daemon OAuth connect flow status",
144
+ description:
145
+ "Returns the current status of an in-flight daemon-owned OAuth connect flow (pending/complete/error).",
146
+ tags: ["internal"],
147
+ pathParams: [{ name: "state" }],
148
+ additionalResponses: {
149
+ "404": { description: "No active OAuth connect flow for the given state token" },
150
+ },
151
+ handler: handleOAuthConnectStatus,
152
+ },
153
+ ];
@@ -79,7 +79,7 @@ export function normalizeTelegramDestination(destination: string): string {
79
79
  // Input / output types
80
80
  // ---------------------------------------------------------------------------
81
81
 
82
- export interface StartOutboundParams {
82
+ interface StartOutboundParams {
83
83
  channel: ChannelId;
84
84
  destination?: string;
85
85
  rebind?: boolean;
@@ -87,13 +87,13 @@ export interface StartOutboundParams {
87
87
  originConversationId?: string;
88
88
  }
89
89
 
90
- export interface ResendOutboundParams {
90
+ interface ResendOutboundParams {
91
91
  channel: ChannelId;
92
92
  /** Origin conversation ID so completion/failure pointers can route back on resend. */
93
93
  originConversationId?: string;
94
94
  }
95
95
 
96
- export interface CancelOutboundParams {
96
+ interface CancelOutboundParams {
97
97
  channel: ChannelId;
98
98
  }
99
99
 
@@ -102,7 +102,7 @@ export interface CancelOutboundParams {
102
102
  * Maps 1:1 with the fields in ChannelVerificationSessionResponse minus the
103
103
  * `type` discriminant.
104
104
  */
105
- export interface OutboundActionResult {
105
+ interface OutboundActionResult {
106
106
  success: boolean;
107
107
  error?: string;
108
108
  message?: string;
@@ -38,21 +38,53 @@ export async function runScript(
38
38
  env: buildSanitizedEnv(),
39
39
  });
40
40
 
41
- // Race process completion against a timeout
41
+ // Start consuming streams immediately so buffered output is available even on timeout.
42
+ // When the process is killed the pipe fds close and these promises resolve on their own.
43
+ const stdoutPromise = new Response(proc.stdout).text();
44
+ const stderrPromise = new Response(proc.stderr).text();
45
+
46
+ let timedOut = false;
47
+
42
48
  const timeoutPromise = new Promise<never>((_, reject) => {
43
49
  const timer = setTimeout(() => {
50
+ timedOut = true;
44
51
  proc.kill("SIGKILL");
45
52
  reject(new Error(`Script timed out after ${timeoutMs}ms`));
46
53
  }, timeoutMs);
47
54
  timer.unref();
48
- // Clean up timer if process finishes first
49
55
  proc.exited.then(() => clearTimeout(timer));
50
56
  });
51
57
 
52
- const exitCode = await Promise.race([proc.exited, timeoutPromise]);
58
+ /** How long to wait for pipes to drain after SIGKILL before giving up. */
59
+ const DRAIN_TIMEOUT_MS = 5_000;
60
+
61
+ let exitCode: number;
62
+ try {
63
+ exitCode = await Promise.race([proc.exited, timeoutPromise]);
64
+ } catch (err) {
65
+ if (!timedOut) throw err;
66
+ // Collect whatever the process wrote before it was killed.
67
+ // Race each stream against a short drain window — if a background child
68
+ // process inherited the pipe fd, the stream would otherwise never reach
69
+ // EOF and block the scheduler tick indefinitely.
70
+ const empty = (ms: number): Promise<string> =>
71
+ new Promise((resolve) => setTimeout(() => resolve(""), ms));
72
+ const [stdoutStr, stderrStr] = await Promise.all([
73
+ Promise.race([stdoutPromise, empty(DRAIN_TIMEOUT_MS)]),
74
+ Promise.race([stderrPromise, empty(DRAIN_TIMEOUT_MS)]),
75
+ ]);
76
+ const stdout = truncate(stdoutStr);
77
+ const timeoutMsg = `Script timed out after ${timeoutMs}ms`;
78
+ const stderr = truncate(stderrStr ? `${timeoutMsg}\n${stderrStr}` : timeoutMsg);
79
+ log.info(
80
+ { command, timedOut: true, stdoutLen: stdout.length },
81
+ "Script timed out",
82
+ );
83
+ return { exitCode: 124, stdout, stderr };
84
+ }
53
85
 
54
- const stdout = truncate(await new Response(proc.stdout).text());
55
- const stderr = truncate(await new Response(proc.stderr).text());
86
+ const stdout = truncate(await stdoutPromise);
87
+ const stderr = truncate(await stderrPromise);
56
88
 
57
89
  log.info(
58
90
  { command, exitCode, stdoutLen: stdout.length, stderrLen: stderr.length },