@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
@@ -0,0 +1,256 @@
1
+ /**
2
+ * The parking rail behind every guardian form.
3
+ *
4
+ * A form-backed command does not write. It parks here, its form goes to every
5
+ * connected client, and the write happens elsewhere (today the gateway, which
6
+ * owns contact writes) and reports back. With no human at a form, nothing is
7
+ * written and the call times out. That property is what makes these commands
8
+ * guardian-gated, and it lives in this file rather than in any one form.
9
+ *
10
+ * What a form owner supplies: how to broadcast it, how to take it down, and
11
+ * what its result looks like. Everything below is the same for all of them.
12
+ *
13
+ * Lifecycle:
14
+ * open -> the call parks, the form goes out, a deadline starts
15
+ * claim -> one submission wins; the answer deadline is swapped for a
16
+ * bounded settle window while the write runs
17
+ * resolve-> the write reports back, the parked call returns, the card is
18
+ * retired everywhere it is showing
19
+ *
20
+ * See `docs/guardian-forms.md` for how to add one.
21
+ */
22
+
23
+ import { v4 as uuid } from "uuid";
24
+
25
+ import {
26
+ GUARDIAN_FORM_DEFAULT_TIMEOUT_MS,
27
+ GUARDIAN_FORM_SETTLE_MS,
28
+ } from "../util/guardian-form-timeouts.js";
29
+ import { getLogger } from "../util/logger.js";
30
+
31
+ const log = getLogger("guardian-form-registry");
32
+
33
+ /** Every form's result carries at least this; owners widen it. */
34
+ export interface GuardianFormResult {
35
+ ok: boolean;
36
+ error?: string;
37
+ }
38
+
39
+ export type GuardianFormClosedReason = "answered" | "cancelled" | "timed_out";
40
+
41
+ /**
42
+ * How a form reaches clients and how it leaves them.
43
+ *
44
+ * Both are the owner's, because the event names are wire contract. The
45
+ * registry decides *when* they fire.
46
+ */
47
+ export interface GuardianFormBroadcast {
48
+ open: (requestId: string) => void;
49
+ closed: (requestId: string, reason: GuardianFormClosedReason) => void;
50
+ }
51
+
52
+ interface PendingGuardianForm {
53
+ kind: string;
54
+ resolve: (result: GuardianFormResult) => void;
55
+ timer: ReturnType<typeof setTimeout>;
56
+ closed: GuardianFormBroadcast["closed"];
57
+ /**
58
+ * Owner-defined state the write may need to read back out of band, for a
59
+ * client with no field to echo it in. Opaque here.
60
+ */
61
+ meta: Record<string, unknown>;
62
+ logContext: Record<string, unknown>;
63
+ /**
64
+ * Set once a submission has been accepted. The form went to every connected
65
+ * client, so more than one can answer it; the first claim wins and the rest
66
+ * write nothing.
67
+ */
68
+ claimed: boolean;
69
+ }
70
+
71
+ const pendingForms = new Map<string, PendingGuardianForm>();
72
+
73
+ export interface OpenGuardianFormOptions {
74
+ /** Which form this is, for logs and for scoping the open-form check. */
75
+ kind: string;
76
+ broadcast: GuardianFormBroadcast;
77
+ /** How long to hold it open. Defaults to the shared 5 minutes. */
78
+ timeoutMs?: number;
79
+ meta?: Record<string, unknown>;
80
+ /** Form-specific fields folded into this form's log lines. */
81
+ logContext?: Record<string, unknown>;
82
+ /** What the parked call is told when nobody answers. */
83
+ timeoutError?: string;
84
+ }
85
+
86
+ /**
87
+ * Park a call until the guardian answers its form.
88
+ *
89
+ * The returned promise settles exactly once: on an answer, on a dismissal, or
90
+ * on the deadline. It never rejects, because the caller's failure is a result
91
+ * it has to report, not an exception to unwind through.
92
+ */
93
+ export function openGuardianForm<TResult extends GuardianFormResult>(
94
+ options: OpenGuardianFormOptions,
95
+ ): Promise<TResult> {
96
+ const {
97
+ kind,
98
+ broadcast,
99
+ timeoutMs,
100
+ meta = {},
101
+ logContext = {},
102
+ timeoutError = "Prompt timed out",
103
+ } = options;
104
+ const requestId = uuid();
105
+
106
+ return new Promise<TResult>((resolve) => {
107
+ const timer = setTimeout(() => {
108
+ const pending = pendingForms.get(requestId);
109
+ if (!pending) {
110
+ return;
111
+ }
112
+ log.warn({ requestId, kind, ...logContext }, "Guardian form timed out");
113
+ expireForm(requestId, pending, timeoutError);
114
+ }, timeoutMs ?? GUARDIAN_FORM_DEFAULT_TIMEOUT_MS);
115
+
116
+ pendingForms.set(requestId, {
117
+ kind,
118
+ // The registry knows only the base shape; the owner's extra fields ride
119
+ // through resolveGuardianForm untouched, so the cast is confined here.
120
+ resolve: resolve as (result: GuardianFormResult) => void,
121
+ timer,
122
+ closed: broadcast.closed,
123
+ meta,
124
+ logContext,
125
+ claimed: false,
126
+ });
127
+
128
+ broadcast.open(requestId);
129
+ log.info({ requestId, kind, ...logContext }, "Guardian form broadcast");
130
+ });
131
+ }
132
+
133
+ /**
134
+ * End a form nobody answered in time, and tell the clients showing it.
135
+ *
136
+ * Without the broadcast the card stays up offering an answer that would now be
137
+ * refused: a form that has closed accepts no submission.
138
+ */
139
+ function expireForm(
140
+ requestId: string,
141
+ pending: PendingGuardianForm,
142
+ error: string,
143
+ ): void {
144
+ pendingForms.delete(requestId);
145
+ pending.resolve({ ok: false, error });
146
+ pending.closed(requestId, "timed_out");
147
+ }
148
+
149
+ /**
150
+ * Claim a pending form so exactly one submission can write.
151
+ *
152
+ * The daemon holds the only record of which forms are still open, so it is the
153
+ * one place that can decide a race between two clients answering the same
154
+ * broadcast, and the only one that can tell whether an id is even the right
155
+ * form. Pass `expectedKind` whenever the caller knows which form it is
156
+ * writing for. First caller wins; the rest are told why they lost, so the writer
157
+ * can tell "somebody already answered this" (leave their answer alone) apart
158
+ * from "no such form" (expired or already resolved, so nothing should be
159
+ * written at all).
160
+ */
161
+ export function claimGuardianForm(
162
+ requestId: string,
163
+ expectedKind?: string,
164
+ ): {
165
+ claimed: boolean;
166
+ reason?: "already_claimed" | "unknown" | "wrong_kind";
167
+ settleMs?: number;
168
+ } {
169
+ const pending = pendingForms.get(requestId);
170
+ if (!pending) {
171
+ return { claimed: false, reason: "unknown" };
172
+ }
173
+ if (expectedKind !== undefined && pending.kind !== expectedKind) {
174
+ // An id alone does not say which form it belongs to. Without this, a
175
+ // submission to form B's endpoint could claim an open form A: B's write
176
+ // would run, and A's parked caller would be handed B's result for a form
177
+ // it never showed.
178
+ log.warn(
179
+ { requestId, expectedKind, actualKind: pending.kind },
180
+ "Guardian form claim rejected: the id belongs to a different form",
181
+ );
182
+ return { claimed: false, reason: "wrong_kind" };
183
+ }
184
+ if (pending.claimed) {
185
+ return { claimed: false, reason: "already_claimed" };
186
+ }
187
+ pending.claimed = true;
188
+ // Answered, so its open-for-answers deadline gives way to a bounded settle
189
+ // window while the write reports back.
190
+ clearTimeout(pending.timer);
191
+ pending.timer = setTimeout(() => {
192
+ log.warn(
193
+ { requestId, kind: pending.kind, ...pending.logContext },
194
+ "Guardian form claimed but never settled; the write never reported back",
195
+ );
196
+ expireForm(requestId, pending, "The submitted form never completed");
197
+ }, GUARDIAN_FORM_SETTLE_MS);
198
+ // The claimer needs the window too: it is how long its write has to report
199
+ // back before the caller gives up, and it should not have to know the number
200
+ // independently.
201
+ return { claimed: true, settleMs: GUARDIAN_FORM_SETTLE_MS };
202
+ }
203
+
204
+ /**
205
+ * Hand a form's answer to the call parked on it and retire the card.
206
+ *
207
+ * An error covers a dismissal and a failed write alike: the form is over
208
+ * either way, and the caller is the one told why.
209
+ */
210
+ export function resolveGuardianForm<TResult extends GuardianFormResult>(
211
+ requestId: string,
212
+ result: TResult,
213
+ ): { resolved: boolean } {
214
+ const pending = pendingForms.get(requestId);
215
+ if (!pending) {
216
+ log.warn({ requestId }, "resolve: no pending guardian form found");
217
+ return { resolved: false };
218
+ }
219
+
220
+ clearTimeout(pending.timer);
221
+ pendingForms.delete(requestId);
222
+ pending.resolve(result);
223
+ pending.closed(requestId, result.ok ? "answered" : "cancelled");
224
+
225
+ log.info(
226
+ { requestId, kind: pending.kind, ...pending.logContext },
227
+ "Guardian form resolved",
228
+ );
229
+ return { resolved: true };
230
+ }
231
+
232
+ /**
233
+ * Whether any form of these kinds is open and still unanswered.
234
+ *
235
+ * Takes the kinds to check rather than sweeping the whole registry. Clients
236
+ * hold one card per kind, so a second broadcast of a kind already on screen
237
+ * replaces the first and leaves its command waiting on a form nobody can
238
+ * answer. A registry-wide check would go further than that and let any pending
239
+ * form block every other one, which is a rule each form should opt into rather
240
+ * than inherit.
241
+ */
242
+ export function hasUnclaimedGuardianForm(kinds: readonly string[]): boolean {
243
+ for (const pending of pendingForms.values()) {
244
+ if (!pending.claimed && kinds.includes(pending.kind)) {
245
+ return true;
246
+ }
247
+ }
248
+ return false;
249
+ }
250
+
251
+ /** Owner-defined state parked with a form, for a writer that must read it back. */
252
+ export function getGuardianFormMeta(
253
+ requestId: string,
254
+ ): Record<string, unknown> | undefined {
255
+ return pendingForms.get(requestId)?.meta;
256
+ }
@@ -23,6 +23,7 @@
23
23
  * to allow for incremental migration and independent testability.
