@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
@@ -73,7 +73,10 @@ import type { ConversationGraphMemory } from "../memory/graph/conversation-graph
73
73
  import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
74
74
  import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
75
75
  import type { QdrantSparseVector } from "../memory/qdrant-client.js";
76
- import { readMemoryV2StaticContent } from "../memory/v2/static-context.js";
76
+ import {
77
+ readMemoryV2StaticContent,
78
+ shouldLoadMemoryV2Static,
79
+ } from "../memory/v2/static-context.js";
77
80
  import type { PermissionPrompter } from "../permissions/prompter.js";
78
81
  import { defaultCompactionTerminal } from "../plugins/defaults/compaction.js";
79
82
  import { defaultHistoryRepairTerminal } from "../plugins/defaults/history-repair.js";
@@ -106,6 +109,11 @@ import type {
106
109
  TurnContext as PluginTurnContext,
107
110
  } from "../plugins/types.js";
108
111
  import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
112
+ import {
113
+ hasProactiveArtifactCompleted,
114
+ runProactiveArtifactJob,
115
+ tryClaimProactiveArtifactTrigger,
116
+ } from "../proactive-artifact/index.js";
109
117
  import type {
110
118
  ContentBlock,
111
119
  Message,
@@ -113,6 +121,7 @@ import type {
113
121
  } from "../providers/types.js";
114
122
  import type { Provider } from "../providers/types.js";
115
123
  import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
124
+ import { broadcastMessage } from "../runtime/assistant-event-hub.js";
116
125
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
117
126
  import { redactSecrets } from "../security/secret-scanner.js";
118
127
  import { getSubagentManager } from "../subagent/index.js";
@@ -175,7 +184,12 @@ import type { SkillProjectionCache } from "./conversation-skill-tools.js";
175
184
  import { markSurfaceCompleted } from "./conversation-surfaces.js";
176
185
  import { resolveTrustClass } from "./conversation-tool-setup.js";
177
186
  import { recordUsage } from "./conversation-usage.js";
178
- import { formatTurnTimestamp } from "./date-context.js";
187
+ import {
188
+ formatTurnTimestamp,
189
+ resolveTurnTimezoneContext,
190
+ } from "./date-context.js";
191
+ import { getDiskPressureStatus } from "./disk-pressure-guard.js";
192
+ import { classifyDiskPressureTurnPolicy } from "./disk-pressure-policy.js";
179
193
  import { deepRepairHistory } from "./history-repair.js";
180
194
  import type {
181
195
  DynamicPageSurfaceData,
@@ -192,6 +206,8 @@ import type { TrustContext } from "./trust-context.js";
192
206
  import { stripHistoricalWebSearchResults } from "./web-search-history.js";
193
207
 
194
208
  const log = getLogger("conversation-agent-loop");
209
+ const DISK_PRESSURE_ERROR_CODE = "DISK_SPACE_CRITICAL" as const;
210
+ const DISK_PRESSURE_ERROR_CATEGORY = "disk_pressure";
195
211
 
196
212
  /** Title-cased friendly labels for tool names, used in confirmation chips. */
197
213
  const TOOL_FRIENDLY_LABEL: Record<string, string> = {
@@ -211,6 +227,10 @@ type GitServiceInitializer = {
211
227
  ensureInitialized(): Promise<void>;
212
228
  };
213
229
 
230
+ function formatDiskPressureBlockedMessage(): string {
231
+ return "Storage is critically low, so background processes are paused and remote messages are ignored until the guardian frees enough space. Remote senders should try again later.";
232
+ }
233
+
214
234
  // ── Compaction circuit-breaker pipeline helpers ─────────────────────
215
235
  //
216
236
  // The circuit-breaker behavior (3 consecutive summary-LLM failures trips a
@@ -438,7 +458,6 @@ export interface AgentLoopConversationContext {
438
458
  /** Timestamp (ms since epoch) until which the circuit breaker is open. */
439
459
  compactionCircuitOpenUntil: number | null;
440
460
 
441
- readonly memoryPolicy: { scopeId: string; includeDefaultFallback: boolean };
442
461
  readonly graphMemory: ConversationGraphMemory;
443
462
 
444
463
  currentActiveSurfaceId?: string;
@@ -495,9 +514,11 @@ export interface AgentLoopConversationContext {
495
514
  voiceCallControlPrompt?: string;
496
515
  transportHints?: string[];
497
516
  slackRuntimeContextNotice?: string;
517
+ clientTimezone?: string;
498
518
 
499
519
  readonly coreToolNames: Set<string>;
500
520
  allowedToolNames?: Set<string>;
521
+ diskPressureCleanupModeActive?: boolean;
501
522
  toolsDisabledDepth: number;
502
523
  preactivatedSkillIds?: string[];
503
524
  readonly skillProjectionState: Map<string, string>;
@@ -707,19 +728,39 @@ export async function runAgentLoopImpl(
707
728
  };
708
729
  })();
709
730
 
731
+ const isInteractiveResolved =
732
+ options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
733
+ const diskPressureDecision = classifyDiskPressureTurnPolicy(
734
+ getDiskPressureStatus(),
735
+ {
736
+ conversationType: turnStartConversation?.conversationType ?? null,
737
+ conversationSource: turnStartConversation?.source ?? null,
738
+ callSite: turnCallSite,
739
+ isInteractive: isInteractiveResolved,
740
+ sourceChannel:
741
+ ctx.trustContext?.sourceChannel ??
742
+ capturedTurnChannelContext.userMessageChannel,
743
+ sourceInterface:
744
+ ctx.channelCapabilities?.clientOS ??
745
+ capturedTurnInterfaceContext.userMessageInterface,
746
+ trustContext: ctx.trustContext
747
+ ? {
748
+ sourceChannel: ctx.trustContext.sourceChannel,
749
+ trustClass: ctx.trustContext.trustClass,
750
+ }
751
+ : null,
752
+ },
753
+ );
754
+ const diskPressureContext =
755
+ diskPressureDecision.action === "allow-cleanup-mode"
756
+ ? { cleanupModeActive: true }
757
+ : null;
758
+ ctx.diskPressureCleanupModeActive =
759
+ diskPressureDecision.action === "allow-cleanup-mode";
760
+
710
761
  ctx.lastAssistantAttachments = [];
711
762
  ctx.lastAttachmentWarnings = [];
712
763
 
713
- // Ensure workspace git repo is initialized before any tools run.
714
- try {
715
- const getWorkspaceGitServiceFn =
716
- ctx.getWorkspaceGitService ?? getWorkspaceGitService;
717
- const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
718
- await gitService.ensureInitialized();
719
- } catch (err) {
720
- rlog.warn({ err }, "Failed to initialize workspace git repo (non-fatal)");
721
- }
722
-
723
764
  ctx.profiler.startRequest();
724
765
  let turnStarted = false;
725
766
 
@@ -736,6 +777,52 @@ export async function runAgentLoopImpl(
736
777
  });
737
778
 
738
779
  try {
780
+ if (diskPressureDecision.action === "block") {
781
+ const message = formatDiskPressureBlockedMessage();
782
+ rlog.warn(
783
+ { reason: diskPressureDecision.reason },
784
+ "Blocked turn during disk pressure cleanup mode",
785
+ );
786
+ ctx.emitActivityState("idle", "error_terminal", "global", reqId);
787
+ ctx.traceEmitter.emit("request_error", message, {
788
+ requestId: reqId,
789
+ status: "error",
790
+ attributes: {
791
+ errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
792
+ errorCode: DISK_PRESSURE_ERROR_CODE,
793
+ diskPressureReason: diskPressureDecision.reason,
794
+ },
795
+ });
796
+ onEvent({
797
+ type: "error",
798
+ conversationId: ctx.conversationId,
799
+ requestId: reqId,
800
+ code: DISK_PRESSURE_ERROR_CODE,
801
+ message,
802
+ category: DISK_PRESSURE_ERROR_CATEGORY,
803
+ errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
804
+ });
805
+ onEvent({
806
+ type: "conversation_error",
807
+ conversationId: ctx.conversationId,
808
+ code: DISK_PRESSURE_ERROR_CODE,
809
+ userMessage: message,
810
+ retryable: true,
811
+ errorCategory: DISK_PRESSURE_ERROR_CATEGORY,
812
+ });
813
+ return;
814
+ }
815
+
816
+ // Ensure workspace git repo is initialized before any tools run.
817
+ try {
818
+ const getWorkspaceGitServiceFn =
819
+ ctx.getWorkspaceGitService ?? getWorkspaceGitService;
820
+ const gitService = getWorkspaceGitServiceFn(ctx.workingDir);
821
+ await gitService.ensureInitialized();
822
+ } catch (err) {
823
+ rlog.warn({ err }, "Failed to initialize workspace git repo (non-fatal)");
824
+ }
825
+
739
826
  // Auto-complete stale interactive surfaces from previous turns.
740
827
  // Only dismiss when the user sends a new message (not a surface action
741
828
  // response), so internal turns (subagent notifications, lifecycle
@@ -899,7 +986,7 @@ export async function runAgentLoopImpl(
899
986
  compactableStartIndex: 1,
900
987
  };
901
988
  };
902
- const applySuccessfulCompaction = (
989
+ const applySuccessfulCompaction = async (
903
990
  result: Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>,
904
991
  compactedBasis?: Message[],
905
992
  ) => {
@@ -913,7 +1000,7 @@ export async function runAgentLoopImpl(
913
1000
  provenanceContext,
914
1001
  result.compactedMessages,
915
1002
  );
916
- applyCompactionResult(ctx, result, onEvent, reqId, {
1003
+ await applyCompactionResult(ctx, result, onEvent, reqId, {
917
1004
  slackContextCompactionWatermarkTs: slackWatermarkTs,
918
1005
  });
919
1006
  currentSlackContextSummary = result.summaryText;
@@ -1000,7 +1087,10 @@ export async function runAgentLoopImpl(
1000
1087
  await trackCompactionOutcome(ctx, compacted.summaryFailed, onEvent);
1001
1088
  }
1002
1089
  if (compacted?.compacted) {
1003
- applySuccessfulCompaction(compacted, messagesForStartOfTurnCompaction);
1090
+ await applySuccessfulCompaction(
1091
+ compacted,
1092
+ messagesForStartOfTurnCompaction,
1093
+ );
1004
1094
  shouldInjectWorkspace = true;
1005
1095
  if (compacted.compactedPersistedMessages > 0) {
1006
1096
  compactedThisTurn = true;
@@ -1238,14 +1328,16 @@ export async function runAgentLoopImpl(
1238
1328
 
1239
1329
  // Compute fresh turn timestamp for date grounding.
1240
1330
  // Absolute "now" is always anchored to assistant host clock, while local
1241
- // date semantics prefer configured user timezone, then recalled memory.
1331
+ // date semantics prefer configured user timezone, then device timezones.
1242
1332
  const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1243
- const configuredUserTimeZone = getConfig().ui.userTimezone ?? null;
1244
- const recalledUserTimeZone = null;
1245
- const timestamp = formatTurnTimestamp({
1333
+ const timezoneContext = resolveTurnTimezoneContext({
1334
+ configuredUserTimeZone: config.ui.userTimezone ?? null,
1335
+ clientTimezone: ctx.clientTimezone ?? null,
1336
+ detectedTimezone: config.ui.detectedTimezone ?? null,
1246
1337
  hostTimeZone,
1247
- configuredUserTimeZone,
1248
- userTimeZone: recalledUserTimeZone,
1338
+ });
1339
+ const timestamp = formatTurnTimestamp({
1340
+ timeZone: timezoneContext.effectiveTimezone,
1249
1341
  });
1250
1342
 
1251
1343
  // Resolve the inbound actor context for the unified <turn_context> block.
@@ -1299,23 +1391,26 @@ export async function runAgentLoopImpl(
1299
1391
  }
1300
1392
  }
1301
1393
 
1394
+ const baseTurnContext = {
1395
+ timestamp,
1396
+ interfaceName,
1397
+ channelName,
1398
+ configuredUserTimezone: timezoneContext.configuredUserTimezone,
1399
+ clientTimezone: timezoneContext.clientTimezone,
1400
+ detectedTimezone: timezoneContext.detectedTimezone,
1401
+ timeSinceLastMessage,
1402
+ };
1302
1403
  const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
1303
1404
  isGuardian
1304
- ? { timestamp, interfaceName, channelName, timeSinceLastMessage }
1405
+ ? baseTurnContext
1305
1406
  : {
1306
- timestamp,
1307
- interfaceName,
1308
- channelName,
1407
+ ...baseTurnContext,
1309
1408
  actorContext: resolvedInboundActorContext,
1310
- timeSinceLastMessage,
1311
1409
  },
1312
1410
  );
1313
1411
 
1314
1412
  // The `remember` tool handles scratchpad-style memory writes directly to the graph.
1315
1413
 
1316
- const isInteractiveResolved =
1317
- options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
1318
-
1319
1414
  // Inject NOW.md and PKB content only on the first turn (or after
1320
1415
  // compaction re-strips them). Old injections persist in history and
1321
1416
  // are never stripped on normal turns — this preserves the cached prefix.
@@ -1330,10 +1425,16 @@ export async function runAgentLoopImpl(
1330
1425
  const pkbActive = currentPkbContent !== null;
1331
1426
 
1332
1427
  // V2 static memory block (essentials/threads/recent/buffer). Same
1333
- // first-turn / post-compaction cadence as PKB `readMemoryV2StaticContent`
1334
- // self-gates on the v2 flag + config, returning null when v2 is off.
1335
- // Skip the file reads entirely on non-injection turns.
1336
- const currentMemoryV2Static = shouldInjectNowAndPkb
1428
+ // first-turn / post-compaction cadence as PKB. `shouldLoadMemoryV2Static`
1429
+ // also blocks remote-channel non-guardian actors from inducing the
1430
+ // model to recite private memory; `readMemoryV2StaticContent` self-gates
1431
+ // on the v2 flag + config and returns null when v2 is off, so the file
1432
+ // reads are skipped on non-injection turns.
1433
+ const currentMemoryV2Static = shouldLoadMemoryV2Static({
1434
+ shouldInjectNowAndPkb,
1435
+ sourceChannel: ctx.trustContext?.sourceChannel,
1436
+ isTrustedActor,
1437
+ })
1337
1438
  ? readMemoryV2StaticContent()
1338
1439
  : null;
1339
1440
  const memoryV2Static = currentMemoryV2Static;
@@ -1349,8 +1450,8 @@ export async function runAgentLoopImpl(
1349
1450
  // `getInContextPkbPaths` re-reads `conversation.messages` on each call,
1350
1451
  // so post-compaction re-injects see the updated history.
1351
1452
  const pkbConversation = pkbActive ? ctx : undefined;
1352
- // PKB points live under a single workspace sentinel scope, not the
1353
- // conversation's memoryPolicy.scopeId. See `PKB_WORKSPACE_SCOPE` for why.
1453
+ // PKB points live under a single workspace sentinel scope.
1454
+ // See `PKB_WORKSPACE_SCOPE` for why.
1354
1455
  const pkbScopeId = pkbActive ? PKB_WORKSPACE_SCOPE : undefined;
1355
1456
 
1356
1457
  // Subagent status injection — gives the parent LLM visibility into active/completed children.
@@ -1417,6 +1518,7 @@ export async function runAgentLoopImpl(
1417
1518
 
1418
1519
  // Shared injection options — reused whenever we need to re-inject after reduction.
1419
1520
  const injectionOpts = {
1521
+ diskPressureContext,
1420
1522
  activeSurface,
1421
1523
  workspaceTopLevelContext: shouldInjectWorkspace
1422
1524
  ? ctx.workspaceTopLevelContext
@@ -1691,7 +1793,7 @@ export async function runAgentLoopImpl(
1691
1793
  await trackCompactionOutcome(ctx, result.summaryFailed, onEvent);
1692
1794
  }
1693
1795
  if (result.compacted) {
1694
- applySuccessfulCompaction(result, compactedBasis);
1796
+ await applySuccessfulCompaction(result, compactedBasis);
1695
1797
  shouldInjectWorkspace = true;
1696
1798
  }
1697
1799
  },
@@ -2020,7 +2122,7 @@ export async function runAgentLoopImpl(
2020
2122
  );
2021
2123
  }
2022
2124
  if (midLoopCompact.compacted) {
2023
- applySuccessfulCompaction(midLoopCompact, rawHistory);
2125
+ await applySuccessfulCompaction(midLoopCompact, rawHistory);
2024
2126
  reducerCompacted = true;
2025
2127
  shouldInjectWorkspace = true;
2026
2128
  }
@@ -2272,7 +2374,7 @@ export async function runAgentLoopImpl(
2272
2374
  }
2273
2375
 
2274
2376
  if (step.compactionResult?.compacted) {
2275
- applySuccessfulCompaction(
2377
+ await applySuccessfulCompaction(
2276
2378
  step.compactionResult,
2277
2379
  convergenceCompactionBasis,
2278
2380
  );
@@ -2438,7 +2540,7 @@ export async function runAgentLoopImpl(
2438
2540
  );
2439
2541
  }
2440
2542
  if (emergencyCompact?.compacted) {
2441
- applySuccessfulCompaction(emergencyCompact, ctx.messages);
2543
+ await applySuccessfulCompaction(emergencyCompact, ctx.messages);
2442
2544
  reducerCompacted = true;
2443
2545
  shouldInjectWorkspace = true;
2444
2546
  }
@@ -2874,6 +2976,42 @@ export async function runAgentLoopImpl(
2874
2976
  "Failed to build home-feed event for background conversation",
2875
2977
  );
2876
2978
  }
2979
+
2980
+ // Proactive artifact: fire once when the processed turn was the 4th user message.
2981
+ // Only trigger for real user-authored turns (not subagent/system messages).
2982
+ {
2983
+ const paConv = getConversation(ctx.conversationId);
2984
+ if (
2985
+ paConv &&
2986
+ paConv.conversationType === "standard" &&
2987
+ options?.isUserMessage
2988
+ ) {
2989
+ void (async () => {
2990
+ try {
2991
+ if (hasProactiveArtifactCompleted()) return;
2992
+ const userMsg = getMessageById(
2993
+ userMessageId,
2994
+ ctx.conversationId,
2995
+ );
2996
+ if (!userMsg) return;
2997
+ if (!tryClaimProactiveArtifactTrigger(userMsg.createdAt))
2998
+ return;
2999
+ await runProactiveArtifactJob({
3000
+ conversationId: ctx.conversationId,
3001
+ userMessageCutoff: userMsg.createdAt,
3002
+ assistantMessageId: state.lastAssistantMessageId,
3003
+ suppressAppBuild: state.appBuildToolUsedThisRun,
3004
+ broadcastMessage,
3005
+ });
3006
+ } catch (err) {
3007
+ log.warn(
3008
+ { err, conversationId: ctx.conversationId },
3009
+ "Proactive artifact trigger failed",
3010
+ );
3011
+ }
3012
+ })();
3013
+ }
3014
+ }
2877
3015
  }
2878
3016
  }
2879
3017
 
@@ -2937,6 +3075,7 @@ export async function runAgentLoopImpl(
2937
3075
  conversationId: ctx.conversationId,
2938
3076
  code: classified.code,
2939
3077
  message: classified.userMessage,
3078
+ errorCategory: classified.errorCategory,
2940
3079
  });
2941
3080
  onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
2942
3081
  }
@@ -2988,6 +3127,7 @@ export async function runAgentLoopImpl(
2988
3127
  ctx.currentRequestId = undefined;
2989
3128
  ctx.currentActiveSurfaceId = undefined;
2990
3129
  ctx.allowedToolNames = undefined;
3130
+ ctx.diskPressureCleanupModeActive = false;
2991
3131
  ctx.preactivatedSkillIds = undefined;
2992
3132
  ctx.currentTurnOverrideProfile = undefined;
2993
3133
  ctx.slackRuntimeContextNotice = undefined;
@@ -3086,7 +3226,7 @@ export interface CompactionApplyContext {
3086
3226
  * truth for the UI indicator after compaction. Emitting both caused a
3087
3227
  * redundant SwiftUI invalidation on every compaction.
3088
3228
  */
3089
- export function applyCompactionResult(
3229
+ export async function applyCompactionResult(
3090
3230
  ctx: CompactionApplyContext,
3091
3231
  result: {
3092
3232
  messages: Message[];
@@ -3112,12 +3252,12 @@ export function applyCompactionResult(
3112
3252
  options: {
3113
3253
  slackContextCompactionWatermarkTs?: string | null;
3114
3254
  } = {},
3115
- ): void {
3255
+ ): Promise<void> {
3116
3256
  ctx.messages = result.messages;
3117
3257
  ctx.contextCompactedMessageCount += result.compactedPersistedMessages;
3118
3258
  const compactedAt = Date.now();
3119
3259
  ctx.contextCompactedAt = compactedAt;
3120
- ctx.graphMemory.onCompacted(result.compactedPersistedMessages);
3260
+ await ctx.graphMemory.onCompacted(result.compactedPersistedMessages);
3121
3261
  updateConversationContextWindow(
3122
3262
  ctx.conversationId,
3123
3263
  result.summaryText,
@@ -36,6 +36,23 @@ const NETWORK_PATTERNS = [
36
36
  // Rate limit patterns (HTTP 429 or explicit rate limit messages)
37
37
  const RATE_LIMIT_PATTERNS = [/429/, /rate.?limit/i, /too many requests/i];
38
38
 
39
+ // Managed usage-limit responses are generated by Vellum, even though they can
40
+ // travel through provider SDKs and get wrapped as ProviderError.
41
+ const MANAGED_USAGE_LIMIT_PATTERNS = [
42
+ /"code"\s*:\s*"daily_quota_exceeded"/i,
43
+ /"code"\s*:\s*"rate_limit_exceeded"/i,
44
+ /system credential proxy rate limit/i,
45
+ /you've reached your usage limit for today/i,
46
+ /current plan allows/i,
47
+ ];
48
+
49
+ const PROVIDER_BILLING_PATTERNS = [
50
+ /credit balance is too low/i,
51
+ /insufficient.*credits?/i,
52
+ /requires more credits/i,
53
+ /can only afford/i,
54
+ ];
55
+
39
56
  // Overloaded patterns — provider is capacity-constrained (distinct from rate limiting)
40
57
  const OVERLOADED_PATTERNS = [/overloaded/i];
41
58
 
@@ -259,15 +276,21 @@ function classifyCore(
259
276
  };
260
277
  }
261
278
  if (error.statusCode === 402) {
262
- return {
263
- code: "PROVIDER_BILLING",
264
- userMessage:
265
- "You've run out of credits. Add funds to continue using the assistant.",
266
- retryable: false,
267
- errorCategory: "credits_exhausted",
268
- };
279
+ if (isManagedBalanceError(error)) {
280
+ return managedBalanceClassification();
281
+ }
282
+ return providerBillingClassification();
269
283
  }
270
284
  if (error.statusCode === 429) {
285
+ if (isManagedUsageLimitError(error, message)) {
286
+ return {
287
+ code: "MANAGED_USAGE_LIMIT",
288
+ userMessage:
289
+ "Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
290
+ retryable: true,
291
+ errorCategory: "managed_usage_limit",
292
+ };
293
+ }
271
294
  return {
272
295
  code: "PROVIDER_RATE_LIMIT",
273
296
  userMessage:
@@ -331,13 +354,8 @@ function classifyCore(
331
354
  errorCategory: "tool_ordering",
332
355
  };
333
356
  }
334
- if (/credit balance is too low|insufficient.*credits?/i.test(message)) {
335
- return {
336
- code: "PROVIDER_BILLING",
337
- userMessage: "Your API key has insufficient credits.",
338
- retryable: false,
339
- errorCategory: "provider_billing",
340
- };
357
+ if (isProviderBillingError(message)) {
358
+ return providerBillingClassification();
341
359
  }
342
360
  if (
343
361
  /invalid.*api.?key|invalid.*x-api-key|authentication.?error|invalid.authentication/i.test(
@@ -362,7 +380,7 @@ function classifyCore(
362
380
  }
363
381
 
364
382
  // Regex fallback for non-ProviderError or ProviderError without statusCode
365
- return classifyByMessage(message);
383
+ return classifyByMessage(error, message);
366
384
  }
367
385
 
368
386
  /** Check whether an error message indicates a context-too-large failure. */
@@ -394,7 +412,52 @@ function isStreamingError(message: string): boolean {
394
412
  return STREAMING_ERROR_PATTERNS.some((p) => p.test(message));
395
413
  }
396
414
 
415
+ function isManagedUsageLimitError(error: unknown, message: string): boolean {
416
+ if (
417
+ error instanceof ProviderError &&
418
+ getProviderRoutingSource(error.provider) === "managed-proxy"
419
+ ) {
420
+ return true;
421
+ }
422
+ return MANAGED_USAGE_LIMIT_PATTERNS.some((p) => p.test(message));
423
+ }
424
+
425
+ function isManagedBalanceError(error: ProviderError): boolean {
426
+ return getProviderRoutingSource(error.provider) === "managed-proxy";
427
+ }
428
+
429
+ function isProviderBillingError(message: string): boolean {
430
+ return PROVIDER_BILLING_PATTERNS.some((p) => p.test(message));
431
+ }
432
+
433
+ function managedBalanceClassification(): Omit<
434
+ ClassifiedConversationError,
435
+ "debugDetails"
436
+ > {
437
+ return {
438
+ code: "PROVIDER_BILLING",
439
+ userMessage:
440
+ "You've run out of credits. Add funds to continue using the assistant.",
441
+ retryable: false,
442
+ errorCategory: "credits_exhausted",
443
+ };
444
+ }
445
+
446
+ function providerBillingClassification(): Omit<
447
+ ClassifiedConversationError,
448
+ "debugDetails"
449
+ > {
450
+ return {
451
+ code: "PROVIDER_BILLING",
452
+ userMessage:
453
+ "Your API provider account or key needs credits. Add funds with the provider or update the key in Settings.",
454
+ retryable: false,
455
+ errorCategory: "provider_billing",
456
+ };
457
+ }
458
+
397
459
  function classifyByMessage(
460
+ error: unknown,
398
461
  message: string,
399
462
  ): Omit<ClassifiedConversationError, "debugDetails"> {
400
463
  // Check context-too-large before other patterns
@@ -411,6 +474,15 @@ function classifyByMessage(
411
474
  // Check rate limit first (before network, since 429 could match both)
412
475
  for (const pattern of RATE_LIMIT_PATTERNS) {
413
476
  if (pattern.test(message)) {
477
+ if (isManagedUsageLimitError(error, message)) {
478
+ return {
479
+ code: "MANAGED_USAGE_LIMIT",
480
+ userMessage:
481
+ "Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
482
+ retryable: true,
483
+ errorCategory: "managed_usage_limit",
484
+ };
485
+ }
414
486
  return {
415
487
  code: "PROVIDER_RATE_LIMIT",
416
488
  userMessage:
@@ -4,6 +4,7 @@
4
4
  * can delegate without exposing its full surface.
5
5
  */
6
6
 
7
+ import { getConfig } from "../config/loader.js";
7
8
  import { createContextSummaryMessage } from "../context/window-manager.js";
8
9
  import type { EventBus } from "../events/bus.js";
9
10
  import type { AssistantDomainEvents } from "../events/domain-events.js";
@@ -153,8 +154,6 @@ export interface DisposeContext extends AbortContext {
153
154
  trustContext?: { trustClass: TrustClass };
154
155
  /** Active memory node IDs snapshotted from the conversation's InContextTracker before disposal. */
155
156
  activeContextNodeIds?: string[];
156
- /** Memory scope for extraction — defaults to "default" if omitted. */
157
- memoryScopeId?: string;
158
157
  abort(): void;
159
158
  }
160
159
 
@@ -375,16 +374,29 @@ export function disposeConversation(ctx: DisposeContext): void {
375
374
  // Best-effort — don't block conversation disposal
376
375
  }
377
376
  if (!isAutoAnalysis) {
377
+ // Suppress v1 graph extraction when memory v2 is active — v2 reads
378
+ // from buffer.md and concept pages, so the v1 graph would be stale
379
+ // data nobody consumes. Mirrors the gate applied in `indexer.ts`
380
+ // for the per-message indexing path. Fail open to v1 if config
381
+ // can't load, since the worker handler also short-circuits.
382
+ let v2Enabled = false;
378
383
  try {
379
- enqueueMemoryJob("graph_extract", {
380
- conversationId: ctx.conversationId,
381
- scopeId: ctx.memoryScopeId ?? "default",
382
- ...(ctx.activeContextNodeIds?.length
383
- ? { activeContextNodeIds: ctx.activeContextNodeIds }
384
- : {}),
385
- });
384
+ v2Enabled = getConfig().memory.v2.enabled;
386
385
  } catch {
387
- // Best-effort — don't block conversation disposal
386
+ // Best-effort — fall through to legacy v1 enqueue
387
+ }
388
+ if (!v2Enabled) {
389
+ try {
390
+ enqueueMemoryJob("graph_extract", {
391
+ conversationId: ctx.conversationId,
392
+ scopeId: "default",
393
+ ...(ctx.activeContextNodeIds?.length
394
+ ? { activeContextNodeIds: ctx.activeContextNodeIds }
395
+ : {}),
396
+ });
397
+ } catch {
398
+ // Best-effort — don't block conversation disposal
399
+ }
388
400
  }
389
401
  }
390
402
 
@@ -182,6 +182,8 @@ export interface ProcessConversationContext {
182
182
  forceCompact(): Promise<ContextWindowResult>;
183
183
  /** Set transport-derived hints for the conversation. */
184
184
  setTransportHints(hints: string[] | undefined): void;
185
+ /** IANA timezone reported by the active client for the current turn. */
186
+ clientTimezone?: string;
185
187
  /**
186
188
  * Apply client-reported host env (home dir, username) from transport
187
189
  * metadata, gating on `supportsHostProxy` so non-host-proxy interfaces
@@ -189,6 +191,10 @@ export interface ProcessConversationContext {
189
191
  * `DaemonServer.applyTransportMetadata` and the queue-drain path below.
190
192
  */
191
193
  applyHostEnvFromTransport(transport: ConversationTransportMetadata): void;
194
+ /** Apply the per-turn client timezone reported by transport metadata. */
195
+ applyClientTimezoneFromTransport(
196
+ transport: ConversationTransportMetadata,
197
+ ): void;
192
198
  }
193
199
 
194
200
  function resolveQueuedTurnContext(
@@ -423,6 +429,7 @@ async function drainSingleMessage(
423
429
  // setter used by DaemonServer.applyTransportMetadata so create/reuse
424
430
  // and queue-drain stay in sync without duplicating the gate logic.
425
431
  conversation.applyHostEnvFromTransport(next.transport);
432
+ conversation.applyClientTimezoneFromTransport(next.transport);
426
433
  }
427
434
 
428
435
  // Re-preactivate host-proxy skills for interactive desktop turns. The
@@ -865,6 +872,7 @@ async function drainBatch(
865
872
  if (head.transport) {
866
873
  conversation.setTransportHints(buildTransportHints(head.transport));
867
874
  conversation.applyHostEnvFromTransport(head.transport);
875
+ conversation.applyClientTimezoneFromTransport(head.transport);
868
876
  }
869
877
 
870
878
  // Re-preactivate host-proxy skills for interactive desktop turns.