@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
@@ -11,10 +11,15 @@ import { eq, inArray } from "drizzle-orm";
11
11
  import type { AcpSessionUpdateEvent } from "../api/events/acp-session-update.js";
12
12
  import type { AssistantEvent } from "../api/index.js";
13
13
  import { findConversation } from "../daemon/conversation-registry.js";
14
+ import { SYNC_TAGS } from "../daemon/message-types/sync.js";
14
15
  import { getDb } from "../persistence/db-connection.js";
15
16
  import { acpSessionHistory } from "../persistence/schema/index.js";
16
17
  import * as pendingInteractions from "../runtime/pending-interactions.js";
17
18
  import { getLogger } from "../util/logger.js";
19
+ import {
20
+ acpAuthMarkerStillCurrent,
21
+ noteClaudeTokenRefused,
22
+ } from "./acp-auth-marker-store.js";
18
23
  import { markAcpConnectCardRaised } from "./acp-connect-card-state.js";
19
24
  import { AcpAgentProcess } from "./agent-process.js";
20
25
  import {
@@ -113,6 +118,12 @@ interface SessionEntry {
113
118
  * gate resume hints to the only adapter (claude-agent-acp) whose CLI
114
119
  * accepts `--resume`. */
115
120
  command: string;
121
+ /** Identity of the Claude token this session ran with, whichever source it
122
+ * came from. Recorded on the history row alongside the failure code, so the
123
+ * marker can be compared against the credential a later spawn resolves
124
+ * rather than needing a sweep to retire it. Absent for agents that use no
125
+ * Claude credential. */
126
+ credentialDigest?: string;
116
127
  }
117
128
 
118
129
  /**
@@ -275,6 +286,14 @@ export class AcpSessionManager {
275
286
  );
276
287
  } catch (err) {
277
288
  log.error({ acpSessionId, agentId, err }, "ACP spawn failed");
289
+ // Here rather than at the tool boundary, because the HTTP spawn route
290
+ // reaches this method directly and would otherwise never record it. A
291
+ // configured CLAUDE_CODE_OAUTH_TOKEN that Claude refused keeps winning
292
+ // over whatever Connect stores until this is written down, so every
293
+ // later spawn repeats the same failure.
294
+ if (isAcpAuthRequired(err)) {
295
+ noteClaudeTokenRefused(agentConfig.credentialDigest, Date.now());
296
+ }
278
297
  // No prompt has fired yet, so no permissions can be pending.
279
298
  this.teardownSession(acpSessionId, entry);
280
299
  throw err;
@@ -374,6 +393,7 @@ export class AcpSessionManager {
374
393
  parentToolUseId: opts.parentToolUseId,
375
394
  task: opts.task,
376
395
  command: basename(opts.agentConfig.command),
396
+ credentialDigest: opts.agentConfig.credentialDigest,
377
397
  };
378
398
 
379
399
  this.sessions.set(acpSessionId, entry);
@@ -972,6 +992,8 @@ export class AcpSessionManager {
972
992
  const usageColumns = {
973
993
  task: entry.state.task ?? null,
974
994
  parentToolUseId: entry.state.parentToolUseId ?? null,
995
+ authErrorCode: entry.state.authErrorCode ?? null,
996
+ authErrorCredential: entry.state.authErrorCredential ?? null,
975
997
  usedTokens: usage?.usedTokens ?? null,
976
998
  contextSize: usage?.contextSize ?? null,
977
999
  costAmount: usage?.costAmount ?? null,
@@ -1114,7 +1136,7 @@ export class AcpSessionManager {
1114
1136
  }
1115
1137
  }
1116
1138
  })
1117
- .catch((err: Error) => {
1139
+ .catch(async (err: Error) => {
1118
1140
  const current = this.sessions.get(acpSessionId);
1119
1141
  // Same guards: entry must exist, prompt must be current, and status
1120
1142
  // must not have been set to "cancelled".
@@ -1158,7 +1180,59 @@ export class AcpSessionManager {
1158
1180
  errorCode !== undefined && current.state.status !== "cancelled"
1159
1181
  ? current.parentToolUseId
1160
1182
  : undefined;
1161
- if (errorCode !== undefined && recoveryAnchor !== undefined) {
1183
+ // Claude refused this credential, so no later spawn should resolve
1184
+ // it again. Recorded before the card is raised and independently of
1185
+ // the marker, because the user may delete this run and clearing
1186
+ // history must not put the revoked value back in play.
1187
+ if (errorCode !== undefined) {
1188
+ noteClaudeTokenRefused(current.credentialDigest, Date.now());
1189
+ }
1190
+ // The live event reaches clients that are listening now, and the
1191
+ // card it raises deliberately skips connected-state self-healing, so
1192
+ // nothing retires it until the next snapshot. If the credential has
1193
+ // already been replaced, that snapshot is the only thing that would
1194
+ // have withheld it, and it may be a navigation away. Ask the same
1195
+ // question the read path asks, against the same source of truth.
1196
+ //
1197
+ // An optimisation, not the authority: a write landing right after
1198
+ // this read raises a card the next snapshot withholds. The marker is
1199
+ // written either way, so the read path still has the final say.
1200
+ const stillCurrent =
1201
+ errorCode !== undefined &&
1202
+ recoveryAnchor !== undefined &&
1203
+ acpAuthMarkerStillCurrent(
1204
+ current.credentialDigest,
1205
+ // Imported at call time so this module's load graph stays as it
1206
+ // was: several suites stub `prepare-agent-env` with a partial
1207
+ // factory, and a new static import from here would break their
1208
+ // module resolution rather than their assertions.
1209
+ await import("./prepare-agent-env.js")
1210
+ .then((m) =>
1211
+ m.resolvedClaudeCredentialDigest(current.state.agentId),
1212
+ )
1213
+ // Unknown reads as current, matching the comparison itself:
1214
+ // failing toward a card the next snapshot can withdraw beats
1215
+ // withholding the user's only route back to auth.
1216
+ .catch(() => current.credentialDigest),
1217
+ );
1218
+ // The credential read above suspends this handler, and a cancel
1219
+ // landing in that window runs to completion first: `cancel()`
1220
+ // persists the cancelled row, drops the event buffer and tears the
1221
+ // session down. Carrying on would emit a card for a run the user
1222
+ // stopped and, worse, persist a second time over a buffer that is
1223
+ // already gone, replacing the stored event log with an empty one.
1224
+ // The map is the evidence: `teardownSession` removes the entry, so an
1225
+ // entry that is no longer the registered one means someone else has
1226
+ // already finished this session and there is nothing left here to
1227
+ // do.
1228
+ if (this.sessions.get(acpSessionId) !== current) {
1229
+ return;
1230
+ }
1231
+ if (
1232
+ errorCode !== undefined &&
1233
+ recoveryAnchor !== undefined &&
1234
+ stillCurrent
1235
+ ) {
1162
1236
  current.sendToVellum({
1163
1237
  type: "acp_auth_required",
1164
1238
  acpSessionId,
@@ -1169,7 +1243,36 @@ export class AcpSessionManager {
1169
1243
  // Same registry as the missing-token spawn path, so the
1170
1244
  // credential-prompt route redirects a redundant secure prompt at
1171
1245
  // the card instead of stacking a second one.
1172
- markAcpConnectCardRaised(current.parentConversationId);
1246
+ markAcpConnectCardRaised(
1247
+ current.parentConversationId,
1248
+ current.state.agentId,
1249
+ );
1250
+ // The event above only reaches clients listening right now. The
1251
+ // code goes on the state so `persistTerminal` (called just below)
1252
+ // carries it to the history row, which is what a client that
1253
+ // reopens the conversation re-raises the card from.
1254
+ current.state.authErrorCode = errorCode;
1255
+ // Tell clients to re-read the snapshot, which is the only thing
1256
+ // that can withdraw this card. The check above reads the vault
1257
+ // and then this branch runs, so a replacement completing in
1258
+ // between publishes its own invalidation before the event goes
1259
+ // out, and a client that acted on it would never look again. This
1260
+ // nudge lands after, so whatever the snapshot says wins.
1261
+ void import("../runtime/sync/sync-publisher.js")
1262
+ .then(({ publishSyncInvalidation }) =>
1263
+ publishSyncInvalidation([SYNC_TAGS.acpAuthRecovery]),
1264
+ )
1265
+ .catch((err: unknown) => {
1266
+ log.warn(
1267
+ { err },
1268
+ "failed to publish ACP auth recovery invalidation",
1269
+ );
1270
+ });
1271
+ // Named beside the code so the marker can answer for itself later.
1272
+ // Without it the row says auth broke but not on what, and a reader
1273
+ // has no way to tell a failure the user has since repaired from one
1274
+ // still waiting on them.
1275
+ current.state.authErrorCredential = current.credentialDigest;
1173
1276
  }
1174
1277
 
1175
1278
  // Persist the terminal row before teardown clears the buffer.
@@ -1187,9 +1290,10 @@ export class AcpSessionManager {
1187
1290
  this.notifyParent(
1188
1291
  current,
1189
1292
  `[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}` +
1190
- (recoveryAnchor !== undefined
1191
- ? `\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`
1192
- : ""),
1293
+ // Same predicate as the recovery surface above. Telling the
1294
+ // model a Connect card is waiting when none was raised sends
1295
+ // it to an affordance that does not exist.
1296
+ (stillCurrent ? `\n\n${ACP_AUTH_RECOVERY_GUIDANCE}` : ""),
1193
1297
  );
1194
1298
  }
1195
1299
  }
package/src/acp/types.ts CHANGED
@@ -12,6 +12,13 @@ export interface AcpAgentConfig {
12
12
  args: string[];
13
13
  description?: string;
14
14
  env?: Record<string, string>;
15
+ /**
16
+ * Identity of the Claude token `prepareAgentEnv` resolved into `env`,
17
+ * whichever source it came from. Recorded on the history row when Claude
18
+ * refuses it, so the marker can later be compared against the credential a
19
+ * spawn would resolve now. Absent for agents that use no Claude credential.
20
+ */
21
+ credentialDigest?: string;
15
22
  }
