@vellumai/assistant 0.11.3 → 0.11.4-staging.1

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 (324) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/docs/architecture/memory.md +11 -0
  3. package/docs/architecture/turn-actor.md +70 -0
  4. package/docs/flux-turn-detection-spike.md +243 -0
  5. package/docs/stt-provider-onboarding.md +3 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  8. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  10. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  11. package/openapi.yaml +140 -38
  12. package/package.json +1 -1
  13. package/scripts/voice-ttft-spike.ts +3 -3
  14. package/src/__tests__/app-compiler.test.ts +38 -3
  15. package/src/__tests__/attachments-store.test.ts +21 -12
  16. package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
  17. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  18. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  19. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  20. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  21. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  22. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  23. package/src/__tests__/config-schema.test.ts +25 -10
  24. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  26. package/src/__tests__/conversation-agent-loop.test.ts +28 -20
  27. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  28. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  29. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  30. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  31. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  32. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  33. package/src/__tests__/conversation-queue.test.ts +55 -4
  34. package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
  35. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  36. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  37. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  38. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  39. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  40. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  41. package/src/__tests__/edit-propagation.test.ts +0 -7
  42. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  43. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  44. package/src/__tests__/injector-chain.test.ts +63 -41
  45. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  46. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  47. package/src/__tests__/llm-schema.test.ts +5 -2
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  49. package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
  50. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  51. package/src/__tests__/mtime-cache.test.ts +1 -1
  52. package/src/__tests__/non-member-access-request.test.ts +0 -20
  53. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  54. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
  55. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  56. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  57. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  58. package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
  59. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  60. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  61. package/src/__tests__/skills.test.ts +32 -0
  62. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  63. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  64. package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
  65. package/src/__tests__/turn-events-store.test.ts +43 -0
  66. package/src/__tests__/user-plugin-loader.test.ts +1 -1
  67. package/src/__tests__/visible-app-context.test.ts +16 -9
  68. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  69. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  70. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  71. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  72. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  73. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  74. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  75. package/src/acp/__tests__/auth-required.test.ts +161 -0
  76. package/src/acp/acp-claude-oauth.ts +19 -2
  77. package/src/acp/agent-process.test.ts +100 -0
  78. package/src/acp/agent-process.ts +29 -26
  79. package/src/acp/auth-required.ts +102 -0
  80. package/src/acp/session-manager.test.ts +119 -0
  81. package/src/acp/session-manager.ts +68 -2
  82. package/src/api/events/acp-auth-required.ts +55 -0
  83. package/src/api/index.ts +7 -0
  84. package/src/apps/app-store.ts +3 -0
  85. package/src/bundler/package-resolver.ts +2 -30
  86. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  87. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  88. package/src/calls/call-controller.ts +9 -2
  89. package/src/calls/call-setup-flow.ts +0 -1
  90. package/src/calls/media-stream-stt-session.ts +15 -0
  91. package/src/calls/voice-session-bridge.ts +71 -16
  92. package/src/calls/voice-triage-escalate.ts +104 -2
  93. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  94. package/src/channels/config.ts +13 -0
  95. package/src/channels/plugin-channel-declarations.ts +108 -0
  96. package/src/channels/types.ts +30 -0
  97. package/src/cli/AGENTS.md +5 -2
  98. package/src/cli/commands/credentials.help.ts +2 -2
  99. package/src/cli/commands/inference-providers.ts +1 -1
  100. package/src/cli/commands/mcp.help.ts +13 -4
  101. package/src/cli/commands/mcp.ts +9 -0
  102. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  103. package/src/cli/commands/memory/index.help.ts +43 -1
  104. package/src/cli/commands/memory/memory-v3.ts +64 -0
  105. package/src/cli/commands/stt.help.ts +27 -2
  106. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  107. package/src/cli/lib/bundled-marketplace.json +13 -0
  108. package/src/cli/lib/upgrade-plugin.ts +42 -0
  109. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  110. package/src/config/__tests__/default-provider.test.ts +6 -1
  111. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  112. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  113. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  114. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  115. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  116. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  117. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  118. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  119. package/src/config/call-site-defaults.ts +4 -5
  120. package/src/config/default-profile-catalog.ts +82 -11
  121. package/src/config/default-profile-names.ts +4 -1
  122. package/src/config/default-provider-resolution.ts +4 -0
  123. package/src/config/llm-context-resolution.ts +11 -3
  124. package/src/config/llm-resolver.ts +28 -1
  125. package/src/config/profile-materialization.ts +70 -22
  126. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  127. package/src/config/schemas/call-site-catalog.ts +4 -4
  128. package/src/config/schemas/live-voice.ts +57 -23
  129. package/src/config/schemas/llm.ts +59 -32
  130. package/src/config/schemas/mcp.ts +23 -0
  131. package/src/config/schemas/plugin-updates.ts +6 -2
  132. package/src/config/schemas/stt.ts +1 -0
  133. package/src/context/outbound-sanitize.ts +96 -1
  134. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  135. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  136. package/src/daemon/conversation-agent-loop.ts +17 -6
  137. package/src/daemon/conversation-messaging.ts +5 -1
  138. package/src/daemon/conversation-notifiers.ts +9 -1
  139. package/src/daemon/conversation-process.ts +9 -6
  140. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  141. package/src/daemon/conversation-tool-setup.ts +1 -2
  142. package/src/daemon/conversation.ts +48 -0
  143. package/src/daemon/mcp-reload-service.ts +36 -6
  144. package/src/daemon/process-message.ts +13 -3
  145. package/src/daemon/providers-setup.ts +6 -3
  146. package/src/daemon/trust-context-types.ts +29 -0
  147. package/src/daemon/wake-conversation-ops.ts +3 -2
  148. package/src/documents/document-store.ts +138 -5
  149. package/src/hooks/hook-loader.ts +3 -3
  150. package/src/hooks/registry.ts +50 -6
  151. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  152. package/src/inbound/oauth-callback-url.ts +61 -0
  153. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  154. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  155. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  156. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  157. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  158. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  159. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  160. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  161. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  162. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  163. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  164. package/src/live-voice/live-voice-archive.ts +2 -0
  165. package/src/live-voice/live-voice-metrics.ts +57 -32
  166. package/src/live-voice/live-voice-photo.ts +1 -2
  167. package/src/live-voice/live-voice-session.ts +535 -314
  168. package/src/live-voice/progress-narration.ts +277 -0
  169. package/src/live-voice/protocol.ts +21 -1
  170. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  171. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  172. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  173. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  174. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  175. package/src/mcp/client.ts +16 -11
  176. package/src/mcp/effective-config.ts +113 -0
  177. package/src/mcp/manager.ts +11 -6
  178. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  179. package/src/mcp/mcp-oauth-provider.ts +205 -240
  180. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  181. package/src/monitoring/plugin-auto-update.ts +128 -24
  182. package/src/notifications/signal.ts +1 -0
  183. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  184. package/src/permissions/confirmation-guardian-request.ts +2 -2
  185. package/src/permissions/question-guardian-request.test.ts +14 -6
  186. package/src/permissions/question-guardian-request.ts +1 -2
  187. package/src/persistence/attachments-store.ts +8 -1
  188. package/src/persistence/bookmark-crud.ts +3 -7
  189. package/src/persistence/conversation-attention-store.ts +16 -45
  190. package/src/persistence/conversation-crud.ts +33 -4
  191. package/src/persistence/conversation-lineage.ts +9 -0
  192. package/src/persistence/conversation-queries.ts +108 -41
  193. package/src/persistence/delivery-crud.ts +38 -29
  194. package/src/persistence/external-conversation-store.ts +32 -4
  195. package/src/persistence/llm-request-log-store.ts +4 -10
  196. package/src/persistence/llm-usage-store.ts +8 -3
  197. package/src/persistence/message-reads.test.ts +197 -0
  198. package/src/persistence/message-reads.ts +211 -0
  199. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  200. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  201. package/src/persistence/real-user-turn-filter.ts +27 -3
  202. package/src/persistence/steps.ts +9 -0
  203. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  204. package/src/plugin-api/conversation-turn.ts +168 -5
  205. package/src/plugin-api/index.ts +21 -5
  206. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  207. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  208. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  209. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  210. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  211. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  212. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  213. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  214. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  215. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  216. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  217. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  218. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  219. package/src/plugins/defaults/memory/indexer.ts +3 -1
  220. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  221. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  222. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  223. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  224. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  225. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  226. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  227. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  228. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  229. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  230. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  231. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  232. package/src/plugins/defaults/memory/worker.ts +6 -3
  233. package/src/plugins/external-plugin-loader.ts +47 -0
  234. package/src/plugins/mcp-servers.ts +361 -0
  235. package/src/plugins/mtime-cache.ts +23 -49
  236. package/src/plugins/worker-plugin-surface.ts +33 -0
  237. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  238. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  239. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  240. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  241. package/src/providers/call-site-routing.ts +30 -3
  242. package/src/providers/connection-resolution.ts +194 -11
  243. package/src/providers/inference/auth.ts +6 -0
  244. package/src/providers/inference/connection-availability.ts +24 -2
  245. package/src/providers/inference/connections.ts +2 -0
  246. package/src/providers/model-intents.ts +26 -6
  247. package/src/providers/openai/codex-models.ts +2 -1
  248. package/src/providers/provider-send-message.ts +32 -3
  249. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  250. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  251. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  252. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  253. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  254. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  255. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  256. package/src/providers/speech-to-text/resolve.ts +25 -2
  257. package/src/routes/worker.ts +17 -5
  258. package/src/runtime/access-request-helper.ts +9 -12
  259. package/src/runtime/agent-wake.ts +3 -3
  260. package/src/runtime/pre-first-message-gate.ts +4 -0
  261. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  262. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  263. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  264. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  265. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  266. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  267. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  268. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  269. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  270. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  271. package/src/runtime/routes/channel-route-shared.ts +0 -6
  272. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  273. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  274. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  275. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  276. package/src/runtime/routes/default-provider-routes.ts +15 -0
  277. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  278. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  279. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  280. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  281. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  282. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  283. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  284. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  285. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  286. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  287. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  288. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  289. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  291. package/src/runtime/routes/log-export-routes.ts +3 -0
  292. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  293. package/src/runtime/routes/plugins-routes.ts +21 -3
  294. package/src/runtime/routes/stt-routes.ts +31 -25
  295. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  296. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  297. package/src/runtime/routes/user-route-import.ts +108 -0
  298. package/src/schedule/worker.ts +6 -0
  299. package/src/security/oauth2.ts +6 -22
  300. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  301. package/src/stt/__tests__/types.test.ts +94 -0
  302. package/src/stt/daemon-batch-transcriber.ts +10 -0
  303. package/src/stt/stt-stream-session.ts +8 -4
  304. package/src/stt/types.ts +103 -0
  305. package/src/subagent/manager.ts +1 -3
  306. package/src/subagent/types.ts +7 -6
  307. package/src/tools/acp/spawn.test.ts +97 -0
  308. package/src/tools/acp/spawn.ts +32 -0
  309. package/src/tools/document/document-tool.ts +12 -3
  310. package/src/tools/registry.ts +2 -1
  311. package/src/tools/workflows/run-workflow.ts +1 -2
  312. package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
  313. package/src/tts/reasoning-tag-filter.ts +110 -0
  314. package/src/workspace/byok-default-profile-ensure.ts +61 -18
  315. package/src/workspace/custom-profile-ensure.ts +4 -24
  316. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  317. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  318. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  319. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  320. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  321. package/src/workspace/migrations/registry.ts +8 -0
  322. package/src/workspace/provider-commit-message-generator.ts +7 -5
  323. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  324. package/src/live-voice/front-decision.ts +0 -476
