@vellumai/assistant 0.11.7 → 0.11.8-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 (446) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +1 -1
  4. package/docs/error-handling.md +1 -2
  5. package/docs/guardian-request-flow.md +51 -5
  6. package/docs/trusted-contact-access.md +13 -12
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
  9. package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
  10. package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
  11. package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
  12. package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
  13. package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  18. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
  19. package/node_modules/@vellumai/environments/package.json +2 -1
  20. package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
  21. package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  26. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
  30. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
  31. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
  32. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
  33. package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
  34. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
  35. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  36. package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  37. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  38. package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  39. package/openapi.yaml +481 -9
  40. package/package.json +3 -1
  41. package/scripts/sync-llm-catalog.ts +3 -0
  42. package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
  43. package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
  44. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
  45. package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
  46. package/src/__tests__/anthropic-provider.test.ts +56 -0
  47. package/src/__tests__/approval-cascade.test.ts +0 -13
  48. package/src/__tests__/attachments-store.test.ts +117 -6
  49. package/src/__tests__/attachments.test.ts +122 -0
  50. package/src/__tests__/bundled-asset.test.ts +22 -1
  51. package/src/__tests__/channel-approval.test.ts +14 -7
  52. package/src/__tests__/channel-approvals.test.ts +3 -3
  53. package/src/__tests__/channel-delivery-store.test.ts +58 -0
  54. package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
  55. package/src/__tests__/channel-invite-transport.test.ts +1 -0
  56. package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
  57. package/src/__tests__/compaction-events.test.ts +0 -12
  58. package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
  59. package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
  60. package/src/__tests__/config-schema.test.ts +35 -0
  61. package/src/__tests__/contacts-write.test.ts +141 -3
  62. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
  63. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
  64. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
  65. package/src/__tests__/conversation-agent-loop.test.ts +95 -13
  66. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
  67. package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
  68. package/src/__tests__/conversation-media-retry.test.ts +33 -2
  69. package/src/__tests__/conversation-pairing.test.ts +54 -0
  70. package/src/__tests__/conversation-process-callsite.test.ts +0 -12
  71. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
  72. package/src/__tests__/conversation-queue.test.ts +4 -13
  73. package/src/__tests__/conversation-retry-route.test.ts +5 -0
  74. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  75. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  76. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  77. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  78. package/src/__tests__/conversation-sight-frames.test.ts +615 -0
  79. package/src/__tests__/conversation-slash-queue.test.ts +0 -13
  80. package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
  81. package/src/__tests__/conversation-speed-override.test.ts +0 -12
  82. package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
  83. package/src/__tests__/conversation-summarize-route.test.ts +5 -0
  84. package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
  85. package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
  86. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
  87. package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
  88. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
  89. package/src/__tests__/credential-health-service.test.ts +45 -0
  90. package/src/__tests__/credential-record-retire.test.ts +256 -0
  91. package/src/__tests__/credential-record-write-through.test.ts +8 -4
  92. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
  93. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
  94. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
  95. package/src/__tests__/delete-propagation.test.ts +121 -3
  96. package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
  97. package/src/__tests__/file-ops-service.test.ts +8 -3
  98. package/src/__tests__/gemini-provider.test.ts +64 -0
  99. package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
  100. package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
  101. package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
  102. package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
  103. package/src/__tests__/guardian-gateway-sim.ts +56 -20
  104. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
  105. package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
  106. package/src/__tests__/guardian-wait-controller.test.ts +6 -0
  107. package/src/__tests__/headless-browser-mode.test.ts +27 -0
  108. package/src/__tests__/helpers/mock-conversation.ts +2 -0
  109. package/src/__tests__/list-messages-system-card.test.ts +70 -0
  110. package/src/__tests__/llm-catalog-parity.test.ts +2 -0
  111. package/src/__tests__/managed-profile-guard.test.ts +2 -2
  112. package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
  113. package/src/__tests__/media-stream-server-integration.test.ts +6 -0
  114. package/src/__tests__/non-member-access-request.test.ts +12 -0
  115. package/src/__tests__/notification-discord-adapter.test.ts +76 -4
  116. package/src/__tests__/oauth-commands-routes.test.ts +21 -0
  117. package/src/__tests__/openai-provider.test.ts +63 -0
  118. package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
  119. package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
  120. package/src/__tests__/persist-unsendable-image.test.ts +19 -0
  121. package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
  122. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  123. package/src/__tests__/process-message-background-slack.test.ts +1 -1
  124. package/src/__tests__/process-message-display-content.test.ts +1 -1
  125. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
  126. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
  127. package/src/__tests__/qdrant-manager.test.ts +6 -0
  128. package/src/__tests__/reaction-persistence.test.ts +58 -62
  129. package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
  130. package/src/__tests__/server-history-render.test.ts +22 -0
  131. package/src/__tests__/sight-frame-retention.test.ts +362 -0
  132. package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
  133. package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
  134. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  135. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  136. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  137. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
  138. package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
  139. package/src/__tests__/terminal-tools.test.ts +9 -9
  140. package/src/__tests__/tool-result-spool.test.ts +5 -8
  141. package/src/__tests__/usage-attribution.test.ts +26 -0
  142. package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
  143. package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
  144. package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
  145. package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
  146. package/src/acp/acp-auth-marker-store.ts +196 -0
  147. package/src/acp/acp-claude-oauth.ts +154 -4
  148. package/src/acp/acp-connect-card-state.ts +49 -8
  149. package/src/acp/agent-process.ts +1 -0
  150. package/src/acp/auto-install.ts +1 -0
  151. package/src/acp/client-handler.ts +1 -0
  152. package/src/acp/prepare-agent-env.ts +79 -1
  153. package/src/acp/resolve-agent.ts +12 -0
  154. package/src/acp/session-manager.test.ts +183 -0
  155. package/src/acp/session-manager.ts +110 -6
  156. package/src/acp/types.ts +17 -0
  157. package/src/agent/attachments.ts +7 -2
  158. package/src/agent/loop.ts +59 -1
  159. package/src/api/events/contact-form-closed.ts +32 -0
  160. package/src/api/events/contact-record-request.ts +63 -0
  161. package/src/api/events/contact-request.ts +10 -3
  162. package/src/api/index.ts +20 -0
  163. package/src/api/responses/conversation-message.ts +20 -0
  164. package/src/api/responses/home.ts +101 -0
  165. package/src/approvals/AGENTS.md +1 -1
  166. package/src/approvals/guardian-card-withdrawal.ts +127 -30
  167. package/src/approvals/guardian-decision-primitive.ts +9 -0
  168. package/src/approvals/guardian-expiry-notifier.ts +22 -16
  169. package/src/approvals/guardian-request-status-sync.ts +98 -0
  170. package/src/avatar/svg-compositor.ts +1 -1
  171. package/src/avatar/traits-png-sync.ts +1 -1
  172. package/src/bundler/app-compiler.ts +1 -0
  173. package/src/bundler/compiler-tools.ts +1 -0
  174. package/src/bundler/package-resolver.ts +1 -0
  175. package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
  176. package/src/calls/guardian-action-sweep.ts +6 -1
  177. package/src/calls/voice-session-bridge.ts +46 -1
  178. package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
  179. package/src/channels/gateway-guardian-requests.ts +37 -18
  180. package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
  181. package/src/cli/commands/__tests__/browser.test.ts +65 -1
  182. package/src/cli/commands/browser.ts +30 -10
  183. package/src/cli/commands/contacts.help.ts +173 -11
  184. package/src/cli/commands/contacts.ts +343 -4
  185. package/src/cli/commands/db/refresh.ts +1 -0
  186. package/src/cli/commands/oauth/index.help.ts +6 -3
  187. package/src/cli/commands/oauth/request.test.ts +37 -0
  188. package/src/cli/commands/oauth/request.ts +7 -6
  189. package/src/cli/lib/bundled-marketplace.json +1 -1
  190. package/src/cli/lib/install-from-github.ts +20 -19
  191. package/src/cli/lib/install-plugin-dependencies.ts +3 -5
  192. package/src/cli/lib/merge-plugin-tree.ts +1 -0
  193. package/src/cli/lib/publish-plugin.ts +4 -1
  194. package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
  195. package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
  196. package/src/config/balanced-model-experiment.ts +15 -9
  197. package/src/config/bundled-skills/contacts/SKILL.md +61 -18
  198. package/src/config/default-profile-catalog.ts +12 -11
  199. package/src/config/feature-flag-registry.json +22 -13
  200. package/src/config/schema.ts +2 -0
  201. package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
  202. package/src/config/schemas/live-voice.ts +7 -0
  203. package/src/config/schemas/sight.ts +45 -0
  204. package/src/config/skills.ts +2 -1
  205. package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
  206. package/src/contacts/contact-store.ts +47 -4
  207. package/src/contacts/contacts-write.ts +5 -2
  208. package/src/contacts/mirror-reconciler.ts +210 -0
  209. package/src/contacts/mirror-tombstones.ts +42 -0
  210. package/src/contacts/notify-contacts-changed.ts +11 -2
  211. package/src/context/compactor.ts +47 -19
  212. package/src/context/tool-result-spool.ts +21 -14
  213. package/src/conversations/message-consolidation.ts +2 -2
  214. package/src/credential-health/credential-health-service.ts +37 -6
  215. package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
  216. package/src/daemon/conversation-agent-loop.ts +100 -1
  217. package/src/daemon/conversation-media-retry.ts +11 -6
  218. package/src/daemon/conversation-messaging.ts +293 -30
  219. package/src/daemon/conversation-runtime-assembly.ts +67 -0
  220. package/src/daemon/conversation-sight-frames.ts +187 -0
  221. package/src/daemon/conversation-store.ts +18 -28
  222. package/src/daemon/conversation-surfaces.ts +12 -2
  223. package/src/daemon/conversation-tool-setup.ts +39 -10
  224. package/src/daemon/conversation.ts +159 -16
  225. package/src/daemon/daemon-control.ts +6 -17
  226. package/src/daemon/handlers/config-model.test.ts +1 -0
  227. package/src/daemon/handlers/conversations.ts +10 -11
  228. package/src/daemon/install-assistant-command.ts +1 -0
  229. package/src/daemon/lifecycle.ts +10 -13
  230. package/src/daemon/message-types/sync.ts +4 -0
  231. package/src/daemon/process-message.ts +2 -17
  232. package/src/daemon/reaction-history-render.test.ts +162 -0
  233. package/src/daemon/reaction-history-render.ts +115 -0
  234. package/src/daemon/reaction-record.test.ts +162 -0
  235. package/src/daemon/reaction-record.ts +83 -0
  236. package/src/daemon/shutdown-handlers.ts +0 -2
  237. package/src/daemon/summarize-boundary.ts +12 -0
  238. package/src/daemon/video-thumbnail.ts +1 -1
  239. package/src/home/feed-types.ts +4 -0
  240. package/src/home/feed-writer.ts +25 -0
  241. package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
  242. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
  243. package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
  244. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
  245. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
  246. package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
  247. package/src/live-voice/live-voice-photo.ts +420 -65
  248. package/src/live-voice/live-voice-session.ts +311 -27
  249. package/src/live-voice/protocol.ts +147 -11
  250. package/src/messaging/provider-message-metadata.ts +7 -0
  251. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
  252. package/src/messaging/providers/channel-transport.ts +37 -0
  253. package/src/messaging/providers/discord/api.ts +4 -4
  254. package/src/messaging/providers/discord/send.test.ts +177 -2
  255. package/src/messaging/providers/discord/send.ts +197 -10
  256. package/src/messaging/providers/discord/transport.ts +10 -0
  257. package/src/messaging/providers/index.ts +42 -0
  258. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
  259. package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
  260. package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
  261. package/src/messaging/providers/slack/adapter.ts +112 -27
  262. package/src/messaging/providers/slack/api.test.ts +10 -0
  263. package/src/messaging/providers/slack/auth.ts +32 -6
  264. package/src/messaging/providers/slack/client.ts +0 -15
  265. package/src/messaging/providers/slack/message-content.ts +137 -0
  266. package/src/messaging/providers/slack/send.test.ts +43 -2
  267. package/src/messaging/providers/slack/send.ts +18 -5
  268. package/src/messaging/providers/slack/transport.ts +10 -0
  269. package/src/messaging/providers/slack/types.ts +21 -2
  270. package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
  271. package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
  272. package/src/messaging/providers/telegram-bot/send.ts +41 -1
  273. package/src/messaging/providers/telegram-bot/transport.ts +10 -0
  274. package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
  275. package/src/messaging/reaction-envelopes.ts +79 -0
  276. package/src/monitoring/control.ts +2 -2
  277. package/src/monitoring/page-cache.ts +5 -1
  278. package/src/notifications/AGENTS.md +1 -1
  279. package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
  280. package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
  281. package/src/notifications/adapters/discord.ts +62 -9
  282. package/src/notifications/conversation-pairing.ts +31 -4
  283. package/src/notifications/emit-signal.ts +8 -5
  284. package/src/notifications/guardian-delivery-recorder.ts +47 -6
  285. package/src/notifications/guardian-feed-projection.ts +492 -0
  286. package/src/notifications/home-feed-side-effect.ts +59 -11
  287. package/src/oauth/byo-connection.test.ts +22 -0
  288. package/src/oauth/byo-connection.ts +11 -5
  289. package/src/oauth/connection.ts +3 -2
  290. package/src/oauth/platform-connection.test.ts +35 -0
  291. package/src/oauth/platform-connection.ts +18 -13
  292. package/src/oauth/scope-utils.ts +48 -0
  293. package/src/oauth/seed-providers.ts +12 -0
  294. package/src/outbound-proxy/certs.ts +5 -4
  295. package/src/permissions/question-guardian-request.test.ts +7 -1
  296. package/src/persistence/attachments-store.ts +134 -31
  297. package/src/persistence/conversation-crud.ts +137 -4
  298. package/src/persistence/conversation-types.ts +109 -0
  299. package/src/persistence/db-connection.ts +3 -21
  300. package/src/persistence/delivery-crud.ts +42 -12
  301. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
  302. package/src/persistence/embeddings/embedding-local.ts +7 -112
  303. package/src/persistence/embeddings/qdrant-manager.ts +4 -1
  304. package/src/persistence/llm-request-log-store.ts +1 -1
  305. package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
  306. package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
  307. package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
  308. package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
  309. package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
  310. package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
  311. package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
  312. package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
  313. package/src/persistence/schema/acp.ts +25 -0
  314. package/src/persistence/steps.ts +8 -0
  315. package/src/platform/platform-patch-queue.test.ts +43 -41
  316. package/src/plugin-api/index.ts +12 -0
  317. package/src/plugins/defaults/compaction/window-manager.ts +2 -0
  318. package/src/plugins/defaults/image-recovery/recover.ts +12 -1
  319. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
  320. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
  321. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
  322. package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
  323. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
  324. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
  325. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
  326. package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
  327. package/src/plugins/defaults/memory/worker-control.ts +1 -2
  328. package/src/providers/content-block-schema.ts +1 -0
  329. package/src/providers/fireworks/client.ts +9 -1
  330. package/src/providers/gemini/client.ts +22 -15
  331. package/src/providers/media-resolve.ts +24 -10
  332. package/src/providers/model-catalog.ts +91 -0
  333. package/src/providers/openai/chat-completions-provider.ts +44 -5
  334. package/src/providers/retry.ts +16 -11
  335. package/src/providers/types.ts +56 -0
  336. package/src/routes/__tests__/control.test.ts +4 -3
  337. package/src/routes/control.ts +1 -1
  338. package/src/routes/route-host-client.ts +1 -1
  339. package/src/runtime/AGENTS.md +30 -12
  340. package/src/runtime/__tests__/agent-wake.test.ts +71 -6
  341. package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
  342. package/src/runtime/access-request-helper.ts +15 -7
  343. package/src/runtime/agent-wake.ts +16 -4
  344. package/src/runtime/channel-approval-types.ts +8 -5
  345. package/src/runtime/channel-invite-transport.ts +2 -0
  346. package/src/runtime/channel-invite-transports/discord.ts +27 -0
  347. package/src/runtime/channel-readiness-service.ts +4 -4
  348. package/src/runtime/channel-reply-delivery.ts +72 -28
  349. package/src/runtime/guardian-action-service.ts +9 -5
  350. package/src/runtime/guardian-form-registry.ts +256 -0
  351. package/src/runtime/guardian-reply-router.ts +33 -14
  352. package/src/runtime/invite-instruction-generator.ts +14 -2
  353. package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
  354. package/src/runtime/no-response.ts +13 -74
  355. package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
  356. package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
  357. package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
  358. package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
  359. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
  360. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
  361. package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
  362. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
  363. package/src/runtime/routes/acp-routes-list.test.ts +58 -0
  364. package/src/runtime/routes/acp-routes.ts +253 -47
  365. package/src/runtime/routes/app-management-routes.ts +80 -47
  366. package/src/runtime/routes/approval-routes.ts +9 -0
  367. package/src/runtime/routes/archive-utils.ts +6 -1
  368. package/src/runtime/routes/avatar-routes.ts +1 -1
  369. package/src/runtime/routes/channel-route-shared.ts +18 -20
  370. package/src/runtime/routes/contact-prompt-routes.ts +271 -105
  371. package/src/runtime/routes/conversation-routes.ts +41 -32
  372. package/src/runtime/routes/credential-prompt-routes.ts +10 -1
  373. package/src/runtime/routes/debug-bash-routes.ts +1 -0
  374. package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
  375. package/src/runtime/routes/guardian-form-routes.ts +115 -0
  376. package/src/runtime/routes/inbound-message-handler.ts +82 -29
  377. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
  378. package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
  379. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
  380. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
  381. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
  382. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
  383. package/src/runtime/routes/index.ts +2 -0
  384. package/src/runtime/routes/integrations/slack/channel.ts +39 -18
  385. package/src/runtime/routes/migration-routes.ts +1 -3
  386. package/src/runtime/routes/monitoring-routes.ts +0 -3
  387. package/src/runtime/routes/oauth-commands-routes.ts +18 -5
  388. package/src/runtime/routes/route-host-worker-routes.ts +1 -1
  389. package/src/runtime/routes/schedule-worker-routes.ts +1 -1
  390. package/src/runtime/tool-grant-request-helper.ts +4 -0
  391. package/src/schedule/run-script.ts +1 -0
  392. package/src/schedule/scheduler.ts +1 -1
  393. package/src/schedule/worker-control.ts +2 -2
  394. package/src/security/oauth2.ts +2 -11
  395. package/src/security/secure-keys.ts +55 -8
  396. package/src/skills/catalog-install.ts +7 -4
  397. package/src/skills/clawhub.ts +1 -0
  398. package/src/skills/inline-command-runner.ts +1 -0
  399. package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
  400. package/src/stt/room-noise-floor.ts +140 -0
  401. package/src/subagent/notify.ts +49 -3
  402. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  403. package/src/tools/acp/spawn.ts +2 -2
  404. package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
  405. package/src/tools/browser/browser-execution.ts +10 -43
  406. package/src/tools/browser/browser-manager.ts +1 -0
  407. package/src/tools/browser/browser-status-constants.ts +6 -5
  408. package/src/tools/browser/runtime-check.ts +2 -0
  409. package/src/tools/channel/react-to-message.test.ts +193 -0
  410. package/src/tools/channel/react-to-message.ts +159 -0
  411. package/src/tools/credentials/metadata-store.ts +115 -19
  412. package/src/tools/credentials/store.ts +0 -2
  413. package/src/tools/filesystem/read.ts +2 -2
  414. package/src/tools/host-filesystem/read.ts +2 -2
  415. package/src/tools/host-terminal/host-shell.test.ts +24 -0
  416. package/src/tools/host-terminal/host-shell.ts +11 -38
  417. package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
  418. package/src/tools/shared/shell-output.test.ts +104 -0
  419. package/src/tools/shared/shell-output.ts +112 -39
  420. package/src/tools/skills/sandbox-runner.ts +1 -0
  421. package/src/tools/system/request-permission.test.ts +34 -0
  422. package/src/tools/system/request-permission.ts +29 -5
  423. package/src/tools/terminal/shell.test.ts +63 -0
  424. package/src/tools/terminal/shell.ts +12 -44
  425. package/src/tools/tool-input-schemas.ts +2 -0
  426. package/src/tools/tool-manifest.ts +2 -0
  427. package/src/usage/attribution.ts +12 -4
  428. package/src/util/__tests__/worker-ownership.test.ts +77 -0
  429. package/src/util/__tests__/worker-process-command.test.ts +246 -1
  430. package/src/util/bun-runtime.ts +44 -13
  431. package/src/util/bundled-asset.ts +9 -1
  432. package/src/util/disk-usage.ts +1 -0
  433. package/src/util/file-use.ts +4 -1
  434. package/src/util/guardian-form-timeouts.ts +50 -0
  435. package/src/util/image-conversion.ts +1 -0
  436. package/src/util/oauth-request-body.test.ts +59 -0
  437. package/src/util/oauth-request-body.ts +56 -5
  438. package/src/util/platform.ts +69 -73
  439. package/src/util/process-table.ts +16 -5
  440. package/src/util/read-stdin.ts +5 -0
  441. package/src/util/spawn.ts +1 -0
  442. package/src/util/sqlite3-runtime.ts +25 -5
  443. package/src/util/worker-ownership.ts +193 -0
  444. package/src/util/worker-process.ts +375 -37
  445. package/src/workspace/git-service.ts +4 -12
  446. package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