16
23
 
17
24
  /**
@@ -32,6 +39,16 @@ export interface AcpSessionState {
32
39
  task?: string;
33
40
  /** Tool-use id of the `acp_spawn` call that spawned this session, if any. */
34
41
  parentToolUseId?: string;
42
+ /**
43
+ * Credential failure that ended the run, when one did. Persisted on the
44
+ * history row so a client that reopens the conversation can re-raise the
45
+ * inline Connect card, and cleared there when a replacement token lands.
46
+ */
47
+ authErrorCode?: string;
48
+ /** Digest of the Claude token that failure was refused on, carried to the
49
+ * history row so the marker can be compared against the credential a later
50
+ * spawn resolves. */
51
+ authErrorCredential?: string;
35
52
  /** Latest context-window usage gauge, from the most recent `usage_update`. */
36
53
  latestUsage?: AcpUsageSnapshot;
37
54
  }
@@ -1,4 +1,8 @@
1
- import type { ContentBlock, Message } from "../providers/types.js";
1
+ import {
2
+ attachmentIdFragment,
3
+ type ContentBlock,
4
+ type Message,
5
+ } from "../providers/types.js";
2
6
  import { optimizeImageForTransport } from "./image-optimize.js";
3
7
 
4
8
  export interface MessageAttachmentInput {
@@ -91,6 +95,7 @@ export function attachmentsToContentBlocks(
91
95
  media_type: mediaType,
92
96
  data,
93
97
  },
98
+ ...attachmentIdFragment(attachment.id),
94
99
  } as ContentBlock;