24
24
  */
25
25
 
26
+ import type { InboundReactionPayload } from "@vellumai/gateway-client";
26
27
  import { isGuardianRequestExpired } from "@vellumai/gateway-client";
27
28
 
28
29
  import {
@@ -61,7 +62,7 @@ import type {
61
62
  ApprovalConversationGenerator,
62
63
  } from "./http-types.js";
63
64
  import * as pendingInteractions from "./pending-interactions.js";
64
- import { parseReactionCallbackData } from "./routes/channel-route-shared.js";
65
+ import { reactionDecisionForEmoji } from "./routes/channel-route-shared.js";
65
66
 
66
67
  const log = getLogger("guardian-reply-router");
67
68
 
@@ -106,7 +107,12 @@ export interface GuardianReplyContext {
106
107
  /** Callback data from button presses (e.g. `apr:<requestId>:<action>`). */
107
108
  callbackData?: string;
108
109
  /**
109
- * For emoji-reaction decisions (`callbackData` of `reaction:<emoji>`): the
110
+ * Structured reaction payload when the inbound event is a reaction; the
111
+ * reaction branch keys on it and reads nothing off callbackData.
112
+ */
113
+ reaction?: InboundReactionPayload;
114
+ /**
115
+ * For emoji-reaction decisions: the
110
116
  * channel-native id (e.g. Slack `ts`) of the message the reaction was
111
117
  * attached to. Used to recover the target request from its delivery record.
112
118
  */
@@ -368,23 +374,36 @@ export async function routeGuardianReply(
368
374
  // even when several cards are pending in the same chat, so — unlike the
369
375
  // text/NL paths — no clarification prompt is ever needed. `reaction_removed`
370
376
  // never expresses intent and is filtered out before reaching the router.
371
- if (
372
- callbackData?.startsWith("reaction:") &&
373
- !callbackData.startsWith("reaction_removed:")
374
- ) {
375
- const reaction = parseReactionCallbackData(callbackData);
377
+ if (ctx.reaction?.op === "added") {
378
+ const decision = reactionDecisionForEmoji(ctx.reaction.emoji);
376
379
  const guardianChatId = channelDeliveryContext?.guardianChatId;
377
- if (!reaction || !reactedMessageTs || !guardianChatId) {
380
+ if (!decision || !reactedMessageTs || !guardianChatId) {
378
381
  // Unknown emoji, or missing addressing context — not an actionable
379
382
  // approval reaction. Leave it for the caller to persist as a transcript
380
383
  // signal (it must not trigger an agent turn).
381
384
  return notConsumed();
382
385
  }
383
- const request = await getPendingRequestByDestinationMessageOrNull(
384
- channel,
385
- guardianChatId,
386
- reactedMessageTs,
387
- );
386
+ // A delivery row keys its card by the destination the adapter delivered
387
+ // to, and channels address that destination in one of two modalities: a
388
+ // chat (the conversation the reaction arrives from) or a person (the
389
+ // guardian's own user id, which person-addressed adapters store so a
390
+ // stale room id can never become a delivery target). The reactor on an
391
+ // approval card is the person it was delivered to, so their id is the
392
+ // second probe; identity validation in applyGuardianDecision still
393
+ // guards the decision itself.
394
+ const request =
395
+ (await getPendingRequestByDestinationMessageOrNull(
396
+ channel,
397
+ guardianChatId,
398
+ reactedMessageTs,
399
+ )) ??
400
+ (actor.actorExternalUserId
401
+ ? await getPendingRequestByDestinationMessageOrNull(
402
+ channel,
403
+ actor.actorExternalUserId,
404
+ reactedMessageTs,
405
+ )
406
+ : null);
388
407
  if (!request) {
389
408
  // The reacted message is not a known pending approval card (a stray
390
409
  // reaction, or one whose request was already resolved). Never approve
@@ -393,7 +412,7 @@ export async function routeGuardianReply(
393
412
  }
394
413
  return applyDecision(
395
414
  request.id,
396
- reaction.action,
415
+ decision.action,
397
416
  actor,
398
417
  undefined,
399
418
  channelDeliveryContext,
@@ -66,9 +66,16 @@ export async function generateInviteInstruction(params: {
66
66
  const handle = params.channelHandle
67
67
  ? ` at ${params.channelHandle}`
68
68
  : ` on ${channelLabel}`;
69
+ // Discord DMs only open between accounts that share a server, and a
70
+ // member can still have server-member DMs turned off, so the instruction
71
+ // names both or the invitee dead-ends before the code.
72
+ const discordCaveat =
73
+ params.channelType === "discord"
74
+ ? " They need to be in a server with me, with DMs from server members enabled, before they can DM me."
75
+ : "";
69
76
  const fallback = params.shareUrl
70
- ? `Send ${contact} this link: ${params.shareUrl} — or tell them to message me${handle} with the code below.`
71
- : `Tell ${contact} to message me${handle} with the code below.`;
77
+ ? `Send ${contact} this link: ${params.shareUrl}, or tell them to message me${handle} with the code below.${discordCaveat}`
78
+ : `Tell ${contact} to message me${handle} with the code below.${discordCaveat}`;
72
79
 
73
80
  const resolved = await resolveConfiguredProvider(
74
81
  "inviteInstructionGenerator",
@@ -101,6 +108,11 @@ export async function generateInviteInstruction(params: {
101
108
  "No share link is available for this channel. Do NOT mention sharing a link or URL.",
102
109
  );
103
110
  }
111
+ if (params.channelType === "discord") {
112
+ parts.push(
113
+ "Platform constraint: the contact must share a Discord server with the bot, and have DMs from server members enabled in their privacy settings, before they can send it a direct message. Mention this.",
114
+ );
115
+ }
104
116
  parts.push(
105
117
  "",
106
118
  "Requirements:",
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * Merge helper for `data/credentials/metadata.json` on bundle import.
3
3
  *
4
- * The credential metadata file lists the credentials known to the assistant
5
- * (service, field, policy, timestamps) and is used by the gateway's
6
- * `readServiceCredentials` to decide whether a service is "configured".
7
- * The VELLUM spec requires all four `vellum:*` fields (`platform_base_url`,
8
- * `assistant_api_key`, `platform_assistant_id`, `webhook_secret`) to be
9
- * present in metadata before the gateway will even look up their values in
10
- * CES.
4
+ * The leftover workspace credential metadata file may remain on disk after
5
+ * CES import. CES owns identity and policy records. Bundle import still
6
+ * merges `vellum:*` catalog rows so platform identity survives a
7
+ * local-to-platform teleport.
11
8
  *
12
9
  * On a local→platform teleport, the bundle carries the SOURCE's metadata
13
10
  * (no `vellum:*` entries, since the source is local), and a naive overwrite
@@ -1,75 +1,14 @@
1
1
  /**
2
- * The assistant emits a `<no_response/>` sentinel to signal that a turn should
3
- * produce no user-visible reply. Channel delivery must hide the sentinel
4
- * itself without hiding real content it happens to coexist with. Slack
5
- * surfaces that begin work on the first deliverable text (the thinking-status
6
- * indicator and native reply streaming) additionally must not act on a
7
- * partially-arrived sentinel: a slow stream can surface just `<` or `<no_resp`
8
- * before the rest lands, and starting on that leaks a stray message that
9
- * later resolves to empty.
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
- /** Matches every sentinel occurrence for stripping it out of mixed content. */
16
- export const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/gi;
17
-
18
- /**
19
- * Detection variant without the `g` flag: a `g`-flagged regex is stateful
20
- * under `.test()` (it resumes from `lastIndex`), so reusing
21
- * {@link NO_RESPONSE_INLINE_RE} for detection would alternate between
22
- * matches and misses across calls.
23
- */
24
- const NO_RESPONSE_MARKER_RE = new RegExp(NO_RESPONSE_INLINE_RE.source, "i");
25
-
26
- /** Whether `text` contains the sentinel anywhere. */
27
- export function containsNoResponseMarker(text: string): boolean {
28
- return NO_RESPONSE_MARKER_RE.test(text);
29
- }
30
-
31
- /** Removes every sentinel occurrence, trimming the leftover whitespace. */
32
- export function stripNoResponseMarkers(text: string): string {
33
- return text.replace(NO_RESPONSE_INLINE_RE, "").trim();
34
- }
35
-
36
- const NO_RESPONSE_SENTINEL_FORMS = [
37
- "<no_response/>",
38
- "<no_response />",
39
- "<no_response>",
40
- ] as const;
41
-
42
- /**
43
- * Whether `text` could still grow into the sentinel — i.e. it is a leading
44
- * substring of one of its forms. Holding on these keeps a slowly-streamed
45
- * `<no_response/>` from surfacing as visible partial content.
46
- */
47
- function isPotentialNoResponsePrefix(text: string): boolean {
48
- const lower = text.trim().toLowerCase();
49
- if (lower.length === 0) {
50
- return false;
51
- }
52
- return NO_RESPONSE_SENTINEL_FORMS.some((sentinel) =>
53
- sentinel.startsWith(lower),
54
- );
55
- }
56
-
57
- /**
58
- * Whether `text` carries user-visible content worth acting on now. Returns
59
- * `false` for empty text, the standalone sentinel, and any prefix that could
60
- * still complete into one; returns `true` once real content remains after
61
- * stripping inline sentinels.
62
- */
63
- export function hasDeliverableAssistantText(text: string): boolean {
64
- const trimmed = text.trim();
65
- if (trimmed.length === 0) {
66
- return false;
67
- }
68
- if (NO_RESPONSE_ONLY_RE.test(trimmed)) {
69
- return false;
70
- }
71
- if (isPotentialNoResponsePrefix(trimmed)) {
72
- return false;
73
- }
74
- return stripNoResponseMarkers(trimmed).length > 0;
75
- }
2
+ * Daemon door for the shared `<no_response/>` sentinel convention. The
3
+ * definitions live in `@vellumai/service-contracts/no-response` so the
4
+ * daemon, gateway, and clients parse identically; this module exists so
5
+ * daemon-internal imports keep one stable path.
6
+ */
7
+ export {
8
+ containsNoResponseMarker,
9
+ hasDeliverableAssistantText,
10
+ isNoResponseOnlyText,
11
+ isPotentialNoResponsePrefix,
12
+ NO_RESPONSE_INLINE_RE,
13
+ stripNoResponseMarkers,
14
+ } from "@vellumai/service-contracts/no-response";