@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
package/ARCHITECTURE.md CHANGED
@@ -203,7 +203,7 @@ The guardian request system provides a channel-agnostic, unified domain for all
203
203
 
204
204
  **Resolver registry:** Kind-specific resolvers (`src/approvals/guardian-request-resolvers.ts`) handle side effects after CAS resolution. Built-in resolvers: `tool_approval` (channel/desktop approval path), `pending_question` (voice call question path), and `access_request` (trusted-contact verification session creation). New request kinds register resolvers without touching the core primitive.
205
205
 
206
- **Expiry:** the daemon's 60-second sweep (`src/runtime/routes/guardian-expiry-sweep.ts`) asks the gateway to CAS-expire pending requests past `expiresAt` (`guardian_requests_sweep_expired`) and fans out card withdrawal + requester notices from the returned rows; interaction-bound kinds (`tool_approval`, `pending_question`) are expired at daemon boot via `guardian_requests_expire_interaction_bound` since they die with the in-memory pending-interactions map. `src/calls/guardian-action-sweep.ts` sends the guardian-facing expiry notices for voice questions.
206
+ **Expiry:** the daemon's 60-second sweep (`src/runtime/routes/guardian-expiry-sweep.ts`) lists a bounded batch of past-deadline pending requests (`guardian_requests_list_expired_pending`, read-only), fans out card withdrawal + requester notices per row, and only then confirms each with the per-request `guardian_requests_expire` CAS, so a lost response or crash leaves the row listed for the next round; interaction-bound kinds (`tool_approval`, `pending_question`) are expired at daemon boot via `guardian_requests_expire_interaction_bound` since they die with the in-memory pending-interactions map, and the boot call never touches persistent kinds. `src/calls/guardian-action-sweep.ts` sends the guardian-facing expiry notices for voice questions.
207
207
 
208
208
  **Key source files:**
209
209
 