95
100
  }
96
101
 
@@ -103,7 +108,7 @@ export function attachmentsToContentBlocks(
103
108
  filename: attachment.filename,
104
109
  },
105
110
  extracted_text: attachment.extractedText,
106
- ...(attachment.id ? { _attachmentId: attachment.id } : {}),
111
+ ...attachmentIdFragment(attachment.id),
107
112
  } as ContentBlock;
108
113
  }),
109
114
  );
package/src/agent/loop.ts CHANGED
@@ -797,6 +797,22 @@ export interface AgentLoopConstructorOptions {
797
797
  * result-time pass and the post-turn truncation covers the turn instead.
798
798
  */
799
799
  resolveConversationDir?: () => string | null;
800
+ /**
801
+ * Trim a freshly compacted history before it becomes the loop's working set.
802
+ *
803
+ * Compaction rebuilds history from the stored rows, so it can reintroduce
804
+ * content the caller had already trimmed out of the array it handed to
805
+ * `run()` (camera-frame retention is the case this exists for: the compaction
806
+ * model may retain any number of older frames, and the rebuilt history is
807
+ * sent on the very next request). The caller closes over whatever
808
+ * conversation state its trimming needs, which is what keeps this module free
809
+ * of any dependency on the daemon's conversation layer.
810
+ *
811
+ * Must be total: the loop treats a throw as "no trim" rather than failing the
812
+ * turn. Callers that hold no conversation (workflow leaf runs) omit it and
813
+ * the compacted history is installed as built.
814
+ */
815
+ transformCompactedHistory?: (messages: Message[]) => Message[];
800
816
  }