@@ -20,16 +20,11 @@ import type {
20
20
  import * as acp from "@agentclientprotocol/sdk";
21
21
 
22
22
  import { getLogger } from "../util/logger.js";
23
+ import { AcpAuthRequiredError, isAcpAuthRequired } from "./auth-required.js";
23
24
  import type { AcpAgentConfig } from "./types.js";
24
25
 
25
26
  const log = getLogger("acp");
26
27
 
27
- /**
28
- * JSON-RPC error code agents use to signal that authentication is required
29
- * (matches the SDK's RequestError.authRequired()).
30
- */
31
- const AUTH_REQUIRED_CODE = -32000;
32
-
33
28
  /**
34
29
  * Rough byte cap for retained stderr. Oldest lines are evicted once the sum of
35
30
  * retained line lengths exceeds this, so the stderr ring (read via stderrSince)
@@ -37,19 +32,6 @@ const AUTH_REQUIRED_CODE = -32000;
37
32
  */
38
33
  const STDERR_RETENTION_BYTES = 4096;
39
34
 
40
- /**
41
- * Detects the ACP auth-required error. Checks the `code` property rather than
42
- * `instanceof acp.RequestError` so plain JSON-RPC error objects are also
43
- * recognized.
44
- */
45
- function isAuthRequiredError(err: unknown): boolean {
46
- return (
47
- typeof err === "object" &&
48
- err !== null &&
49
- (err as { code?: unknown }).code === AUTH_REQUIRED_CODE
50
- );
51
- }
52
-
53
35
  function isEnvVarMethod(
54
36
  method: AuthMethod,
55
37
  ): method is AuthMethodEnvVar & { type: "env_var" } {
@@ -296,7 +278,7 @@ export class AcpAgentProcess {
296
278
  try {
297
279
  return await op();
298
280
  } catch (err) {
299
- if (!isAuthRequiredError(err)) {
281
+ if (!isAcpAuthRequired(err)) {
300
282
  throw err;
301
283
  }
302
284
 
@@ -306,12 +288,23 @@ export class AcpAgentProcess {
306
288
 
307
289
  const method = this.selectEnvVarAuthMethod();
308
290
  if (!method) {
309
- throw new Error(
310
- `ACP agent "${this.agentId}" requires authentication. ` +
311
- `Advertised methods: ${this.describeAuthMethods()}. ` +
312
- "Set the required env var under acp.agents.<id>.env in config.json, " +
313
- "collect it securely via 'assistant credentials prompt --service acp --field <field> --label \"<label>\"', " +
314
- "or complete the agent's own login flow in the workspace.",
291
+ // Typed so the session manager can tell an auth failure from a crash
292
+ // and surface a re-authentication path. The remediation text is split
293
+ // by what the agent advertises: env-var and `credentials prompt`
294
+ // advice only helps when an env_var method exists to satisfy, and for
295
+ // terminal-login-only adapters it sends the user chasing a CLI
296
+ // workaround for a credential the app repairs on its own.
297
+ throw new AcpAuthRequiredError(
298
+ this.agentId,
299
+ this.hasEnvVarAuthMethod()
300
+ ? `ACP agent "${this.agentId}" requires authentication. ` +
301
+ `Advertised methods: ${this.describeAuthMethods()}. ` +
302
+ "Set the required env var under acp.agents.<id>.env in config.json, " +
303
+ "or collect it securely via 'assistant credentials prompt --service acp --field <field> --label \"<label>\"'."
304
+ : `ACP agent "${this.agentId}" requires authentication and its ` +
305
+ "stored credential was not accepted. Advertised methods: " +
306
+ `${this.describeAuthMethods()}. None can be completed headlessly, ` +
307
+ "so the credential has to be reconnected.",
315
308
  );
316
309
  }
317
310
 
@@ -325,6 +318,16 @@ export class AcpAgentProcess {
325
318
  }
326
319
  }
327
320
 
321
+ /**
322
+ * Whether the agent advertises any `env_var` auth method at all, satisfiable
323
+ * or not. Distinguishes "we could not satisfy the env var it wants" from
324
+ * "env vars are not how this agent authenticates", which are different
325
+ * problems with different fixes.
326
+ */
327
+ private hasEnvVarAuthMethod(): boolean {
328
+ return this.authMethods.some(isEnvVarMethod);
329
+ }
330
+
328
331
  /**
329
332
  * Renders the agent's advertised auth methods for error messages, e.g.
330
333
  * `"Login with ChatGPT" (chatgpt), "Use OPENAI_API_KEY" (env var OPENAI_API_KEY)`.
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Detection of Claude authentication failures on ACP runs.
3
+ *
4
+ * These failures reach the daemon in two shapes, and both are needed:
5
+ *
6
+ * 1. No credentials at all: the adapter raises a structured ACP
7
+ * `auth_required` rejection (JSON-RPC -32000). {@link isAcpAuthRequired}.
8
+ * 2. Credentials present but rejected (the expired/revoked case): the CLI
9
+ * surfaces the API 401 as an error message it authors, and the adapter
10
+ * relays it as a generic -32603 internal error, NOT as `auth_required`.
11
+ * {@link isClaudeAuthFailureMessage}.
12
+ *
13
+ * A positive detection becomes {@link ACP_CLAUDE_AUTH_REQUIRED_CODE} on the
14
+ * `acp_auth_required` event, which the client turns into the inline
15
+ * "Connect Claude Code" card.
16
+ */
17
+
18
+ export { ACP_CLAUDE_AUTH_REQUIRED_CODE } from "../api/events/acp-auth-required.js";
19
+
20
+ /**
21
+ * JSON-RPC error code agents use to signal that authentication is required
22
+ * (matches the ACP SDK's `RequestError.authRequired()`).
23
+ */
24
+ export const AUTH_REQUIRED_CODE = -32000;
25
+
26
+ /**
27
+ * An agent rejected an operation with `auth_required` and the daemon has no
28
+ * way to satisfy it. A distinct type so the classification survives to the
29
+ * session manager, where the failure becomes an event the UI can act on.
30
+ */
31
+ export class AcpAuthRequiredError extends Error {
32
+ constructor(
33
+ readonly agentId: string,
34
+ message: string,
35
+ ) {
36
+ super(message);
37
+ this.name = "AcpAuthRequiredError";
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Detects the structured auth-required signal in either shape it travels: the
43
+ * raw JSON-RPC rejection from the agent, or our own
44
+ * {@link AcpAuthRequiredError}. Checks the `code` property rather than an
45
+ * `instanceof` so plain JSON-RPC error objects are recognized too.
46
+ */
47
+ export function isAcpAuthRequired(err: unknown): boolean {
48
+ if (err instanceof AcpAuthRequiredError) {
49
+ return true;
50
+ }
51
+ return (
52
+ typeof err === "object" &&
53
+ err !== null &&
54
+ (err as { code?: unknown }).code === AUTH_REQUIRED_CODE
55
+ );
56
+ }
57
+
58
+ /**
59
+ * The Claude CLI's own auth-failure phrasings. Unanchored (no ^/$) because
60
+ * the messages arrive with adapter framing ("Internal error: ...") or as a
61
+ * line `deriveFailureError` picked out of stderr.
62
+ */
63
+ const CLAUDE_AUTH_FAILURE_PATTERNS: readonly RegExp[] = [
64
+ /Failed to authenticate\b/,
65
+ /Please run \/login/,
66
+ /\bNot logged in\b/,
67
+ ];
68
+
69
+ /**
70
+ * Whether a failure message is a Claude authentication failure. Callers gate
71
+ * on the adapter (`claude-agent-acp`) BEFORE consulting this: the patterns
72
+ * are Claude-specific, and the marker built on them promises a repair only
73
+ * the Connect Claude flow can perform.
74
+ */
75
+ export function isClaudeAuthFailureMessage(
76
+ message: string | undefined,
77
+ ): boolean {
78
+ return (
79
+ message != null &&
80
+ CLAUDE_AUTH_FAILURE_PATTERNS.some((pattern) => pattern.test(message))
81
+ );
82
+ }
83
+
84
+ /** The adapter whose auth failures the Connect Claude flow can repair. */
85
+ export const CLAUDE_ACP_COMMAND = "claude-agent-acp";
86
+
87
+ /**
88
+ * Model-facing text accompanying a Claude auth failure wherever the inline
89
+ * Connect card is raised (the tool result for a pre-spawn rejection, the
90
+ * parent notification for a post-spawn one), so the model points at the card
91
+ * instead of inventing a remedy (CLI commands, pasted tokens, or a card that
92
+ * is not there). Bans placement words because card position is a UI detail
93
+ * the model cannot see.
94
+ */
95
+ export const ACP_AUTH_RECOVERY_GUIDANCE =
96
+ "The Claude Code connection needs to be re-authorized. The app shows the " +
97
+ 'user an inline "Connect Claude Code" card. Reply with ONE short sentence: ' +
98
+ "ask them to click Connect to sign in again, and tell them you'll continue " +
99
+ "automatically once they are connected. Do NOT say where the card is; never " +
100
+ 'say "below", "above", "at the bottom", or "here". Do NOT tell them to run ' +
101
+ "`claude setup-token`, paste a token in chat, run credential CLI commands, " +
102
+ "or re-run the agent yourself; the card and auto-continue handle it.";
@@ -5,6 +5,7 @@ import {
5
5
  deleteConversation,
6
6
  setConversation,
7
7
  } from "../daemon/conversation-registry.js";
8
+ import { hasAcpConnectCardRaised } from "./acp-connect-card-state.js";
8
9
  import { VellumAcpClientHandler } from "./client-handler.js";
9
10
  import { AcpSessionManager } from "./session-manager.js";
10
11
 
@@ -310,3 +311,121 @@ describe("AcpSessionManager parent notification", () => {
310
311
  expect((manager.getStatus() as unknown[]).length).toBe(1);
311
312
  });
312
313
  });
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // Auth-required recovery surface
317
+ // ---------------------------------------------------------------------------
318
+
319
+ describe("AcpSessionManager auth-required recovery surface", () => {
320
+ /** The live-captured shape of a rejected-credential failure. */
321
+ const authFailure = () =>
322
+ Promise.reject(
323
+ new Error(
324
+ "Internal error: Failed to authenticate. API Error: 401 OAuth access token has been revoked.",
325
+ ),
326
+ );
327
+
328
+ /** Drives an auth-shaped prompt failure through firePromptInBackground. */
329
+ async function driveAuthFailure(opts: {
330
+ id: string;
331
+ command: string;
332
+ parentToolUseId?: string;
333
+ cancelled?: boolean;
334
+ }) {
335
+ const manager = new AcpSessionManager(1);
336
+ const parentId = `parent-${opts.id}`;
337
+ const { conversation, persistUserMessage, loopRan } = mockConversation();
338
+ setConversation(parentId, conversation);
339
+ registered.push(parentId);
340
+
341
+ const entry = injectSession(
342
+ manager,
343
+ opts.id,
344
+ parentId,
345
+ fakeProcess(authFailure),
346
+ );
347
+ entry.command = opts.command;
348
+ (entry as { parentToolUseId?: string }).parentToolUseId =
349
+ opts.parentToolUseId;
350
+ if (opts.cancelled) {
351
+ entry.state.status = "cancelled";
352
+ }
353
+
354
+ await fire(manager, opts.id, entry).catch(() => {});
355
+ if (!opts.cancelled) {
356
+ await loopRan;
357
+ }
358
+
359
+ const events = (
360
+ entry.sendToVellum as ReturnType<typeof mock>
361
+ ).mock.calls.map((c) => c[0] as { type: string } & Record<string, unknown>);
362
+ const firstPersist = persistUserMessage.mock.calls[0] as unknown as
363
+ | [{ content: string }]
364
+ | undefined;
365
+ return {
366
+ parentId,
367
+ authEvent: events.find((e) => e.type === "acp_auth_required"),
368
+ persistedContent: firstPersist?.[0].content,
369
+ };
370
+ }
371
+
372
+ test("claude failure with an anchor raises the full surface: event, registry mark, guidance", async () => {
373
+ const r = await driveAuthFailure({
374
+ id: "sess-auth-anchor",
375
+ command: "claude-agent-acp",
376
+ parentToolUseId: "tool-anchor-1",
377
+ });
378
+
379
+ expect(r.authEvent).toMatchObject({
380
+ acpSessionId: "sess-auth-anchor",
381
+ authCode: "acp_claude_auth_required",
382
+ agent: "claude",
383
+ parentToolUseId: "tool-anchor-1",
384
+ });
385
+ // The credential-prompt route consults this registry to redirect a
386
+ // redundant secure prompt at the card instead of stacking a second one.
387
+ expect(hasAcpConnectCardRaised(r.parentId)).toBe(true);
388
+ expect(r.persistedContent).toContain("Connect Claude Code");
389
+ });
390
+
391
+ test("claude failure without an anchor keeps the plain failure: no event, no mark, no guidance", async () => {
392
+ // No spawning tool call means no transcript row to render the card under;
393
+ // guidance would point the model at a card that cannot appear.
394
+ const r = await driveAuthFailure({
395
+ id: "sess-auth-noanchor",
396
+ command: "claude-agent-acp",
397
+ });
398
+
399
+ expect(r.authEvent).toBeUndefined();
400
+ expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
401
+ expect(r.persistedContent).not.toContain("Connect Claude Code");
402
+ });
403
+
404
+ test("a run the user already stopped raises nothing: no event, no registry mark, no parent turn", async () => {
405
+ // The client never renders a card for a cancelled run, and the
406
+ // prompt-dedup registry is never cleared, so marking it here would
407
+ // suppress the secure token prompt for the daemon's lifetime against a
408
+ // card that does not exist.
409
+ const r = await driveAuthFailure({
410
+ id: "sess-auth-cancelled",
411
+ command: "claude-agent-acp",
412
+ parentToolUseId: "tool-anchor-3",
413
+ cancelled: true,
414
+ });
415
+
416
+ expect(r.authEvent).toBeUndefined();
417
+ expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
418
+ expect(r.persistedContent).toBeUndefined();
419
+ });
420
+
421
+ test("a non-claude adapter never raises the surface, even on an auth-shaped failure", async () => {
422
+ const r = await driveAuthFailure({
423
+ id: "sess-auth-codex",
424
+ command: "codex-acp",
425
+ parentToolUseId: "tool-anchor-2",
426
+ });
427
+
428
+ expect(r.authEvent).toBeUndefined();
429
+ expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
430
+ });
431
+ });
@@ -15,7 +15,15 @@ import { getDb } from "../persistence/db-connection.js";
15
15
  import { acpSessionHistory } from "../persistence/schema/index.js";
16
16
  import * as pendingInteractions from "../runtime/pending-interactions.js";
17
17
  import { getLogger } from "../util/logger.js";
18
+ import { markAcpConnectCardRaised } from "./acp-connect-card-state.js";
18
19
  import { AcpAgentProcess } from "./agent-process.js";
20
+ import {
21
+ ACP_AUTH_RECOVERY_GUIDANCE,
22
+ ACP_CLAUDE_AUTH_REQUIRED_CODE,
23
+ CLAUDE_ACP_COMMAND,
24
+ isAcpAuthRequired,
25
+ isClaudeAuthFailureMessage,
26
+ } from "./auth-required.js";
19
27
  import { resolveAgentWithAutoInstall } from "./auto-install.js";
20
28
  import { VellumAcpClientHandler } from "./client-handler.js";
21
29
  import { deriveFailureError } from "./failure-error.js";
@@ -26,6 +34,30 @@ import type { AcpAgentConfig, AcpSessionState } from "./types.js";
26
34
 
27
35
  const log = getLogger("acp:session-manager");
28
36
 
37
+ /**
38
+ * The `authCode` for a run that failed on its Claude credential, or undefined
39
+ * for any other failure. Adapter-gated first: the signals are Claude-specific,
40
+ * and the marker promises a repair only the Connect Claude flow can perform.
41
+ * Checks both auth-failure shapes (see `auth-required.ts`) against both the
42
+ * raw rejection and the derived failure message, since `deriveFailureError`
43
+ * may replace one with the other.
44
+ */
45
+ function claudeAuthRequiredCode(
46
+ err: unknown,
47
+ failureMessage: string,
48
+ entry: { command: string },
49
+ ): string | undefined {
50
+ if (entry.command !== CLAUDE_ACP_COMMAND) {
51
+ return undefined;
52
+ }
53
+ const rawMessage = err instanceof Error ? err.message : String(err);
54
+ return isAcpAuthRequired(err) ||
55
+ isClaudeAuthFailureMessage(rawMessage) ||
56
+ isClaudeAuthFailureMessage(failureMessage)
57
+ ? ACP_CLAUDE_AUTH_REQUIRED_CODE
58
+ : undefined;
59
+ }
60
+
29
61
  /**
30
62
  * The manager's "unknown session id" error. Thrown whenever an operation
31
63
  * references an acpSessionId with no in-memory entry (and, for resume, no
@@ -1093,6 +1125,11 @@ export class AcpSessionManager {
1093
1125
  err.message,
1094
1126
  current.process.stderrSince(stderrMark),
1095
1127
  );
1128
+ const errorCode = claudeAuthRequiredCode(
1129
+ err,
1130
+ failureMessage,
1131
+ current,
1132
+ );
1096
1133
  if (current.state.status !== "cancelled") {
1097
1134
  current.state.status = "failed";
1098
1135
  current.state.completedAt = Date.now();
@@ -1100,14 +1137,40 @@ export class AcpSessionManager {
1100
1137
  }
1101
1138
  current.currentPrompt = null;
1102
1139
  log.error(
1103
- { acpSessionId, error: err.message, failureMessage },
1140
+ { acpSessionId, error: err.message, failureMessage, errorCode },
1104
1141
  "ACP prompt failed",
1105
1142
  );
1143
+ // `acp_session_error` keeps its pre-existing shape so older packaged
1144
+ // clients still parse it; the recovery signal rides its own event
1145
+ // below (see `api/events/acp-auth-required.ts`).
1106
1146
  current.sendToVellum({
1107
1147
  type: "acp_session_error",
1108
1148
  acpSessionId,
1109
1149
  error: failureMessage,
1110
1150
  });
1151
+ // The recovery surface (event, model guidance, prompt-dedup marker)
1152
+ // is raised as a unit, and only when a spawning tool call exists to
1153
+ // anchor the card AND the user did not already stop the run. A
1154
+ // cancelled run renders no card (the client guards it), so marking
1155
+ // the never-cleared prompt-dedup registry here would suppress the
1156
+ // secure token prompt against a card that never appears.
1157
+ const recoveryAnchor =
1158
+ errorCode !== undefined && current.state.status !== "cancelled"
1159
+ ? current.parentToolUseId
1160
+ : undefined;
1161
+ if (errorCode !== undefined && recoveryAnchor !== undefined) {
1162
+ current.sendToVellum({
1163
+ type: "acp_auth_required",
1164
+ acpSessionId,
1165
+ authCode: errorCode,
1166
+ agent: current.state.agentId,
1167
+ parentToolUseId: recoveryAnchor,
1168
+ });
1169
+ // Same registry as the missing-token spawn path, so the
1170
+ // credential-prompt route redirects a redundant secure prompt at
1171
+ // the card instead of stacking a second one.
1172
+ markAcpConnectCardRaised(current.parentConversationId);
1173
+ }
1111
1174
 
1112
1175
  // Persist the terminal row before teardown clears the buffer.
1113
1176
  this.persistTerminal(acpSessionId, current);
@@ -1123,7 +1186,10 @@ export class AcpSessionManager {
1123
1186
  if (current.state.status !== "cancelled") {
1124
1187
  this.notifyParent(
1125
1188
  current,
1126
- `[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}`,
1189
+ `[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}` +
1190
+ (recoveryAnchor !== undefined
1191
+ ? `\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`
1192
+ : ""),
1127
1193
  );
1128
1194
  }
1129
1195
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `acp_auth_required` SSE event.
3
+ *
4
+ * Server to client notification that an ACP run failed because the agent
5
+ * rejected its stored credential and the client can offer a re-authentication
6
+ * affordance. Always emitted immediately AFTER the `acp_session_error` that
7
+ * carries the human-readable failure.
8
+ *
9
+ * It is a separate event rather than a field on `acp_session_error` because
10
+ * every event schema here is `.strict()` and clients fall back to an inert
11
+ * `unknown` event when parsing fails: a new FIELD makes an older packaged
12
+ * client (iOS/macOS bundle the web app) reject the whole error event and
13
+ * render the run as still active, while an unknown event TYPE is simply
14
+ * ignored. Additive signals on existing events need their own event type for
15
+ * the same reason.
16
+ *
17
+ * Canonical wire-contract source. Re-exported to external consumers via
18
+ * `@vellumai/assistant-api` (the `api/index.ts` barrel).
19
+ */
20
+
21
+ import { z } from "zod";
22
+
23
+ /**
24
+ * The code for a `claude-agent-acp` run whose Claude credential needs to be
25
+ * reconnected: the `authCode` on this event for post-spawn failures, and the
26
+ * `errorCode` on the failed `acp_spawn` tool result for pre-spawn ones.
27
+ * Single source for both sides of the wire: the daemon re-exports it via
28
+ * `acp/auth-required.ts`, the web client via
29
+ * `domains/chat/utils/acp-connect.ts`.
30
+ */
31
+ export const ACP_CLAUDE_AUTH_REQUIRED_CODE = "acp_claude_auth_required";
32
+
33
+ export const AcpAuthRequiredEventSchema = z
34
+ .object({
35
+ type: z.literal("acp_auth_required"),
36
+ acpSessionId: z.string(),
37
+ /**
38
+ * Machine-readable classification of what kind of re-authentication is
39
+ * needed, so clients branch on a stable value rather than on the failure
40
+ * text (which varies by adapter and failure mode). Currently only
41
+ * {@link ACP_CLAUDE_AUTH_REQUIRED_CODE}.
42
+ */
43
+ authCode: z.string(),
44
+ /** Agent id the run was spawned with, for display. */
45
+ agent: z.string(),
46
+ /**
47
+ * Tool-use id of the `acp_spawn` call that started this run, so the
48
+ * client anchors the inline affordance to the right transcript row.
49
+ * Absent when the run was not started by a tool call.
50
+ */
51
+ parentToolUseId: z.string().optional(),
52
+ })
53
+ .strict();
54
+
55
+ export type AcpAuthRequiredEvent = z.infer<typeof AcpAuthRequiredEventSchema>;
package/src/api/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { AcpAuthRequiredEventSchema } from "./events/acp-auth-required.js";
3
4
  import { AcpSessionCompletedEventSchema } from "./events/acp-session-completed.js";
4
5
  import { AcpSessionErrorEventSchema } from "./events/acp-session-error.js";
5
6
  import { AcpSessionSpawnedEventSchema } from "./events/acp-session-spawned.js";
@@ -149,6 +150,11 @@ export {
149
150
  SSE_REPLAY_RING_COUNT_LIMIT,
150
151
  } from "./constants/sse-replay.js";
151
152
  export { DEFAULT_TOOL_EXECUTION_TIMEOUT_SEC } from "./constants/tool-execution.js";
153
+ export {
154
+ ACP_CLAUDE_AUTH_REQUIRED_CODE,
155
+ type AcpAuthRequiredEvent,
156
+ AcpAuthRequiredEventSchema,
157
+ } from "./events/acp-auth-required.js";
152
158
  export {
153
159
  type AcpSessionCompletedEvent,
154
160
  AcpSessionCompletedEventSchema,
@@ -895,6 +901,7 @@ export {
895
901
  * migration recipe.
896
902
  */
897
903
  export const AssistantEventSchema = z.discriminatedUnion("type", [
904
+ AcpAuthRequiredEventSchema,
898
905
  AcpSessionCompletedEventSchema,
899
906
  AcpSessionErrorEventSchema,
900
907
  AcpSessionSpawnedEventSchema,
@@ -1422,6 +1422,9 @@ export function backfillAppConversationIds(): void {
1422
1422
  }
1423
1423
 
1424
1424
  try {
1425
+ // Unfinalized rows cannot match: their content is a { ref } pointer,
1426
+ // never inline surface JSON, so this scan sees finalized content only by
1427
+ // shape. No completeness predicate needed.
1425
1428
  const rows = rawAll<{ conversation_id: string; content: string }>(
1426
1429
  "apps:backfillConversationIds",
1427
1430
  `SELECT conversation_id, content FROM messages WHERE content LIKE '%"type":"ui_surface"%'`,
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { existsSync } from "node:fs";
9
- import { mkdir, stat } from "node:fs/promises";
9
+ import { mkdir } from "node:fs/promises";
10
10
  import { join } from "node:path";
11
11
 
12
12
  import { ensureBun } from "../util/bun-runtime.js";
@@ -24,8 +24,8 @@ export const ALLOWED_PACKAGES: readonly string[] = [
24
24
  "clsx",
25
25
  ] as const;
26
26
 
27
+ /** Bounds a runaway install; the allowlist above bounds what may be installed. */
27
28
  const INSTALL_TIMEOUT_MS = 10_000;
28
- const MAX_PACKAGE_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB
29
29
 
30
30
  /** In-flight install promises keyed by package name, to deduplicate concurrent requests. */
31
31
  const inflight = new Map<string, Promise<string | null>>();
@@ -148,37 +148,9 @@ async function installPackage(
148
148
  return null;
149
149
  }
150
150
 
151
- // Enforce max size
152
- if (existsSync(pkgDir)) {
153
- const size = await dirSize(pkgDir);
154
- if (size > MAX_PACKAGE_SIZE_BYTES) {
155
- log.warn({ pkg, size }, "Package exceeds size limit, removing");
156
- const { rm } = await import("node:fs/promises");
157
- await rm(pkgDir, { recursive: true, force: true });
158
- return null;
159
- }
160
- }
161
-
162
151
  return existsSync(pkgDir) ? nodeModulesDir : null;
163
152
  } catch (err) {
164
153
  log.warn({ pkg, err }, "Package resolution failed");
165
154
  return null;
166
155
  }
167
156
  }
168
-
169
- /** Recursively sum file sizes under a directory. */
170
- async function dirSize(dir: string): Promise<number> {
171
- const { readdir } = await import("node:fs/promises");
172
- const entries = await readdir(dir, { withFileTypes: true });
173
- let total = 0;
174
- for (const entry of entries) {
175
- const full = join(dir, entry.name);
176
- if (entry.isDirectory()) {
177
- total += await dirSize(full);
178
- } else {
179
- const s = await stat(full);
180
- total += s.size;
181
- }
182
- }
183
- return total;
184
- }