@@ -267,19 +267,19 @@ Guardian requests (tool approvals, access requests, voice ASK_GUARDIAN questions
267
267
 
268
268
  **Voice consultation timeout:** When a voice call's ASK_GUARDIAN consultation times out before the guardian responds, the call controller expires the request via the gateway (`guardian_requests_expire` — deliveries expire with it), sends expiry notices to every surface the approval card reached (vellum conversations get an assistant message; external channels get a direct channel reply), and injects a `[GUARDIAN_TIMEOUT]` instruction so the model apologizes to the caller and offers a message or callback on the next voice turn.
269
269
 
270
- **Background expiry sweep:** Independent of any active call, a 60-second sweep asks the gateway to CAS-expire pending guardian requests past their `expiresAt` (`guardian_requests_sweep_expired` — a concurrent decision that wins the race is never overwritten), then fans out the side effects per expired row: withdrawing the approval cards on every surface, notifying the requester, and releasing any in-memory pending interaction.
270
+ **Background expiry sweep:** Independent of any active call, a 60-second sweep lists pending guardian requests past their `expiresAt` (`guardian_requests_list_expired_pending`), runs the side effects per row (withdrawing the approval cards on every surface, notifying the requester, releasing any in-memory pending interaction), and then confirms each with the per-request `guardian_requests_expire` CAS; a decision arriving past the deadline loses to expiry atomically, since the decide CAS guards on `expires_at`.
271
271
 
272
272
  **Generated messaging requirement:** All user-facing copy in the guardian timeout/expiry path is generated through the `guardian-action-message-composer.ts` composition system, which uses a 2-tier priority chain: (1) daemon-injected LLM generator for natural, varied text; (2) deterministic fallback templates for reliability. No hardcoded user-facing strings exist in the flow files outside of internal log messages and LLM-instruction prompts. A guard test (`guardian-action-no-hardcoded-copy.test.ts`) enforces this invariant.
273
273
 
274
274
  **Key source files:**
275
275
 
276
- | File | Purpose |
277
- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
278
- | `src/channels/gateway-guardian-requests.ts` | Gateway-backed guardian-request client: typed wrappers over the `guardian_requests_*` IPC routes that own request/delivery state and expiry |
279
- | `src/runtime/routes/guardian-expiry-sweep.ts` | Periodic 60s sweep: gateway CAS-expiry (`guardian_requests_sweep_expired`) plus card-withdrawal, requester-notice, and pending-interaction fan-out |
280
- | `src/calls/call-controller.ts` | Voice timeout handling: expires the request, sends expiry notices, injects `[GUARDIAN_TIMEOUT]` instruction for the generated voice response |
281
- | `src/calls/guardian-action-sweep.ts` | Expiry notice delivery to guardian destinations (vellum conversation message or direct channel reply) |
282
- | `src/runtime/guardian-action-message-composer.ts` | 2-tier text generation: daemon-injected LLM generator with deterministic fallback templates |
276
+ | File | Purpose |
277
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
278
+ | `src/channels/gateway-guardian-requests.ts` | Gateway-backed guardian-request client: typed wrappers over the `guardian_requests_*` IPC routes that own request/delivery state and expiry |
279
+ | `src/runtime/routes/guardian-expiry-sweep.ts` | Periodic 60s sweep: bounded expired-pending list, per-row card-withdrawal, requester-notice, and pending-interaction fan-out, then per-request expire CAS |
280
+ | `src/calls/call-controller.ts` | Voice timeout handling: expires the request, sends expiry notices, injects `[GUARDIAN_TIMEOUT]` instruction for the generated voice response |
281
+ | `src/calls/guardian-action-sweep.ts` | Expiry notice delivery to guardian destinations (vellum conversation message or direct channel reply) |
282
+ | `src/runtime/guardian-action-message-composer.ts` | 2-tier text generation: daemon-injected LLM generator with deterministic fallback templates |
283
283
 
284
284
  ### WhatsApp Channel (Meta Cloud API)
285
285
 
@@ -2066,7 +2066,7 @@ Connected channels are resolved at signal emission time: vellum is always includ
2066
2066
  | ---------------------------------------- | ------------------------------------------------------ | ----------------------------------- | ---------------------------------- | ------------------------------------------------------- |
2067
2067
  | API key | CES / encrypted file store | Encrypted binary | CES API / `secure-keys.ts` | Permanent |
2068
2068
  | Credential secrets | CES / encrypted file store | Encrypted binary | `secure-keys.ts` wrapper | Permanent (until deleted via tool) |
2069
- | Credential metadata | `$VELLUM_WORKSPACE_DIR/data/credentials/metadata.json` | JSON | Atomic file write | Permanent (until deleted via tool) |
2069
+ | Credential records | CES `<cesDataRoot>/metadata.json` | JSON | CES RPC/HTTP + atomic file write | Permanent (until deleted via tool) |
2070
2070
  | Integration OAuth tokens | CES / encrypted file store (via `secure-keys.ts`) | Encrypted binary | `TokenManager` auto-refresh | Until disconnected or revoked |
2071
2071
  | User preferences | UserDefaults | plist | Foundation | Permanent |
2072
2072
  | Session logs | `~/Library/.../logs/session-*.json` | JSON per session | Swift Codable | Unbounded |
package/Dockerfile CHANGED
@@ -117,6 +117,7 @@ RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx
117
117
  COPY --from=builder /app /app
118
118
 
119
119
  # Copy source after installing dependencies for better layer caching.
120
+ COPY packages/avatar-catalog /app/packages/avatar-catalog
120
121
  COPY packages/avatar-manifest /app/packages/avatar-manifest
121
122
  COPY packages/ces-client /app/packages/ces-client
122
123
  COPY packages/service-contracts /app/packages/service-contracts
@@ -229,7 +229,7 @@ User messages are scanned at ingress (`secret-ingress.ts`) against known credent
229
229
  | Component | Location | What it stores |
230
230
  | ------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
231
231
  | Secret values | CES credential store or encrypted file store | Encrypted credential values keyed as `credential/{service}/{field}`. Stored via CES RPC (primary), CES HTTP (containerized), or encrypted file store (fallback). |
232
- | Credential metadata | `$VELLUM_WORKSPACE_DIR/data/credentials/metadata.json` | Service, field, label, policy (allowedTools, allowedDomains), timestamps |
232
+ | Credential records | CES `<cesDataRoot>/metadata.json` | Service, field, alias, policy (`allowedTools`, `allowedDomains`, `injectionTemplates`), timestamps. Served over CES RPC/HTTP. |
233
233
  | Config | `$VELLUM_WORKSPACE_DIR/config.*` | `secretDetection` settings: enabled, blockIngress, allowOneTimeSend, blockTokenShapedMessages (default `true` — whole-message token-shape heuristic) |
234
234
 
235
235
  ### Key Files
@@ -52,7 +52,7 @@ interface ToolExecutionResult {
52
52
  }
53
53
  ```
54
54
 
55
- Existing examples: `EditEngineResult` in `edit-engine.ts`, `PathResult` in `path-policy.ts`, `ToolExecutionResult` in `tools/types.ts`, `MemoryRecallResult` in `memory/retriever.ts`.
55
+ Existing examples: `EditEngineResult` in `edit-engine.ts`, `PathResult` in `path-policy.ts`, `ToolExecutionResult` in `tools/types.ts`.
56
56
 
57
57
  ## 3. Never return null/undefined to indicate failure
58
58
 
@@ -66,7 +66,6 @@ Returning `undefined` is acceptable only for **lookup functions** where "not fou
66
66
  | ----------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
67
67
  | Agent loop (`agent/loop.ts`) | Throws + catch-and-emit | Unrecoverable provider errors break the loop; expected errors (abort, tool-use limits) are caught and emitted as events |
68
68
  | Tool executor (`tools/executor.ts`) | Result object (`ToolExecutionResult`) | Tool failures are expected operational outcomes — permission denied, unknown tool, sandbox violations. Never throws to callers |
69
- | Memory retriever (`memory/retriever.ts`) | Result object (`MemoryRecallResult`) with degraded/reason fields | Graceful degradation — embedding failures, search failures degrade quality without crashing |
70
69
  | Filesystem tools (`path-policy.ts`, `edit-engine.ts`) | Discriminated union (`{ ok, reason }`) | Validation outcomes that the caller must handle (out of bounds, not found, ambiguous) |
71
70
  | Subagent manager (`subagent/manager.ts`) | Throws for precondition violations, string literal unions for expected outcomes | Depth limit exceeded is a bug; `sendMessage` returns `'not_found' \| 'terminal' \| 'queue_full'` as expected states |
72
71
  | Interactive UI (`cli/commands/ui.ts`) | Result object (`InteractiveUiResult`) with `status` + exit codes | User cancel and timeout are expected operational outcomes, not errors. IPC failures are exceptional. |
@@ -54,15 +54,54 @@ anti-pattern was retired in #35642 and again in the ask_question redesign).
54
54
  | Kind-specific follow-through | resolver registry (`kind` → resolver) | switch statements in the router |
55
55
  | Reply understanding | guardian reply router (codes, buttons, reactions, modes) | per-feature inbound intercepts |
56
56
 
57
+ ## The canonical home-feed projection
58
+
59
+ Every pending request is also exactly one home-feed item, the bell's
60
+ "Needs attention" row. `notifications/guardian-feed-projection.ts` owns
61
+ it end to end:
62
+
63
+ - The pipeline's feed side effect writes it keyed
64
+ `guardian:<requestId>`, so the feed's replace-in-place merge keeps one
65
+ item per request. Its typed `guardianRequest` field
66
+ (`FeedItemGuardianRequestSchema` in `api/responses/home.ts`) is a read
67
+ projection of the gateway row: intent, status, requester, tool, source
68
+ reference, and the Slack-DM card deep links. It is never a delivery
69
+ row of its own; clients derive every affordance from `status`.
70
+ - `withdrawGuardianRequestCards` rewrites it into a terminal receipt as
71
+ one of the surfaces it settles, and its failure gates `complete` the
72
+ same way a failed card edit does. The write-vs-resolve race converges
73
+ from the other side too: the feed writer re-checks canonical status
74
+ after its append.
75
+ - The feed writer's bulk-dismiss pass skips pending guardian items
76
+ (`isPendingGuardianFeedItem`), so "Clear all" can never retire an
77
+ unresolved request; only its receipt is clearable.
78
+ - The guardian expiry sweep runs `reconcileGuardianFeedProjections()`
79
+ each round: pending requests missing an item are backfilled from the
80
+ request row (no conversation is generated), and an item still
81
+ actionable for a terminal request is receipted with a loud log line.
82
+
83
+ Routing policy for channel-delivered cards: a guardian card sent to a
84
+ channel chat (Slack, Telegram, Discord, WhatsApp) is a delivery
85
+ projection, not conversation content. Channel deliveries are not
86
+ paired with any conversation (`conversation-pairing.ts` skips every
87
+ non-vellum guardian delivery), and the Slack DM cold backfill skips
88
+ messages whose ts matches a recorded guardian delivery for the chat
89
+ (`guardian_requests_list_deliveries_by_chat`), so a card never enters
90
+ a Vellum transcript. The in-app homes of a request are the feed item
91
+ and the source conversation's pinned vellum card.
92
+
57
93
  ## Cards are not conversation history
58
94
 
59
- `pairDeliveryWithConversation` persists one message row per delivery so the
60
- card renders and deep-links. For a guardian card that row is addressed to a
95
+ `pairDeliveryWithConversation` persists one message row for the vellum
96
+ delivery so the card renders and deep-links. That row is addressed to a
61
97
  conversation the request is _about_, not one the assistant is speaking in:
62
98
  `buildVellumCardAffinity` pins the vellum card to the originating
63
- conversation, and a channel card lands in whatever conversation the guardian's
64
- chat binds to. Either way the row is written straight to the DB by the
65
- notification pipeline, so the live turn's in-memory history never sees it.
99
+ conversation. Channel guardian deliveries pair no conversation (see the
100
+ routing policy above), so the vellum row is the only card row written; it
101
+ goes straight to the DB by the notification pipeline, so the live turn's
102
+ in-memory history never sees it. Rows that channel deliveries paired
103
+ before the projection-only policy still exist and are covered by the same
104
+ filter below.
66
105
 
67
106
  That row must never be replayed to the model. The conversation it lands in is
68
107
  typically parked mid-approval, with its last assistant message carrying the
@@ -156,3 +195,10 @@ this exists to prevent. Telegram group chats carry the same exposure and are
156
195
  not covered: only Slack has a chat whose privacy can be read off its id. They remain load-bearing for that flow, and
157
196
  the reply router runs first for everything the pipeline owns. Converge new
158
197
  work on the pipeline; do not extend the legacy interception.
198
+
199
+ Any flow that resolves a confirmation in-memory first (this rail, the in-app
200
+ `/v1/confirm` route, the auto-deny sweeps) brings the gateway row to the
201
+ matching terminal status through `approvals/guardian-request-status-sync.ts`,
202
+ which also runs the same cross-surface card withdrawal the decision primitive
203
+ runs whenever its CAS is the one that landed: no projection of a resolved
204
+ request may stay actionable, whichever rail resolved it.
@@ -68,12 +68,12 @@ requested -> pending_guardian -> verification_pending -> active | denied | expir
68
68
 
69
69
  Identity binding ensures the verification code can only be consumed by the intended recipient on the intended channel. The binding fields are set on the gateway `channel_verification_sessions` record when the session is created; the check itself is `checkIdentityMatch` (`gateway/src/verification/identity-match.ts`).
70
70
 
71
- | Channel | Identity fields | Binding behavior |
72
- | --------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
73
- | Telegram | `expectedExternalUserId` = Telegram user ID, `expectedChatId` = Telegram chat ID | Both are taken from the original denied message's metadata. The `identityBindingStatus` is `'bound'`. Verification requires the actor's user ID or chat ID to match (user ID required when both are set on a shared chat). |
74
- | Voice | `expectedPhoneE164` = phone number in E.164 format | Phone-based identity binding. Verification requires the caller's number to match the expected phone. |
71
+ | Channel | Identity fields | Binding behavior |
72
+ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | Telegram | `expectedExternalUserId` = Telegram user ID, `expectedChatId` = Telegram chat ID | Both are taken from the original denied message's metadata. The `identityBindingStatus` is `'bound'`. Verification requires the actor's user ID or chat ID to match (user ID required when both are set on a shared chat). |
74
+ | Voice | `expectedPhoneE164` = phone number in E.164 format | Phone-based identity binding. Verification requires the caller's number to match the expected phone. |
75
75
  | Discord | `expectedExternalUserId` = Discord user snowflake. `expectedChatId` is unset on the guardian-initiated and trusted-contact paths, and is the originating guild channel on the access-request approval path (`mint_outbound_session`), which sets it for every channel alike. | The code is delivered to a DM and answered there, but the DM channel does not exist until it is opened, so those two paths have no chat id to record at mint time. Where the approval path does set one, it is a room rather than an identity and never becomes a sufficient credential on its own: `checkIdentityMatch` requires the `expectedExternalUserId` match whenever both fields are set. That path also writes the room into `destinationAddress`, where it describes nothing the code is ever sent to (see LUM-3110). |
76
- | Bootstrap | none yet (`identityBindingStatus: 'pending_bootstrap'`) | Unbound deep-link sessions use a 32-byte hex token instead of a numeric code; the identity is bound when the token is redeemed (`verification_sessions_bind_identity`). |
76
+ | Bootstrap | none yet (`identityBindingStatus: 'pending_bootstrap'`) | Unbound deep-link sessions use a 32-byte hex token instead of a numeric code; the identity is bound when the token is redeemed (`verification_sessions_bind_identity`). |
77
77
 
78
78
  **Anti-oracle invariant:** When identity verification fails, the error message is identical to the "invalid or expired code" message. This prevents attackers from distinguishing between a wrong code and a wrong identity, which would leak information about which identities have pending sessions.
79
79
 
@@ -129,10 +129,10 @@ Separately, the admitted-mode introduction **nudge** (for a contact who cleared
129
129
 
130
130
  ### Stage: `expired`
131
131
 
132
- | Store | Table | Record |
133
- | ------------------------------------------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------- |
134
- | gateway `guardian-request-store.ts` (gateway DB) | `guardian_requests` | Updated to `status: 'expired'` via `guardian_requests_sweep_expired` (the daemon's sweep runs every 60s). |
135
- | `session-store.ts` (gateway DB) | `channel_verification_sessions` | Expires naturally: `expiresAt < Date.now()` makes it invisible to `findPendingSessionByHash()`. |
132
+ | Store | Table | Record |
133
+ | ------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
134
+ | gateway `guardian-request-store.ts` (gateway DB) | `guardian_requests` | Updated to `status: 'expired'` via the per-request `guardian_requests_expire` CAS, after the daemon's 60s sweep has run that row's side effects. |
135
+ | `session-store.ts` (gateway DB) | `channel_verification_sessions` | Expires naturally: `expiresAt < Date.now()` makes it invisible to `findPendingSessionByHash()`. |
136
136
 
137
137
  ### Invites (alternative path)
138
138
 
@@ -216,10 +216,11 @@ sequenceDiagram
216
216
 
217
217
  else Guardian never responds
218
218
  Note over A: runGuardianExpirySweep()<br/>runs every 60 seconds
219
- A->>GW: guardian_requests_sweep_expired (IPC)
220
- GW->>GW: Approval TTL elapsed → status: 'expired' (CAS)
219
+ A->>GW: guardian_requests_list_expired_pending (IPC)
221
220
  A->>A: Withdraw approval cards on all surfaces
222
221
  A-->>U: "Your access request has expired."
222
+ A->>GW: guardian_requests_expire (IPC)
223
+ GW->>GW: status: 'expired' (CAS, the fan-out's receipt)
223
224
 
224
225
  else Code expires (requester never enters it)
225
226
  Note over GW: Verification session TTL: 10 min
@@ -232,7 +233,7 @@ sequenceDiagram
232
233
 
233
234
  ### Guardian never responds
234
235
 
235
- - The `runGuardianExpirySweep()` timer (`runtime/routes/guardian-expiry-sweep.ts`) runs every 60 seconds; the gateway CAS-transitions pending requests past their `expiresAt` to `status: 'expired'` (`guardian_requests_sweep_expired`) and returns the expired rows.
236
+ - The `runGuardianExpirySweep()` timer (`runtime/routes/guardian-expiry-sweep.ts`) runs every 60 seconds; the gateway returns a bounded, read-only batch of pending requests past their `expiresAt` (`guardian_requests_list_expired_pending`), and the daemon confirms each row with the per-request `guardian_requests_expire` CAS only after running its side effects, so the fan-out is recoverable from state.
236
237
  - The daemon then withdraws the now-stale approval cards on every surface and notifies the requester that the request expired.
237
238
 
238
239
  ### Verification code expires
package/knip.json CHANGED
@@ -15,6 +15,7 @@
15
15
  "ignore": ["src/config/preloaded-apps/**"],
16
16
  "ignoreDependencies": [
17
17
  "@microsoft/api-extractor",
18
+ "@vellumai/avatar-catalog",
18
19
  "@vellumai/avatar-manifest",
19
20
  "@vellumai/ces-client",
20
21
  "@vellumai/credential-storage",
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@vellumai/avatar-catalog",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./colors": "./src/colors.ts"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "bunx tsc --noEmit",
13
+ "test": "bun test src/"
14
+ },
15
+ "devDependencies": {
16
+ "@types/bun": "1.3.11",
17
+ "typescript": "5.9.3"
18
+ }
19
+ }
@@ -1,43 +1,18 @@
1
- interface BodyShapeDefinition {
2
- id: string;
3
- viewBox: { width: number; height: number };
4
- faceCenter: { x: number; y: number };
5
- svgPath: string;
6
- }
7
-
8
- interface EyePathDefinition {
9
- svgPath: string;
10
- color: string; // hex e.g. "#F2F2F2"
11
- }
12
-
13
- interface EyeStyleDefinition {
14
- id: string;
15
- sourceViewBox: { width: number; height: number };
16
- eyeCenter: { x: number; y: number };
17
- paths: EyePathDefinition[];
18
- }
19
-
20
- interface ColorDefinition {
21
- id: string;
22
- hex: string; // e.g. "#4C9B50"
23
- }
24
-
25
- interface FaceCenterOverride {
26
- bodyShape: string;
27
- eyeStyle: string;
28
- faceCenter: { x: number; y: number };
29
- }
30
-
31
- interface CharacterComponents {
32
- bodyShapes: BodyShapeDefinition[];
33
- eyeStyles: EyeStyleDefinition[];
34
- colors: ColorDefinition[];
35
- faceCenterOverrides: FaceCenterOverride[];
36
- }
1
+ import { AVATAR_COLORS } from "./colors.js";
2
+ import type { CharacterComponents } from "./types.js";
37
3
 
38
4
  const SCLERA = "#F2F2F2";
39
5
  const PUPIL = "#1A1A1A";
40
6
 
7
+ /**
8
+ * Every body shape, eye style, and face-centre override an avatar can be
9
+ * composed from, plus the palette re-exposed on `colors`.
10
+ *
11
+ * This is the single definition. The daemon serves it over
12
+ * `/avatar/character-components`; clients that must compose an avatar before
13
+ * the daemon answers (a hatching screen, a chooser card) import it directly
14
+ * so no copy has to be kept in step with this one.
15
+ */
41
16
  export function getCharacterComponents(): CharacterComponents {
42
17
  return {
43
18
  bodyShapes: [
@@ -369,14 +344,7 @@ export function getCharacterComponents(): CharacterComponents {
369
344
  },
370
345
  ],
371
346
 
372
- colors: [
373
- { id: "green", hex: "#4C9B50" },
374
- { id: "orange", hex: "#E9642F" },
375
- { id: "pink", hex: "#DB4B77" },
376
- { id: "purple", hex: "#A665C9" },
377
- { id: "teal", hex: "#0E9B8B" },
378
- { id: "yellow", hex: "#E9C91A" },
379
- ],
347
+ colors: AVATAR_COLORS,
380
348
 
381
349
  faceCenterOverrides: [
382
350
  // Ghost body — shift non-native eyes from y=167 -> y=200
@@ -0,0 +1,19 @@
1
+ import type { ColorDefinition } from "./types.js";
2
+
3
+ /**
4
+ * The avatar colour palette, in the order the picker offers it.
5
+ *
6
+ * Its own module because the palette is the one part of the catalog that
7
+ * boot-path code needs: a sidebar pin resolves a stored colour id on first
8
+ * render, and the accent CSS variable falls back to the first entry before
9
+ * the daemon's own catalog arrives. Importing the shapes to reach six hex
10
+ * values would put ~47 kB of SVG path data on that path.
11
+ */
12
+ export const AVATAR_COLORS: ColorDefinition[] = [
13
+ { id: "green", hex: "#4C9B50" },
14
+ { id: "orange", hex: "#E9642F" },
15
+ { id: "pink", hex: "#DB4B77" },
16
+ { id: "purple", hex: "#A665C9" },
17
+ { id: "teal", hex: "#0E9B8B" },
18
+ { id: "yellow", hex: "#E9C91A" },
19
+ ];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @vellumai/avatar-catalog: the single definition of the components an
3
+ * assistant's character avatar is composed from.
4
+ *
5
+ * The daemon serves this catalog over `/avatar/character-components` and
6
+ * renders PNGs from it; clients bundle it because an avatar has to compose
7
+ * before the daemon can answer (the hatching screen, the assistant chooser,
8
+ * a transcript's subagent chips). Bundling is why this is a package rather
9
+ * than a daemon module every client copies.
10
+ *
11
+ * The palette is deliberately not re-exported here. It lives behind
12
+ * `@vellumai/avatar-catalog/colors`, and importing it from that subpath is
13
+ * what keeps boot-path code (a sidebar pin resolving a stored colour id, the
14
+ * accent variable's fallback) off the ~47 kB of SVG path data the shapes
15
+ * carry. Re-exporting it would make the expensive import the convenient one.
16
+ *
17
+ * The shapes' types stay internal for the same reason they were private to
18
+ * the daemon module this package replaces: the wire contract for this data is
19
+ * the route's `responseBody` schema, which clients consume through their
20
+ * generated OpenAPI types. A hand-written type exported from here would be a
21
+ * second name for that shape, and the closer one to reach for.
22
+ *
23
+ * Leaf package: no dependencies, no runtime imports.
24
+ */
25
+ export { getCharacterComponents } from "./catalog.js";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Shapes for the catalog literal in this package.
3
+ *
4
+ * Declared here rather than derived from the generated OpenAPI types because
5
+ * this is a leaf package with no dependencies, and the clients that consume
6
+ * those generated types depend on it rather than the other way round. The
7
+ * canonical wire shape is the `avatar/character-components` route's
8
+ * `responseBody` schema; these are not exported so no consumer picks them up
9
+ * as a second name for it.
10
+ */
11
+
12
+ export interface BodyShapeDefinition {
13
+ id: string;
14
+ viewBox: { width: number; height: number };
15
+ faceCenter: { x: number; y: number };
16
+ svgPath: string;
17
+ }
18
+
19
+ export interface EyePathDefinition {
20
+ svgPath: string;
21
+ color: string; // hex e.g. "#F2F2F2"
22
+ }
23
+
24
+ export interface EyeStyleDefinition {
25
+ id: string;
26
+ sourceViewBox: { width: number; height: number };
27
+ eyeCenter: { x: number; y: number };
28
+ paths: EyePathDefinition[];
29
+ }
30
+
31
+ export interface ColorDefinition {
32
+ id: string;
33
+ hex: string; // e.g. "#4C9B50"
34
+ }
35
+
36
+ export interface FaceCenterOverride {
37
+ bodyShape: string;
38
+ eyeStyle: string;
39
+ faceCenter: { x: number; y: number };
40
+ }
41
+
42
+ export interface CharacterComponents {
43
+ bodyShapes: BodyShapeDefinition[];
44
+ eyeStyles: EyeStyleDefinition[];
45
+ colors: ColorDefinition[];
46
+ faceCenterOverrides: FaceCenterOverride[];
47
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "types": ["bun"]
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -11,6 +11,7 @@
11
11
  "./client-metadata": "./src/client-metadata.ts",
12
12
  "./credential-rpc": "./src/credential-rpc.ts",
13
13
  "./ingress": "./src/ingress.ts",
14
+ "./no-response": "./src/no-response.ts",
14
15
  "./remote-web-pairing": "./src/remote-web-pairing.ts",
15
16
  "./twilio-ingress": "./src/twilio-ingress.ts",
16
17
  "./trust-rules": "./src/trust-rules.ts",
@@ -66,6 +66,13 @@ export function isChannelId(value: unknown): value is ChannelId {
66
66
  * That irregularity is why this is stated rather than derived from the key,
67
67
  * and it is stated here because this file already owns what a channel is.
68
68
  *
69
+ * Both senses can hold a user token, which is the sharpest edge. The `slack`
70
+ * integration's persisted token *is* the installer's user token, held on its
71
+ * OAuth connection. `slack_channel` holds an optional `user_token` in the
72
+ * credential store, beside its bot and app tokens. Same words, different
73
+ * homes, and only the second is a credential-store key: a pasted token is
74
+ * always the channel's, because the integration's never leaves the exchange.
75
+ *
69
76
  * Deliberately only the key. What fields each credential requires is declared
70
77
  * once already, per service, in the gateway's credential specs; restating it
71
78
  * here would be a second copy of a different fact.
@@ -27,6 +27,7 @@ export * from "./handles.js";
27
27
  export * from "./rpc.js";
28
28
  export * from "./trust-rules.js";
29
29
  export * from "./ingress.js";
30
+ export * from "./no-response.js";
30
31
  export * from "./remote-web-pairing.js";
31
32
  export * from "./twilio-ingress.js";
32
33
  export * from "./url-normalization.js";
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The `<no_response/>` sentinel: the cross-service convention for a turn
3
+ * that deliberately produces no user-visible reply.
4
+ *
5
+ * The model emits it, the daemon stamps and strips it, channel delivery
6
+ * suppresses it, and clients hold live-streamed prefixes of it back from
7
+ * display. One definition here keeps every consumer's parsing identical;
8
+ * a hand-rolled copy in any one of them is how the case-sensitivity of a
9
+ * regex drifts.
10
+ */
11
+
12
+ /** Matches a message whose entire content is the sentinel. */
13
+ const NO_RESPONSE_ONLY_RE = /^\s*<no_response\s*\/?>\s*$/i;
14
+
15
+ /**
16
+ * Whether `text` is nothing but the sentinel: the whole reply is a
17
+ * deliberate non-response, as opposed to real content with an inline
18
+ * sentinel mixed in.
19
+ */
20
+ export function isNoResponseOnlyText(text: string): boolean {
21
+ return NO_RESPONSE_ONLY_RE.test(text);
22
+ }
23
+
24
+ /** Matches every sentinel occurrence for stripping it out of mixed content. */
25
+ export const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/gi;
26
+
27
+ /**
28
+ * Detection variant without the `g` flag: a `g`-flagged regex is stateful
29
+ * under `.test()` (it resumes from `lastIndex`), so reusing
30
+ * {@link NO_RESPONSE_INLINE_RE} for detection would alternate between
31
+ * matches and misses across calls.
32
+ */
33
+ const NO_RESPONSE_MARKER_RE = new RegExp(NO_RESPONSE_INLINE_RE.source, "i");
34
+
35
+ /** Whether `text` contains the sentinel anywhere. */
36
+ export function containsNoResponseMarker(text: string): boolean {
37
+ return NO_RESPONSE_MARKER_RE.test(text);
38
+ }
39
+
40
+ /** Removes every sentinel occurrence, trimming the leftover whitespace. */
41
+ export function stripNoResponseMarkers(text: string): string {
42
+ return text.replace(NO_RESPONSE_INLINE_RE, "").trim();
43
+ }
44
+
45
+ const NO_RESPONSE_SENTINEL_FORMS = [
46
+ "<no_response/>",
47
+ "<no_response />",
48
+ "<no_response>",
49
+ ] as const;
50
+
51
+ /**
52
+ * Whether `text` could still grow into the sentinel, i.e. it is a leading
53
+ * substring of one of its forms. Holding on these keeps a slowly-streamed
54
+ * `<no_response/>` from surfacing as visible partial content.
55
+ */
56
+ export function isPotentialNoResponsePrefix(text: string): boolean {
57
+ const lower = text.trim().toLowerCase();
58
+ if (lower.length === 0) {
59
+ return false;
60
+ }
61
+ return NO_RESPONSE_SENTINEL_FORMS.some((sentinel) =>
62
+ sentinel.startsWith(lower),
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Whether `text` carries user-visible content worth acting on now. Returns
68
+ * `false` for empty text, the standalone sentinel, and any prefix that could
69
+ * still complete into one; returns `true` once real content remains after
70
+ * stripping inline sentinels.
71
+ */
72
+ export function hasDeliverableAssistantText(text: string): boolean {
73
+ const trimmed = text.trim();
74
+ if (trimmed.length === 0) {
75
+ return false;
76
+ }
77
+ if (NO_RESPONSE_ONLY_RE.test(trimmed)) {
78
+ return false;
79
+ }
80
+ if (isPotentialNoResponsePrefix(trimmed)) {
81
+ return false;
82
+ }
83
+ return stripNoResponseMarkers(trimmed).length > 0;
84
+ }