801
817
 
802
818
  export class AgentLoop {
@@ -818,6 +834,11 @@ export class AgentLoop {
818
834
  /** See {@link AgentLoopConstructorOptions.resolveConversationDir}. */
819
835
  private readonly resolveConversationDir: (() => string | null) | null;
820
836
 
837
+ /** See {@link AgentLoopConstructorOptions.transformCompactedHistory}. */
838
+ private readonly transformCompactedHistory:
839
+ | ((messages: Message[]) => Message[])
840
+ | null;
841
+
821
842
  /**
822
843
  * Loop-held compaction circuit breaker. The loop has a 1:1 lifetime with its
823
844
  * conversation, so it is the source of truth for the cross-turn failure
@@ -837,6 +858,7 @@ export class AgentLoop {
837
858
  resolveTools,
838
859
  conversationId,
839
860
  resolveConversationDir,
861
+ transformCompactedHistory,
840
862
  } = options;
841
863
  this.provider = provider;
842
864
  this.systemPrompt = systemPrompt;
@@ -846,6 +868,7 @@ export class AgentLoop {
846
868
  this.toolExecutor = toolExecutor ?? null;
847
869
  this.conversationId = conversationId;
848
870
  this.resolveConversationDir = resolveConversationDir ?? null;
871
+ this.transformCompactedHistory = transformCompactedHistory ?? null;
849
872
  this.compactionCircuit = new CompactionCircuit(this.conversationId);
850
873
  }
851
874
 
@@ -925,6 +948,34 @@ export class AgentLoop {
925
948
  }
926
949
  }
927
950
 
951
+ /**
952
+ * Run the caller's post-compaction trim over a freshly compacted history,
953
+ * falling back to the untrimmed array when no transform is configured or the
954
+ * transform throws.
955
+ *
956
+ * The trim narrows what the next request carries, so failing it is strictly
957
+ * worse than skipping it: the turn still has a valid history either way, and
958
+ * the caller's own passes cover the next turn. Kept best-effort for the same
959
+ * reason the caller's pre-run pass is.
960
+ */
961
+ private applyCompactedHistoryTransform(
962
+ compacted: Message[],
963
+ rlog: ReturnType<typeof getLogger>,
964
+ ): Message[] {
965
+ if (!this.transformCompactedHistory) {
966
+ return compacted;
967
+ }
968
+ try {
969
+ return this.transformCompactedHistory(compacted);
970
+ } catch (err) {
971
+ rlog.warn(
972
+ { err },
973
+ "Post-compaction history transform failed (non-fatal); keeping the compacted history as built",
974
+ );
975
+ return compacted;
976
+ }
977
+ }
978
+
928
979
  /**
929
980
  * Compact the running history in place when the budget gate trips.
930
981
  *
@@ -1363,7 +1414,14 @@ export class AgentLoop {
1363
1414
  overflowSignal ?? undefined,
1364
1415
  );
1365
1416
  if (attempt.history) {
1366
- history = attempt.history;
1417
+ // Trim before anything else reads the rebuilt array: the
1418
+ // provider call further down this same iteration sends it, so
1419
+ // content compaction reintroduced has to be brought back
1420
+ // within the caller's bounds here or it ships un-trimmed.
1421
+ history = this.applyCompactedHistoryTransform(
1422
+ attempt.history,
1423
+ rlog,
1424
+ );
1367
1425
  // The compacted, re-injected array is the new base; output
1368
1426
  // produced after this point is what the wrapper persists.
1369
1427
  newMessagesStart = history.length;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `contact_form_closed` SSE event.
3
+ *
4
+ * Tells every connected client that a contact form is over, so the card comes
5
+ * down instead of sitting there offering to submit an answer the gateway will
6
+ * now refuse.
7
+ *
8
+ * Both contact forms are workspace-global, broadcast without a conversation,
9
+ * which is why this is its own event rather than `interaction_resolved`: that
10
+ * one is conversation-scoped on the wire and is deliberately not broadcast for
11
+ * a conversation-less interaction.
12
+ *
13
+ * Canonical wire-contract source. Daemon code imports the type directly from
14
+ * this file; external consumers import via `@vellumai/assistant-api`.
15
+ */
16
+
17
+ import { z } from "zod";
18
+
19
+ export const ContactFormClosedEventSchema = z.object({
20
+ type: z.literal("contact_form_closed"),
21
+ requestId: z.string(),
22
+ /**
23
+ * Why it closed. Every client saw the form, so every client needs to know:
24
+ * "answered" and "cancelled" retire the card on the clients that did not
25
+ * answer it, and "timed_out" retires it on all of them.
26
+ */
27
+ reason: z.enum(["answered", "cancelled", "timed_out"]),
28
+ });
29
+
30
+ export type ContactFormClosedEvent = z.infer<
31
+ typeof ContactFormClosedEventSchema
32
+ >;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `contact_record_request` SSE event.
3
+ *
4
+ * Server to client form asking the guardian to confirm a contact record write
5
+ * the assistant proposed: a create, an update, or a delete. Emitted by the
6
+ * `contacts/record-prompt` IPC route while a `pendingContactPrompts` entry
7
+ * awaits a reply.
8
+ *
9
+ * The proposed values are a starting point, not a decision. The guardian may
10
+ * edit the name and notes before submitting, and the client posts the result
11
+ * straight to the gateway, which owns the write.
12
+ *
13
+ * The form closes when the guardian answers it, or with a
14
+ * `contact_form_closed` event if it times out first. `interaction_resolved`
15
+ * does not cover it: that event is conversation-scoped on the wire and is
16
+ * deliberately not broadcast for a conversation-less interaction like this
17
+ * one.
18
+ *
19
+ * Canonical wire-contract source. Daemon code imports the type directly from
20
+ * this file; external consumers import via `@vellumai/assistant-api`.
21
+ */
22
+
23
+ import { z } from "zod";
24
+
25
+ export const ContactRecordRequestEventSchema = z.object({
26
+ type: z.literal("contact_record_request"),
27
+ requestId: z.string(),
28
+ operation: z.enum(["create", "update", "delete"]),
29
+ /** Target of an update or delete. Absent on create. */
30
+ contactId: z.string().optional(),
31
+ /** Current name of the target, so the form can show what is changing. */
32
+ currentDisplayName: z.string().optional(),
33
+ /**
34
+ * The target's current notes. The form submits a field only when it differs
35
+ * from what is stored, so it needs what is stored to compare against.
36
+ */
37
+ currentNotes: z.string().optional(),
38
+ /**
39
+ * The target's channels, so a delete confirmation can say which of two
40
+ * same-named contacts it is about, and what access is about to be lost.
41
+ */
42
+ channels: z
43
+ .array(z.object({ type: z.string(), address: z.string() }))
44
+ .optional(),
45
+
46
+ /** Proposed name, prefilled into the form. */
47
+ displayName: z.string().optional(),
48
+ /** Proposed notes, prefilled into the form. */
49
+ notes: z.string().optional(),
50
+ /**
51
+ * Whether the caller asked for these notes explicitly. A proposal that
52
+ * happens to match what is stored is still a change the guardian confirmed,
53
+ * and an unreadable mirror reports stored notes as empty, so the form cannot
54
+ * tell "clear them" from "nothing to do" by comparison alone.
55
+ */
56
+ notesProposed: z.boolean().optional(),
57
+ label: z.string().optional(),
58
+ description: z.string().optional(),
59
+ });
60
+
61
+ export type ContactRecordRequestEvent = z.infer<
62
+ typeof ContactRecordRequestEventSchema
63
+ >;
@@ -5,9 +5,11 @@
5
5
  * address (phone, email, etc.). Emitted by the `contacts/prompt` IPC
6
6
  * route while a `pendingContactPrompts` entry awaits a reply.
7
7
  *
8
- * Resolved by a paired `interaction_resolved` event (`kind:
9
- * "contact"`, `state: "answered" | "cancelled"`) once the user
10
- * responds or the timeout fires.
8
+ * The form closes when the user answers it, or with a
9
+ * `contact_form_closed` event if it times out first. `interaction_resolved`
10
+ * does not cover it: that event is conversation-scoped on the wire and is
11
+ * deliberately not broadcast for a conversation-less interaction like this
12
+ * one.
11
13
  *
12
14
  * `channel` and `role` are advisory hints, not enforced enums — the
13
15
  * client may render any input it likes and post back a structured
@@ -29,6 +31,11 @@ export const ContactRequestEventSchema = z.object({
29
31
  label: z.string().optional(),
30
32
  description: z.string().optional(),
31
33
  role: z.string().optional(),
34
+ /**
35
+ * Initial state of the form's "mark verified" checkbox, from the CLI's
36
+ * `--verify`. What the guardian submits is what gets attested.
37
+ */
38
+ verify: z.boolean().optional(),
32
39
  });
33
40
 
34
41
  export type ContactRequestEvent = z.infer<typeof ContactRequestEventSchema>;
package/src/api/index.ts CHANGED
@@ -23,6 +23,8 @@ import { CompactionCircuitOpenEventSchema } from "./events/compaction-circuit-op
23
23
  import { ConfigChangedEventSchema } from "./events/config-changed.js";
24
24
  import { ConfirmationRequestEventSchema } from "./events/confirmation-request.js";
25
25
  import { ConfirmationStateChangedEventSchema } from "./events/confirmation-state-changed.js";
26
+ import { ContactFormClosedEventSchema } from "./events/contact-form-closed.js";
27
+ import { ContactRecordRequestEventSchema } from "./events/contact-record-request.js";
26
28
  import { ContactRequestEventSchema } from "./events/contact-request.js";
27
29
  import { ContactsChangedEventSchema } from "./events/contacts-changed.js";
28
30
  import { ContextCompactedEventSchema } from "./events/context-compacted.js";
@@ -285,6 +287,14 @@ export {
285
287
  type ConfirmationStateChangedEvent,
286
288
  ConfirmationStateChangedEventSchema,
287
289
  } from "./events/confirmation-state-changed.js";
290
+ export {
291
+ type ContactFormClosedEvent,
292
+ ContactFormClosedEventSchema,
293
+ } from "./events/contact-form-closed.js";
294
+ export {
295
+ type ContactRecordRequestEvent,
296
+ ContactRecordRequestEventSchema,
297
+ } from "./events/contact-record-request.js";
288
298
  export {
289
299
  type ContactRequestEvent,
290
300
  ContactRequestEventSchema,
@@ -767,6 +777,12 @@ export {
767
777
  type FeedItemDetailPanelKind,
768
778
  FeedItemDetailPanelKindSchema,
769
779
  FeedItemDetailPanelSchema,
780
+ type FeedItemGuardianIntent,
781
+ FeedItemGuardianIntentSchema,
782
+ type FeedItemGuardianRequest,
783
+ FeedItemGuardianRequestSchema,
784
+ type FeedItemGuardianStatus,
785
+ FeedItemGuardianStatusSchema,
770
786
  FeedItemSchema,
771
787
  type FeedItemSourceType,
772
788
  FeedItemSourceTypeSchema,
@@ -776,8 +792,10 @@ export {
776
792
  FeedItemTypeSchema,
777
793
  type FeedItemUrgency,
778
794
  FeedItemUrgencySchema,
795
+ GUARDIAN_TERMINAL_REASON_SUPERSEDED,
779
796
  type HomeFeedResponse,
780
797
  HomeFeedResponseSchema,
798
+ isPendingGuardianFeedItem,
781
799
  type RelationshipState,
782
800
  RelationshipStateSchema,
783
801
  type RelationshipTier,
@@ -952,6 +970,8 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
952
970
  ConfigChangedEventSchema,
953
971
  ConfirmationRequestEventSchema,
954
972
  ConfirmationStateChangedEventSchema,
973
+ ContactFormClosedEventSchema,
974
+ ContactRecordRequestEventSchema,
955
975
  ContactRequestEventSchema,
956
976
  ContactsChangedEventSchema,
957
977
  ContextCompactedEventSchema,
@@ -607,6 +607,26 @@ export const ConversationMessageSchema = z.object({
607
607
  * system notices — no avatar, no persona bubble — and never group them
608
608
  * with adjacent assistant turns. */
609
609
  systemCard: z.boolean().optional(),
610
+ /** Set on a turn whose whole reply was the `<no_response/>` sentinel
611
+ * (`metadata.messageKind === "no_response"`): the assistant deliberately
612
+ * chose silence. Clients render a quiet standalone notice instead of the
613
+ * sentinel text, and treat the row as the turn's reply so nothing keeps
614
+ * waiting for one. */
615
+ noResponse: z.boolean().optional(),
616
+ /** Present on a reaction row, either direction: an inbound reaction the
617
+ * daemon persisted, or the assistant's own (`selfAuthored`). Clients
618
+ * render a reaction line from this instead of the row's stored sentinel
619
+ * text. Slack rows additionally carry their own `slackMessage` envelope,
620
+ * which Slack-aware renderers may prefer. */
621
+ reaction: z
622
+ .object({
623
+ emoji: z.string(),
624
+ op: z.enum(["added", "removed"]),
625
+ targetMessageId: z.string(),
626
+ actorDisplayName: z.string().optional(),
627
+ selfAuthored: z.boolean().optional(),
628
+ })
629
+ .optional(),
610
630
  /** Present when this assistant row is a daemon-persisted provider-failure
611
631
  * notice (`metadata.messageKind === "provider_error"`); clients may render
612
632
  * a themed card instead of a persona bubble. `code` is the stable
@@ -107,6 +107,83 @@ export const FeedItemDetailPanelSchema = z.object({
107
107
  });
108
108
  export type FeedItemDetailPanel = z.infer<typeof FeedItemDetailPanelSchema>;
109
109
 
110
+ /**
111
+ * Canonical guardian-request status projected onto a feed item.
112
+ *
113
+ * Values mirror `GuardianRequestStatusSchema` in
114
+ * `@vellumai/gateway-client` (this file is copied verbatim into client
115
+ * packages, so it cannot import the contract; the daemon's projection
116
+ * writer asserts the two stay aligned at compile time).
117
+ */
118
+ export const FeedItemGuardianStatusSchema = z.enum([
119
+ "pending",
120
+ "approved",
121
+ "denied",
122
+ "expired",
123
+ "cancelled",
124
+ ]);
125
+ export type FeedItemGuardianStatus = z.infer<
126
+ typeof FeedItemGuardianStatusSchema
127
+ >;
128
+
129
+ /** Whether the guardian is being asked to approve or to answer. */
130
+ export const FeedItemGuardianIntentSchema = z.enum(["approval", "question"]);
131
+ export type FeedItemGuardianIntent = z.infer<
132
+ typeof FeedItemGuardianIntentSchema
133
+ >;
134
+
135
+ /**
136
+ * Read projection of one canonical guardian request onto its feed item.
137
+ *
138
+ * The feed item carrying this is the request's single "Needs attention"
139
+ * home: exactly one item per `requestId`, kept current by the daemon's
140
+ * status fan-out. Clients derive every affordance from `status` +
141
+ * `intent`: a `pending` approval offers Approve/Reject (via
142
+ * `POST /v1/guardian-actions/decision`), a `pending` question routes to
143
+ * the source conversation, and a terminal status renders as a receipt.
144
+ * Nothing here is an independent delivery record; it restates gateway
145
+ * `guardian_requests` state and is never a source of truth on its own.
146
+ */
147
+ export const FeedItemGuardianRequestSchema = z.object({
148
+ requestId: z.string(),
149
+ /**
150
+ * Guardian request kind (`tool_approval`, `tool_grant_request`,
151
+ * `pending_question`, `access_request`). A string rather than an enum so
152
+ * rows written by a newer daemon still parse.
153
+ */
154
+ kind: z.string(),
155
+ intent: FeedItemGuardianIntentSchema,
156
+ status: FeedItemGuardianStatusSchema,
157
+ /** Display name or identifier of the requester (e.g. "Alice"). */
158
+ requesterLabel: z.string().optional(),
159
+ /** Tool the request is about, when it is a tool approval/grant. */
160
+ toolName: z.string().optional(),
161
+ /** Channel the request originated from (e.g. "slack"). */
162
+ sourceChannel: z.string().optional(),
163
+ /** Display label for the originating chat (e.g. "#user-feedback"). */
164
+ sourceContextLabel: z.string().optional(),
165
+ /** Permalink to the originating channel message, when derivable. */
166
+ sourceUrl: z.string().optional(),
167
+ /** Web URL of the Slack guardian-DM approval card ("Open in Slack"). */
168
+ slackCardUrl: z.string().optional(),
169
+ /** slack:// deep link for the same card, preferred on devices with the app. */
170
+ slackCardAppUrl: z.string().optional(),
171
+ /** Action that resolved the request, for terminal statuses. */
172
+ decidedAction: z.string().optional(),
173
+ /** Display label of the decider, when the decision came from a person. */
174
+ decidedByLabel: z.string().optional(),
175
+ /** ISO-8601 time the request reached its terminal status. */
176
+ decidedAt: z.string().optional(),
177
+ /**
178
+ * Why a non-decision terminal status was reached (e.g. `superseded`
179
+ * when a newer message auto-denied the request). Display-only.
180
+ */
181
+ terminalReason: z.string().optional(),
182
+ });
183
+ export type FeedItemGuardianRequest = z.infer<
184
+ typeof FeedItemGuardianRequestSchema
185
+ >;
186
+
110
187
  /**
111
188
  * A single item rendered in the Home feed.
112
189
  *
@@ -135,6 +212,9 @@ export const FeedItemSchema = z.object({
135
212
  urgency: FeedItemUrgencySchema.optional(),
136
213
  conversationId: z.string().optional(),
137
214
  detailPanel: FeedItemDetailPanelSchema.optional(),
215
+ // Present only on the canonical "Needs attention" item projecting a
216
+ // guardian request; see FeedItemGuardianRequestSchema.
217
+ guardianRequest: FeedItemGuardianRequestSchema.optional(),
138
218
  category: FeedItemCategorySchema.optional(),
139
219
  noteworthy: z.boolean().optional(),
140
220
  fromAssistant: z.boolean().optional(),
@@ -150,6 +230,27 @@ export const FeedItemSchema = z.object({
150
230
  });
151
231
  export type FeedItem = z.infer<typeof FeedItemSchema>;
152
232
 
233
+ /**
234
+ * `terminalReason` value for a request auto-denied because a newer
235
+ * inbound message superseded it. Shared by the daemon rails that record
236
+ * it and the clients that render "Superseded" instead of a rejection.
237
+ */
238
+ export const GUARDIAN_TERMINAL_REASON_SUPERSEDED = "superseded";
239
+
240
+ /**
241
+ * Whether a feed item is the live projection of an unresolved guardian
242
+ * request. Shared by the daemon (bulk-dismiss protection in the feed
243
+ * writer) and clients (visibility carve-outs, bulk-action id sets) so
244
+ * "pending guardian item" means the same thing on both sides: such an
245
+ * item must stay actionable until the canonical request resolves, and
246
+ * only its terminal receipt is ordinarily clearable.
247
+ */
248
+ export function isPendingGuardianFeedItem(
249
+ item: Pick<FeedItem, "guardianRequest">,
250
+ ): boolean {
251
+ return item.guardianRequest?.status === "pending";
252
+ }
253
+
153
254
  // ---------------------------------------------------------------------------
154
255
  // Suggested prompt
155
256
  // ---------------------------------------------------------------------------