@@ -116,6 +116,18 @@ mock.module("../../assistant-event-hub.js", () => ({
116
116
  },
117
117
  }));
118
118
 
119
+ // The credential a spawn would resolve, which every credential-failure marker
120
+ // is compared against before it is served. Only the vault read is stubbed:
121
+ // the comparison itself stays real, and spreading the module keeps every other
122
+ // export intact for consumers that reach for one.
123
+ let fakeStoredCredential: string | undefined;
124
+ const realMarkerStore = await import("../../../acp/acp-auth-marker-store.js");
125
+ const realClaudeOauth = await import("../../../acp/acp-claude-oauth.js");
126
+ mock.module("../../../acp/acp-claude-oauth.js", () => ({
127
+ ...realClaudeOauth,
128
+ storedClaudeTokenDigest: async () => fakeStoredCredential,
129
+ }));
130
+
119
131
  /** Drain pending micro/macrotasks so background resume work settles. */
120
132
  const flushAsync = () => new Promise((resolve) => setTimeout(resolve, 0));
121
133
 
@@ -169,6 +181,7 @@ interface ResponseShape {
169
181
  inputTokens?: number;
170
182
  outputTokens?: number;
171
183
  eventLog?: unknown[];
184
+ authErrorCode?: string;
172
185
  }>;
