@vellumai/assistant 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -15,6 +15,8 @@ import {
15
15
  createApprovalConversationGenerator,
16
16
  createApprovalCopyGenerator,
17
17
  } from "../../daemon/approval-generators.js";
18
+ import { getDiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
19
+ import { classifyDiskPressureTurnPolicy } from "../../daemon/disk-pressure-policy.js";
18
20
  import { processMessage } from "../../daemon/process-message.js";
19
21
  import type { TrustContext } from "../../daemon/trust-context.js";
20
22
  import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
@@ -35,6 +37,7 @@ import {
35
37
  getPendingVerificationReply,
36
38
  } from "../../memory/delivery-channels.js";
37
39
  import {
40
+ clearPayload,
38
41
  findMessageBySourceId,
39
42
  linkMessage,
40
43
  recordInbound,
@@ -75,6 +78,8 @@ import { tryTranscribeAudioAttachments } from "./inbound-stages/transcribe-audio
75
78
  import type { RouteHandlerArgs } from "./types.js";
76
79
 
77
80
  const log = getLogger("runtime-http");
81
+ const DISK_PRESSURE_REMOTE_BLOCK_REPLY =
82
+ "Storage is critically low, so remote messages are ignored until the guardian frees enough space. Please try again later.";
78
83
 
79
84
  // Delete-lookup retry configuration. Delete webhooks can race ahead of
80
85
  // the inbound handler's `linkMessage` call when the original message's
@@ -524,6 +529,78 @@ export async function handleChannelInbound({
524
529
  });
525
530
  }
526
531
 
532
+ // ── Actor role resolution ──
533
+ // Uses shared channel-agnostic resolution so all ingress paths classify
534
+ // guardian vs non-guardian actors the same way.
535
+ const trustCtx: TrustContext = resolveTrustContext({
536
+ assistantId: canonicalAssistantId,
537
+ sourceChannel,
538
+ conversationExternalId,
539
+ actorExternalId: rawSenderId,
540
+ actorUsername: body.actorUsername,
541
+ actorDisplayName: body.actorDisplayName,
542
+ });
543
+
544
+ const diskPressureDecision = classifyDiskPressureTurnPolicy(
545
+ getDiskPressureStatus(),
546
+ {
547
+ sourceChannel,
548
+ sourceInterface,
549
+ trustContext: {
550
+ sourceChannel: trustCtx.sourceChannel,
551
+ trustClass: trustCtx.trustClass,
552
+ },
553
+ },
554
+ );
555
+ if (diskPressureDecision.action === "block") {
556
+ if (!result.duplicate) {
557
+ clearPayload(result.eventId);
558
+ markProcessed(result.eventId);
559
+ }
560
+ log.info(
561
+ {
562
+ conversationId: result.conversationId,
563
+ eventId: result.eventId,
564
+ duplicate: result.duplicate,
565
+ reason: diskPressureDecision.reason,
566
+ trustClass: trustCtx.trustClass,
567
+ },
568
+ "Channel inbound blocked during disk pressure cleanup mode",
569
+ );
570
+
571
+ if (replyCallbackUrl && !result.duplicate) {
572
+ const replyPayload: Parameters<typeof deliverChannelReply>[1] = {
573
+ chatId: conversationExternalId,
574
+ text: DISK_PRESSURE_REMOTE_BLOCK_REPLY,
575
+ assistantId: canonicalAssistantId,
576
+ };
577
+ if (sourceChannel === "slack" && (canonicalSenderId ?? rawSenderId)) {
578
+ replyPayload.ephemeral = true;
579
+ replyPayload.user = (canonicalSenderId ?? rawSenderId)!;
580
+ }
581
+ try {
582
+ await deliverChannelReply(replyCallbackUrl, replyPayload);
583
+ } catch (err) {
584
+ log.warn(
585
+ {
586
+ err,
587
+ conversationId: result.conversationId,
588
+ eventId: result.eventId,
589
+ },
590
+ "Failed to deliver disk pressure block reply",
591
+ );
592
+ }
593
+ }
594
+
595
+ return {
596
+ accepted: true,
597
+ duplicate: result.duplicate,
598
+ eventId: result.eventId,
599
+ diskPressure: "blocked",
600
+ reason: diskPressureDecision.reason,
601
+ };
602
+ }
603
+
527
604
  // ── Slack reaction handling ──
528
605
  // Reactions arrive as regular `SlackInboundEvent`s with `callbackData`
529
606
  // prefixed `reaction:` (added) or `reaction_removed:` (removed).
@@ -735,18 +812,6 @@ export async function handleChannelInbound({
735
812
  // which handles request code matching, callback parsing, and NL classification
736
813
  // against canonical_guardian_requests.
737
814
 
738
- // ── Actor role resolution ──
739
- // Uses shared channel-agnostic resolution so all ingress paths classify
740
- // guardian vs non-guardian actors the same way.
741
- const trustCtx: TrustContext = resolveTrustContext({
742
- assistantId: canonicalAssistantId,
743
- sourceChannel,
744
- conversationExternalId,
745
- actorExternalId: rawSenderId,
746
- actorUsername: body.actorUsername,
747
- actorDisplayName: body.actorDisplayName,
748
- });
749
-
750
815
  // ── Canonical guardian reply router ──
751
816
  const guardianReplyResult = await handleGuardianReplyIntercept({
752
817
  isDuplicate: result.duplicate,
@@ -174,6 +174,9 @@ export async function handleGuardianReplyIntercept(
174
174
  eventId,
175
175
  canonicalRouter: routerResult.type,
176
176
  requestId: routerResult.requestId,
177
+ ...(routerResult.activatedContact
178
+ ? { activatedContact: routerResult.activatedContact }
179
+ : {}),
177
180
  }),
178
181
  skipApprovalInterception: false,
179
182
  };
@@ -37,9 +37,11 @@ import { ROUTES as CONVERSATION_QUERY_ROUTES } from "./conversation-query-routes
37
37
  import { ROUTES as CONVERSATION_MESSAGE_ROUTES } from "./conversation-routes.js";
38
38
  import { ROUTES as CONVERSATION_STARTER_ROUTES } from "./conversation-starter-routes.js";
39
39
  import { ROUTES as CREDENTIAL_PROMPT_ROUTES } from "./credential-prompt-routes.js";
40
+ import { ROUTES as DEBUG_BASH_ROUTES } from "./debug-bash-routes.js";
40
41
  import { ROUTES as DEBUG_ROUTES } from "./debug-routes.js";
41
42
  import { ROUTES as DEFER_ROUTES } from "./defer-routes.js";
42
43
  import { ROUTES as DIAGNOSTICS_ROUTES } from "./diagnostics-routes.js";
44
+ import { ROUTES as DISK_PRESSURE_ROUTES } from "./disk-pressure-routes.js";
43
45
  import { ROUTES as DOCUMENT_ROUTES } from "./documents-routes.js";
44
46
  import { ROUTES as EVENTS_ROUTES } from "./events-routes.js";
45
47
  import { ROUTES as FILING_ROUTES } from "./filing-routes.js";
@@ -74,6 +76,7 @@ import { ROUTES as MIGRATION_ROLLBACK_ROUTES } from "./migration-rollback-routes
74
76
  import { ROUTES as MIGRATION_ROUTES } from "./migration-routes.js";
75
77
  import { ROUTES as NOTIFICATION_ROUTES } from "./notification-routes.js";
76
78
  import { ROUTES as OAUTH_APPS_ROUTES } from "./oauth-apps.js";
79
+ import { ROUTES as OAUTH_CONNECT_ROUTES } from "./oauth-connect-routes.js";
77
80
  import { ROUTES as OAUTH_PROVIDERS_ROUTES } from "./oauth-providers.js";
78
81
  import { ROUTES as PLAYGROUND_ROUTES } from "./playground/index.js";
79
82
  import { ROUTES as PROFILER_ROUTES } from "./profiler-routes.js";
@@ -137,8 +140,10 @@ export const ROUTES: RouteDefinition[] = [
137
140
  ...DEFER_ROUTES,
138
141
  ...CONVERSATION_QUERY_ROUTES,
139
142
  ...CONVERSATION_STARTER_ROUTES,
143
+ ...DEBUG_BASH_ROUTES,
140
144
  ...DEBUG_ROUTES,
141
145
  ...DIAGNOSTICS_ROUTES,
146
+ ...DISK_PRESSURE_ROUTES,
142
147
  ...DOCUMENT_ROUTES,
143
148
  ...EVENTS_ROUTES,
144
149
  ...FILING_ROUTES,
@@ -159,6 +164,7 @@ export const ROUTES: RouteDefinition[] = [
159
164
  ...INTERFACE_ROUTES,
160
165
  ...INTERNAL_OAUTH_ROUTES,
161
166
  ...MCP_AUTH_ROUTES,
167
+ ...OAUTH_CONNECT_ROUTES,
162
168
  ...INTERNAL_TWILIO_ROUTES,
163
169
  ...LOG_EXPORT_ROUTES,
164
170
  ...LLM_CALL_SITES_ROUTES,
@@ -6,6 +6,13 @@
6
6
  */
7
7
  import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
8
8
 
9
+ import { _setOverridesForTesting } from "../../config/assistant-feature-flags.js";
10
+
11
+ // This test exercises v1 memory CRUD routes. The `memory-v2-enabled` flag
12
+ // (registry default `true`) flips memory routing to v2 — disable it here so
13
+ // the v1 paths under test stay active.
14
+ _setOverridesForTesting({ "memory-v2-enabled": false });
15
+
9
16
  mock.module("../../util/logger.js", () => ({
10
17
  getLogger: () =>
11
18
  new Proxy({} as Record<string, unknown>, {
@@ -68,11 +75,7 @@ import { eq } from "drizzle-orm";
68
75
  import { getDb } from "../../memory/db-connection.js";
69
76
  import { initializeDb } from "../../memory/db-init.js";
70
77
  import { memoryGraphNodes, memoryJobs } from "../../memory/schema.js";
71
- import {
72
- BadRequestError,
73
- ConflictError,
74
- NotFoundError,
75
- } from "./errors.js";
78
+ import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
76
79
  import { ROUTES } from "./memory-item-routes.js";
77
80
  import type { RouteDefinition } from "./types.js";
78
81
 
@@ -269,7 +272,9 @@ describe("Memory Item Routes", () => {
269
272
  content: "s2\nst2",
270
273
  });
271
274
 
272
- const res = await callHandler(route, { queryParams: { kind: "semantic" } });
275
+ const res = await callHandler(route, {
276
+ queryParams: { kind: "semantic" },
277
+ });
273
278
  const body = (await res.json()) as {
274
279
  items: Array<{ id: string }>;
275
280
  total: number;
@@ -319,7 +324,9 @@ describe("Memory Item Routes", () => {
319
324
  lastAccessed: 3000,
320
325
  });
321
326
 
322
- const res = await callHandler(route, { queryParams: { limit: "1", offset: "1" } });
327
+ const res = await callHandler(route, {
328
+ queryParams: { limit: "1", offset: "1" },
329
+ });
323
330
  const body = (await res.json()) as {
324
331
  items: Array<{ id: string }>;
325
332
  total: number;
@@ -344,7 +351,9 @@ describe("Memory Item Routes", () => {
344
351
  created: 1000,
345
352
  });
346
353
 
347
- const res = await callHandler(route, { queryParams: { sort: "firstSeenAt", order: "asc" } });
354
+ const res = await callHandler(route, {
355
+ queryParams: { sort: "firstSeenAt", order: "asc" },
356
+ });
348
357
  const body = (await res.json()) as {
349
358
  items: Array<{ id: string }>;
350
359
  };
@@ -366,7 +375,9 @@ describe("Memory Item Routes", () => {
366
375
  significance: 0.9,
367
376
  });
368
377
 
369
- const res = await callHandler(route, { queryParams: { sort: "importance", order: "desc" } });
378
+ const res = await callHandler(route, {
379
+ queryParams: { sort: "importance", order: "desc" },
380
+ });
370
381
  const body = (await res.json()) as {
371
382
  items: Array<{ id: string }>;
372
383
  };
@@ -418,7 +429,9 @@ describe("Memory Item Routes", () => {
418
429
  },
419
430
  ];
420
431
 
421
- const res = await callHandler(route, { queryParams: { search: "alice" } });
432
+ const res = await callHandler(route, {
433
+ queryParams: { search: "alice" },
434
+ });
422
435
  const body = (await res.json()) as {
423
436
  items: Array<{ id: string }>;
424
437
  total: number;
@@ -503,7 +516,9 @@ describe("Memory Item Routes", () => {
503
516
  ];
504
517
 
505
518
  // Request page 2 (offset=1, limit=1)
506
- const res = await callHandler(route, { queryParams: { search: "item", limit: "1", offset: "1" } });
519
+ const res = await callHandler(route, {
520
+ queryParams: { search: "item", limit: "1", offset: "1" },
521
+ });
507
522
  const body = (await res.json()) as {
508
523
  items: Array<{ id: string }>;
509
524
  total: number;
@@ -563,7 +578,10 @@ describe("Memory Item Routes", () => {
563
578
  content: "dark mode\nPrefers dark mode",
564
579
  });
565
580
 
566
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "i1" } });
581
+ const res = await callHandler(route, {
582
+ queryParams: {},
583
+ pathParams: { id: "i1" },
584
+ });
567
585
  expect(res.status).toBe(200);
568
586
  const body = (await res.json()) as {
569
587
  item: { id: string; subject: string };
@@ -573,7 +591,10 @@ describe("Memory Item Routes", () => {
573
591
  });
574
592
 
575
593
  test("returns 404 for non-existent item", async () => {
576
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "nonexistent" } });
594
+ const res = await callHandler(route, {
595
+ queryParams: {},
596
+ pathParams: { id: "nonexistent" },
597
+ });
577
598
  expect(res.status).toBe(404);
578
599
  });
579
600
 
@@ -584,7 +605,10 @@ describe("Memory Item Routes", () => {
584
605
  content: "some content\nsome statement",
585
606
  });
586
607
 
587
- const res = await callHandler(route, { queryParams: {}, pathParams: { id: "i1" } });
608
+ const res = await callHandler(route, {
609
+ queryParams: {},
610
+ pathParams: { id: "i1" },
611
+ });
588
612
  const body = (await res.json()) as {
589
613
  item: { supersedes: unknown; supersededBy: unknown };
590
614
  };
@@ -862,7 +886,9 @@ describe("Memory Item Routes", () => {
862
886
  });
863
887
 
864
888
  test("returns 404 for non-existent item", async () => {
865
- const res = await callHandler(route, { pathParams: { id: "nonexistent" } });
889
+ const res = await callHandler(route, {
890
+ pathParams: { id: "nonexistent" },
891
+ });
866
892
  expect(res.status).toBe(404);
867
893
  });
868
894
 
@@ -22,6 +22,10 @@ import {
22
22
  enqueueMemoryJob,
23
23
  type MemoryJobType,
24
24
  } from "../../memory/jobs-store.js";
25
+ import {
26
+ type ConceptFrequencyResponse,
27
+ getConceptFrequencySummary,
28
+ } from "../../memory/memory-v2-concept-frequency.js";
25
29
  import {
26
30
  getEdgeIndex,
27
31
  totalEdgeCount,
@@ -459,6 +463,24 @@ async function handleExplainSimilarity({
459
463
  };
460
464
  }
461
465
 
466
+ // ── Concept injection frequency (debug-only) ────────────────────────────
467
+
468
+ const MemoryV2ConceptFrequencyParams = z
469
+ .object({
470
+ conversationId: z.string().min(1).optional(),
471
+ sinceMs: z.number().int().nonnegative().optional(),
472
+ })
473
+ .strict();
474
+
475
+ async function handleConceptFrequency({
476
+ body = {},
477
+ }: RouteHandlerArgs): Promise<ConceptFrequencyResponse> {
478
+ const { conversationId, sinceMs } =
479
+ MemoryV2ConceptFrequencyParams.parse(body);
480
+ const workspaceDir = getWorkspaceDir();
481
+ return getConceptFrequencySummary(workspaceDir, { conversationId, sinceMs });
482
+ }
483
+
462
484
  // ── Fit anisotropy calibration ──────────────────────────────────────────
463
485
 
464
486
  const MemoryV2FitAnisotropyParams = z
@@ -614,6 +636,17 @@ export const ROUTES: RouteDefinition[] = [
614
636
  tags: ["memory"],
615
637
  requestBody: MemoryV2RebuildCorpusStatsParams,
616
638
  },
639
+ {
640
+ operationId: "memory_v2_concept_frequency",
641
+ method: "POST",
642
+ endpoint: "memory/v2/concept-frequency",
643
+ handler: handleConceptFrequency,
644
+ summary: "Aggregate per-concept injection frequency from activation logs",
645
+ description:
646
+ "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.",
647
+ tags: ["memory"],
648
+ requestBody: MemoryV2ConceptFrequencyParams,
649
+ },
617
650
  {
618
651
  operationId: "memory_v2_fit_anisotropy",
619
652
  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 },
@@ -1,3 +1,8 @@
1
+ import {
2
+ checkDiskPressureBackgroundGate,
3
+ diskPressureBackgroundSkipLogFields,
4
+ shouldLogDiskPressureBackgroundSkip,
5
+ } from "../daemon/disk-pressure-background-gate.js";
1
6
  import { emitFeedEvent } from "../home/emit-feed-event.js";
2
7
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
3
8
  import { getConversation } from "../memory/conversation-crud.js";
@@ -129,7 +134,7 @@ export function startScheduler(
129
134
  };
130
135
  }
131
136
 
132
- async function runScheduleOnce(
137
+ export async function runScheduleOnce(
133
138
  processMessage: ScheduleMessageProcessor,
134
139
  notifyScheduleOneShot: ScheduleNotifyModeNotifier,
135
140
  watcherNotifier?: WatcherNotifier,
@@ -139,6 +144,20 @@ async function runScheduleOnce(
139
144
  const now = Date.now();
140
145
  let processed = 0;
141
146
 
147
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
148
+ if (diskPressureGate.action === "skip") {
149
+ if (shouldLogDiskPressureBackgroundSkip("scheduler")) {
150
+ log.warn(
151
+ {
152
+ source: "schedule",
153
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
154
+ },
155
+ "Schedule tick skipped during disk pressure cleanup mode",
156
+ );
157
+ }
158
+ return 0;
159
+ }
160
+
142
161
  // ── Schedules (recurring cron/RRULE + one-shot) ─────────────────────
143
162
  const jobs = claimDueSchedules(now);
144
163
  for (const job of jobs) {
@@ -108,6 +108,8 @@ function getStoreKeyPath(): string {
108
108
  );
109
109
  }
110
110
 
111
+
112
+
111
113
  /**
112
114
  * Read the store.key file. Returns the raw 32-byte key buffer, or null
113
115
  * if the file is missing, wrong size, or unreadable.