173
186
  }
174
187
 
@@ -185,6 +198,7 @@ beforeEach(() => {
185
198
  approvalBehavior = "allow";
186
199
  confirmationRequests.length = 0;
187
200
  broadcasts.length = 0;
201
+ fakeStoredCredential = undefined;
188
202
  });
189
203
 
190
204
  describe("GET /v1/acp/sessions — merged in-memory + history", () => {
@@ -878,3 +892,516 @@ describe("POST /v1/acp/:id/steer: resume approval gate", () => {
878
892
  expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
879
893
  });
880
894
  });
895
+
896
+ describe("GET /v1/acp/sessions: a marker answers for itself", () => {
897
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
898
+ const REPLACEMENT = realMarkerStore.claudeTokenDigest("sk-ant-oat-new");
899
+
900
+ function markedRow(overrides: Record<string, unknown> = {}) {
901
+ insertHistoryRow({
902
+ id: "hist-auth",
903
+ agentId: "claude",
904
+ acpSessionId: "proto-auth",
905
+ parentConversationId: "conv-auth",
906
+ startedAt: 5000,
907
+ completedAt: 6000,
908
+ status: "failed",
909
+ eventLogJson: "[]",
910
+ authErrorCode: "acp_claude_auth_required",
911
+ authErrorCredential: REFUSED,
912
+ ...overrides,
913
+ });
914
+ }
915
+
916
+ test("serves the marker while the refused credential is still the stored one", async () => {
917
+ markedRow();
918
+ fakeStoredCredential = REFUSED;
919
+
920
+ const handler = getSessionsHandler();
921
+ const body = (await handler({
922
+ queryParams: { conversationId: "conv-auth" },
923
+ })) as ResponseShape;
924
+
925
+ expect(body.sessions[0].authErrorCode).toBe("acp_claude_auth_required");
926
+ });
927
+
928
+ test("withholds it once a different credential is stored", async () => {
929
+ // The user completed Connect. Nothing swept the row; the comparison is
930
+ // what stops the card rendering, so it needs no ordering to be right.
931
+ markedRow();
932
+ fakeStoredCredential = REPLACEMENT;
933
+
934
+ const handler = getSessionsHandler();
935
+ const body = (await handler({
936
+ queryParams: { conversationId: "conv-auth" },
937
+ })) as ResponseShape;
938
+
939
+ expect(body.sessions[0].authErrorCode).toBeUndefined();
940
+ });
941
+
942
+ test("withholding it twice gives the same answer", async () => {
943
+ // The sweep this replaced could only run once, which is why a restart or a
944
+ // second client used to be able to lose the answer.
945
+ markedRow();
946
+ fakeStoredCredential = REPLACEMENT;
947
+
948
+ const handler = getSessionsHandler();
949
+ const first = (await handler({
950
+ queryParams: { conversationId: "conv-auth" },
951
+ })) as ResponseShape;
952
+ const second = (await handler({
953
+ queryParams: { conversationId: "conv-auth" },
954
+ })) as ResponseShape;
955
+
956
+ expect(first.sessions[0].authErrorCode).toBeUndefined();
957
+ expect(second.sessions[0].authErrorCode).toBeUndefined();
958
+ });
959
+
960
+ test("serves a marker naming no credential rather than hiding it", async () => {
961
+ // Written before the credential column existed. An unknown credential is
962
+ // no evidence the failure was repaired.
963
+ markedRow({ authErrorCredential: null });
964
+ fakeStoredCredential = REPLACEMENT;
965
+
966
+ const handler = getSessionsHandler();
967
+ const body = (await handler({
968
+ queryParams: { conversationId: "conv-auth" },
969
+ })) as ResponseShape;
970
+
971
+ expect(body.sessions[0].authErrorCode).toBe("acp_claude_auth_required");
972
+ });
973
+
974
+ test("serves the marker when the vault holds nothing", async () => {
975
+ markedRow();
976
+ fakeStoredCredential = undefined;
977
+
978
+ const handler = getSessionsHandler();
979
+ const body = (await handler({
980
+ queryParams: { conversationId: "conv-auth" },
981
+ })) as ResponseShape;
982
+
983
+ expect(body.sessions[0].authErrorCode).toBe("acp_claude_auth_required");
984
+ });
985
+ });
986
+
987
+ describe("GET /v1/acp/sessions: markers resolve against configured tokens too", () => {
988
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
989
+ const CONFIGURED = "sk-ant-oat-configured-repair";
990
+
991
+ function markedRow() {
992
+ insertHistoryRow({
993
+ id: "hist-cfg",
994
+ agentId: "claude",
995
+ acpSessionId: "proto-cfg",
996
+ parentConversationId: "conv-cfg",
997
+ startedAt: 5000,
998
+ completedAt: 6000,
999
+ status: "failed",
1000
+ eventLogJson: "[]",
1001
+ authErrorCode: "acp_claude_auth_required",
1002
+ authErrorCredential: REFUSED,
1003
+ });
1004
+ }
1005
+
1006
+ test("withholds the marker when config supplies the repaired token", async () => {
1007
+ // The user fixed auth by setting `acp.agents.claude.env`, not by
1008
+ // connecting. Config wins at spawn, so the next run uses the repaired
1009
+ // token and the old failure is not going to repeat. A vault-only
1010
+ // comparison would keep restoring the card forever, since the vault still
1011
+ // holds the refused value.
1012
+ markedRow();
1013
+ fakeStoredCredential = REFUSED;
1014
+ config.setConfig({
1015
+ agents: {
1016
+ claude: {
1017
+ command: "claude-agent-acp",
1018
+ args: [],
1019
+ env: { CLAUDE_CODE_OAUTH_TOKEN: CONFIGURED },
1020
+ },
1021
+ },
1022
+ });
1023
+
1024
+ const handler = getSessionsHandler();
1025
+ const body = (await handler({
1026
+ queryParams: { conversationId: "conv-cfg" },
1027
+ })) as ResponseShape;
1028
+
1029
+ expect(body.sessions[0].authErrorCode).toBeUndefined();
1030
+ });
1031
+
1032
+ test("serves it when the configured token is the one that was refused", async () => {
1033
+ markedRow();
1034
+ fakeStoredCredential = undefined;
1035
+ config.setConfig({
1036
+ agents: {
1037
+ claude: {
1038
+ command: "claude-agent-acp",
1039
+ args: [],
1040
+ env: { CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat-refused" },
1041
+ },
1042
+ },
1043
+ });
1044
+
1045
+ const handler = getSessionsHandler();
1046
+ const body = (await handler({
1047
+ queryParams: { conversationId: "conv-cfg" },
1048
+ })) as ResponseShape;
1049
+
1050
+ expect(body.sessions[0].authErrorCode).toBe("acp_claude_auth_required");
1051
+ });
1052
+
1053
+ test("never puts the credential digest on the wire", async () => {
1054
+ markedRow();
1055
+ fakeStoredCredential = REFUSED;
1056
+
1057
+ const handler = getSessionsHandler();
1058
+ const body = (await handler({
1059
+ queryParams: { conversationId: "conv-cfg" },
1060
+ })) as ResponseShape;
1061
+
1062
+ expect(body.sessions[0]).not.toHaveProperty("authErrorCredential");
1063
+ });
1064
+ });
1065
+
1066
+ describe("GET /v1/acp/sessions: retired markers do not accumulate past the page", () => {
1067
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
1068
+ const REPLACEMENT = realMarkerStore.claudeTokenDigest("sk-ant-oat-new");
1069
+
1070
+ test("stale marked rows outside the page are dropped, not carried along", async () => {
1071
+ // Marker columns are retained rather than cleared, so every failure a
1072
+ // conversation has ever had still carries one. Letting them escape the
1073
+ // page before the comparison strikes the repaired ones grows the response
1074
+ // without limit, and a client that reads a full page as truncated then
1075
+ // stops retiring runs missing from it.
1076
+ for (let i = 0; i < 6; i++) {
1077
+ insertHistoryRow({
1078
+ id: `hist-old-${i}`,
1079
+ agentId: "claude",
1080
+ acpSessionId: `proto-old-${i}`,
1081
+ parentConversationId: "conv-many",
1082
+ startedAt: 1000 + i,
1083
+ completedAt: 2000 + i,
1084
+ status: "failed",
1085
+ eventLogJson: "[]",
1086
+ authErrorCode: "acp_claude_auth_required",
1087
+ authErrorCredential: REFUSED,
1088
+ });
1089
+ }
1090
+ // Newer unmarked runs fill the page.
1091
+ for (let i = 0; i < 3; i++) {
1092
+ insertHistoryRow({
1093
+ id: `hist-new-${i}`,
1094
+ agentId: "claude",
1095
+ acpSessionId: `proto-new-${i}`,
1096
+ parentConversationId: "conv-many",
1097
+ startedAt: 9000 + i,
1098
+ completedAt: 9500 + i,
1099
+ status: "completed",
1100
+ eventLogJson: "[]",
1101
+ });
1102
+ }
1103
+ fakeStoredCredential = REPLACEMENT;
1104
+
1105
+ const handler = getSessionsHandler();
1106
+ const body = (await handler({
1107
+ queryParams: { conversationId: "conv-many", limit: "3" },
1108
+ })) as ResponseShape;
1109
+
1110
+ expect(body.sessions).toHaveLength(3);
1111
+ expect(body.sessions.map((s) => s.id)).toEqual([
1112
+ "hist-new-2",
1113
+ "hist-new-1",
1114
+ "hist-new-0",
1115
+ ]);
1116
+ });
1117
+
1118
+ test("a marker that is still current does escape the page", async () => {
1119
+ insertHistoryRow({
1120
+ id: "hist-live-marker",
1121
+ agentId: "claude",
1122
+ acpSessionId: "proto-live-marker",
1123
+ parentConversationId: "conv-many",
1124
+ startedAt: 1000,
1125
+ completedAt: 2000,
1126
+ status: "failed",
1127
+ eventLogJson: "[]",
1128
+ authErrorCode: "acp_claude_auth_required",
1129
+ authErrorCredential: REFUSED,
1130
+ });
1131
+ for (let i = 0; i < 3; i++) {
1132
+ insertHistoryRow({
1133
+ id: `hist-fill-${i}`,
1134
+ agentId: "claude",
1135
+ acpSessionId: `proto-fill-${i}`,
1136
+ parentConversationId: "conv-many",
1137
+ startedAt: 9000 + i,
1138
+ completedAt: 9500 + i,
1139
+ status: "completed",
1140
+ eventLogJson: "[]",
1141
+ });
1142
+ }
1143
+ fakeStoredCredential = REFUSED;
1144
+
1145
+ const handler = getSessionsHandler();
1146
+ const body = (await handler({
1147
+ queryParams: { conversationId: "conv-many", limit: "3" },
1148
+ })) as ResponseShape;
1149
+
1150
+ expect(body.sessions).toHaveLength(4);
1151
+ expect(body.sessions.map((s) => s.id)).toContain("hist-live-marker");
1152
+ });
1153
+ });
1154
+
1155
+ describe("GET /v1/acp/sessions: current markers are bounded too", () => {
1156
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
1157
+
1158
+ test("only the newest marked run escapes the page", async () => {
1159
+ // Repeated failures against a credential that is still current all keep
1160
+ // their markers, since nothing clears them. The restore path takes the
1161
+ // newest marked run and stops, so letting the rest escape would grow the
1162
+ // response by a full event log apiece for no one's benefit.
1163
+ for (let i = 0; i < 5; i++) {
1164
+ insertHistoryRow({
1165
+ id: `hist-marked-${i}`,
1166
+ agentId: "claude",
1167
+ acpSessionId: `proto-marked-${i}`,
1168
+ parentConversationId: "conv-repeat",
1169
+ startedAt: 1000 + i,
1170
+ completedAt: 2000 + i,
1171
+ status: "failed",
1172
+ eventLogJson: "[]",
1173
+ parentToolUseId: `tool-${i}`,
1174
+ authErrorCode: "acp_claude_auth_required",
1175
+ authErrorCredential: REFUSED,
1176
+ });
1177
+ }
1178
+ for (let i = 0; i < 2; i++) {
1179
+ insertHistoryRow({
1180
+ id: `hist-recent-${i}`,
1181
+ agentId: "claude",
1182
+ acpSessionId: `proto-recent-${i}`,
1183
+ parentConversationId: "conv-repeat",
1184
+ startedAt: 9000 + i,
1185
+ completedAt: 9500 + i,
1186
+ status: "completed",
1187
+ eventLogJson: "[]",
1188
+ });
1189
+ }
1190
+ fakeStoredCredential = REFUSED;
1191
+
1192
+ const handler = getSessionsHandler();
1193
+ const body = (await handler({
1194
+ queryParams: { conversationId: "conv-repeat", limit: "2" },
1195
+ })) as ResponseShape;
1196
+
1197
+ // The page, plus exactly one escaped marker: the newest of the five.
1198
+ expect(body.sessions).toHaveLength(3);
1199
+ expect(body.sessions[2].id).toBe("hist-marked-4");
1200
+ });
1201
+
1202
+ test("markers inside the page are all returned", async () => {
1203
+ // The cap is on escaping the page, not on markers as such.
1204
+ for (let i = 0; i < 3; i++) {
1205
+ insertHistoryRow({
1206
+ id: `hist-inpage-${i}`,
1207
+ agentId: "claude",
1208
+ acpSessionId: `proto-inpage-${i}`,
1209
+ parentConversationId: "conv-inpage",
1210
+ startedAt: 1000 + i,
1211
+ completedAt: 2000 + i,
1212
+ status: "failed",
1213
+ eventLogJson: "[]",
1214
+ parentToolUseId: `tool-in-${i}`,
1215
+ authErrorCode: "acp_claude_auth_required",
1216
+ authErrorCredential: REFUSED,
1217
+ });
1218
+ }
1219
+ fakeStoredCredential = REFUSED;
1220
+
1221
+ const handler = getSessionsHandler();
1222
+ const body = (await handler({
1223
+ queryParams: { conversationId: "conv-inpage", limit: "10" },
1224
+ })) as ResponseShape;
1225
+
1226
+ expect(body.sessions).toHaveLength(3);
1227
+ expect(
1228
+ body.sessions.every(
1229
+ (s) => s.authErrorCode === "acp_claude_auth_required",
1230
+ ),
1231
+ ).toBe(true);
1232
+ });
1233
+ });
1234
+
1235
+ describe("GET /v1/acp/sessions: the marker past the page is looked up, not filtered", () => {
1236
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
1237
+ const CURRENT = realMarkerStore.claudeTokenDigest("sk-ant-oat-current");
1238
+
1239
+ test("reaches the newest current marker past a run of stale ones", async () => {
1240
+ // Newest-first, the markers go: stale, stale, current. Taking the newest
1241
+ // marked row and judging it afterwards would return nothing; the lookup
1242
+ // has to ask for the newest row whose credential still matches.
1243
+ const rows: Array<[string, number, string]> = [
1244
+ ["hist-current", 3000, CURRENT],
1245
+ ["hist-stale-a", 4000, REFUSED],
1246
+ ["hist-stale-b", 5000, REFUSED],
1247
+ ];
1248
+ for (const [id, startedAt, credential] of rows) {
1249
+ insertHistoryRow({
1250
+ id,
1251
+ agentId: "claude",
1252
+ acpSessionId: `proto-${id}`,
1253
+ parentConversationId: "conv-mixed",
1254
+ startedAt,
1255
+ completedAt: startedAt + 1,
1256
+ status: "failed",
1257
+ eventLogJson: "[]",
1258
+ parentToolUseId: `tool-${id}`,
1259
+ authErrorCode: "acp_claude_auth_required",
1260
+ authErrorCredential: credential,
1261
+ });
1262
+ }
1263
+ insertHistoryRow({
1264
+ id: "hist-recent",
1265
+ agentId: "claude",
1266
+ acpSessionId: "proto-recent",
1267
+ parentConversationId: "conv-mixed",
1268
+ startedAt: 9000,
1269
+ completedAt: 9001,
1270
+ status: "completed",
1271
+ eventLogJson: "[]",
1272
+ });
1273
+ fakeStoredCredential = CURRENT;
1274
+
1275
+ const handler = getSessionsHandler();
1276
+ const body = (await handler({
1277
+ queryParams: { conversationId: "conv-mixed", limit: "1" },
1278
+ })) as ResponseShape;
1279
+
1280
+ expect(body.sessions.map((s) => s.id)).toEqual([
1281
+ "hist-recent",
1282
+ "hist-current",
1283
+ ]);
1284
+ expect(body.sessions[1].authErrorCode).toBe("acp_claude_auth_required");
1285
+ });
1286
+
1287
+ test("reaches nothing when every marker is stale", async () => {
1288
+ insertHistoryRow({
1289
+ id: "hist-only-stale",
1290
+ agentId: "claude",
1291
+ acpSessionId: "proto-only-stale",
1292
+ parentConversationId: "conv-all-stale",
1293
+ startedAt: 1000,
1294
+ completedAt: 1001,
1295
+ status: "failed",
1296
+ eventLogJson: "[]",
1297
+ parentToolUseId: "tool-stale",
1298
+ authErrorCode: "acp_claude_auth_required",
1299
+ authErrorCredential: REFUSED,
1300
+ });
1301
+ insertHistoryRow({
1302
+ id: "hist-newer",
1303
+ agentId: "claude",
1304
+ acpSessionId: "proto-newer",
1305
+ parentConversationId: "conv-all-stale",
1306
+ startedAt: 9000,
1307
+ completedAt: 9001,
1308
+ status: "completed",
1309
+ eventLogJson: "[]",
1310
+ });
1311
+ fakeStoredCredential = CURRENT;
1312
+
1313
+ const handler = getSessionsHandler();
1314
+ const body = (await handler({
1315
+ queryParams: { conversationId: "conv-all-stale", limit: "1" },
1316
+ })) as ResponseShape;
1317
+
1318
+ expect(body.sessions.map((s) => s.id)).toEqual(["hist-newer"]);
1319
+ });
1320
+ });
1321
+
1322
+ describe("GET /v1/acp/sessions: a marker in the merged overflow", () => {
1323
+ const REFUSED = realMarkerStore.claudeTokenDigest("sk-ant-oat-refused");
1324
+
1325
+ test("surfaces a marker the page cut, even when history was read to the end", async () => {
1326
+ // In-memory sessions merge on top of the history read, so the merged list
1327
+ // can overflow the page while the query still reached the end of the
1328
+ // table. The marker the client needs can be sitting in that overflow, and
1329
+ // the short read is no proof it does not exist.
1330
+ fakeInMemorySessions = [
1331
+ {
1332
+ id: "live-a",
1333
+ agentId: "claude",
1334
+ acpSessionId: "proto-live-a",
1335
+ parentConversationId: "conv-overflow",
1336
+ status: "running",
1337
+ startedAt: 9000,
1338
+ },
1339
+ {
1340
+ id: "live-b",
1341
+ agentId: "claude",
1342
+ acpSessionId: "proto-live-b",
1343
+ parentConversationId: "conv-overflow",
1344
+ status: "running",
1345
+ startedAt: 9001,
1346
+ },
1347
+ ];
1348
+ insertHistoryRow({
1349
+ id: "hist-marked",
1350
+ agentId: "claude",
1351
+ acpSessionId: "proto-marked",
1352
+ parentConversationId: "conv-overflow",
1353
+ startedAt: 1000,
1354
+ completedAt: 1001,
1355
+ status: "failed",
1356
+ eventLogJson: "[]",
1357
+ parentToolUseId: "tool-marked",
1358
+ authErrorCode: "acp_claude_auth_required",
1359
+ authErrorCredential: REFUSED,
1360
+ });
1361
+ fakeStoredCredential = REFUSED;
1362
+
1363
+ const handler = getSessionsHandler();
1364
+ const body = (await handler({
1365
+ queryParams: { conversationId: "conv-overflow", limit: "2" },
1366
+ })) as ResponseShape;
1367
+
1368
+ expect(body.sessions).toHaveLength(3);
1369
+ expect(body.sessions[2].id).toBe("hist-marked");
1370
+ expect(body.sessions[2].authErrorCode).toBe("acp_claude_auth_required");
1371
+ });
1372
+
1373
+ test("a stale marker in the overflow is not surfaced", async () => {
1374
+ fakeInMemorySessions = [
1375
+ {
1376
+ id: "live-c",
1377
+ agentId: "claude",
1378
+ acpSessionId: "proto-live-c",
1379
+ parentConversationId: "conv-overflow-stale",
1380
+ status: "running",
1381
+ startedAt: 9000,
1382
+ },
1383
+ ];
1384
+ insertHistoryRow({
1385
+ id: "hist-stale",
1386
+ agentId: "claude",
1387
+ acpSessionId: "proto-stale",
1388
+ parentConversationId: "conv-overflow-stale",
1389
+ startedAt: 1000,
1390
+ completedAt: 1001,
1391
+ status: "failed",
1392
+ eventLogJson: "[]",
1393
+ parentToolUseId: "tool-stale",
1394
+ authErrorCode: "acp_claude_auth_required",
1395
+ authErrorCredential: REFUSED,
1396
+ });
1397
+ fakeStoredCredential = realMarkerStore.claudeTokenDigest("sk-ant-oat-new");
1398
+
1399
+ const handler = getSessionsHandler();
1400
+ const body = (await handler({
1401
+ queryParams: { conversationId: "conv-overflow-stale", limit: "1" },
1402
+ })) as ResponseShape;
1403
+
1404
+ expect(body.sessions).toHaveLength(1);
1405
+ expect(body.sessions[0].id).toBe("live-c");
1406
+ });
1407
+ });
@@ -0,0 +1,74 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
+
6
+ import {
7
+ _listSharedAppsForTests,
8
+ _resolveSharedAppDirForTests,
9
+ } from "../app-management-routes.js";
10
+
11
+ let rootDir: string;
12
+ let canonicalDir: string;
13
+ let legacyDir: string;
14
+
15
+ function writeSharedApp(dir: string, uuid: string, name: string): void {
16
+ mkdirSync(join(dir, uuid), { recursive: true });
17
+ writeFileSync(
18
+ join(dir, `${uuid}-meta.json`),
19
+ JSON.stringify({
20
+ uuid,
21
+ name,
22
+ entry: "index.html",
23
+ trustTier: "verified",
24
+ installedAt: "2026-01-01T00:00:00.000Z",
25
+ }),
26
+ );
27
+ writeFileSync(
28
+ join(dir, uuid, "manifest.json"),
29
+ JSON.stringify({ version: "1.0.0", content_id: `content-${uuid}` }),
30
+ );
31
+ }
32
+
33
+ beforeEach(() => {
34
+ rootDir = mkdtempSync(join(tmpdir(), "shared-app-roots-"));
35
+ canonicalDir = join(rootDir, "canonical");
36
+ legacyDir = join(rootDir, "legacy");
37
+ });
38
+
39
+ afterEach(() => {
40
+ rmSync(rootDir, { recursive: true, force: true });
41
+ });
42
+
43
+ describe("shared app roots", () => {
44
+ test("lists canonical and legacy apps after the canonical directory exists", () => {
45
+ writeSharedApp(canonicalDir, "app-new", "New App");
46
+ writeSharedApp(legacyDir, "app-legacy", "Legacy App");
47
+
48
+ const apps = _listSharedAppsForTests([canonicalDir, legacyDir]);
49
+
50
+ expect(apps.map((app) => app.uuid)).toEqual(["app-new", "app-legacy"]);
51
+ });
52
+
53
+ test("prefers the canonical copy when both roots contain the same app", () => {
54
+ writeSharedApp(canonicalDir, "app-shared", "Canonical App");
55
+ writeSharedApp(legacyDir, "app-shared", "Legacy App");
56
+
57
+ const apps = _listSharedAppsForTests([canonicalDir, legacyDir]);
58
+
59
+ expect(apps).toHaveLength(1);
60
+ expect(apps[0]?.name).toBe("Canonical App");
61
+ expect(
62
+ _resolveSharedAppDirForTests("app-shared", [canonicalDir, legacyDir]),
63
+ ).toBe(canonicalDir);
64
+ });
65
+
66
+ test("resolves a legacy app when it is absent from the canonical root", () => {
67
+ mkdirSync(canonicalDir, { recursive: true });
68
+ writeSharedApp(legacyDir, "app-legacy", "Legacy App");
69
+
70
+ expect(
71
+ _resolveSharedAppDirForTests("app-legacy", [canonicalDir, legacyDir]),
72
+ ).toBe(legacyDir);
73
+ });
74
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pins the contact form kinds the daemon opens its forms under.
3
+ *
4
+ * The gateway names these same strings when it claims a form, and a claim
5
+ * naming a different kind is rejected. Nothing at compile time links the two
6
+ * packages, so a rename on one side alone would show up only as contact forms
7
+ * failing to submit. The gateway has the matching test; both must be edited
8
+ * together for a rename to land.
9
+ */
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import {
13
+ CONTACT_ADDRESS_FORM_KIND,
14
+ CONTACT_RECORD_FORM_KIND,
15
+ } from "../contact-prompt-routes.js";
16
+
17
+ describe("contact form kinds", () => {
18
+ test("match the strings the gateway claims under", () => {
19
+ expect(CONTACT_ADDRESS_FORM_KIND).toBe("contacts.address");
20
+ expect(CONTACT_RECORD_FORM_KIND).toBe("contacts.record");
21
+ });
22
+ });