@vellumai/assistant 0.11.8 → 0.11.9-staging.2

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 (326) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/Dockerfile +8 -48
  3. package/docker-entrypoint.sh +5 -1
  4. package/docker-kata-apt-env.sh +3 -0
  5. package/docker-kata-apt-shims.sh +127 -0
  6. package/docker-kata-apt-wrapper.sh +45 -0
  7. package/docker-kata-chroot-exec.sh +35 -0
  8. package/docker-kata-pip.sh +8 -2
  9. package/docs/architecture/memory.md +8 -4
  10. package/docs/guardian-request-flow.md +18 -14
  11. package/docs/trusted-contact-access.md +1 -7
  12. package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
  13. package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -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/platform-credential.ts +41 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  24. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
  25. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
  26. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
  27. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
  28. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
  29. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
  30. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
  31. package/node_modules/@vellumai/service-contracts/package.json +4 -1
  32. package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  33. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  34. package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  35. package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  36. package/openapi.yaml +193 -4
  37. package/package.json +2 -2
  38. package/src/__tests__/access-request-card-view.test.ts +6 -5
  39. package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
  40. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
  42. package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
  43. package/src/__tests__/attachments-store.test.ts +27 -0
  44. package/src/__tests__/attachments.test.ts +43 -0
  45. package/src/__tests__/canned-reply-release.test.ts +48 -5
  46. package/src/__tests__/channel-delivery-store.test.ts +129 -18
  47. package/src/__tests__/channel-reply-delivery.test.ts +553 -93
  48. package/src/__tests__/channel-retry-sweep.test.ts +199 -0
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
  50. package/src/__tests__/conversation-error.test.ts +15 -0
  51. package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
  52. package/src/__tests__/conversation-pairing.test.ts +141 -1
  53. package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
  54. package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
  56. package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
  57. package/src/__tests__/conversation-sync-tags.test.ts +2 -37
  58. package/src/__tests__/credential-health-service.test.ts +98 -10
  59. package/src/__tests__/delete-propagation.test.ts +469 -0
  60. package/src/__tests__/dm-persistence.test.ts +16 -0
  61. package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
  62. package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
  63. package/src/__tests__/gemini-provider.test.ts +46 -0
  64. package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
  65. package/src/__tests__/guardian-gateway-sim.ts +0 -23
  66. package/src/__tests__/guardian-question-mode.test.ts +108 -0
  67. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
  68. package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
  69. package/src/__tests__/host-proxy-interface.test.ts +10 -0
  70. package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
  71. package/src/__tests__/injector-v3-suppression.test.ts +43 -9
  72. package/src/__tests__/list-messages-page-latest.test.ts +127 -0
  73. package/src/__tests__/list-messages-system-card.test.ts +103 -0
  74. package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
  75. package/src/__tests__/notification-decision-fallback.test.ts +199 -77
  76. package/src/__tests__/notification-decision-strategy.test.ts +198 -213
  77. package/src/__tests__/notification-discord-adapter.test.ts +25 -0
  78. package/src/__tests__/notification-slack-adapter.test.ts +133 -0
  79. package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
  80. package/src/__tests__/openai-provider.test.ts +5 -5
  81. package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
  82. package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
  83. package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
  84. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
  85. package/src/__tests__/pricing.test.ts +11 -0
  86. package/src/__tests__/process-message-display-content.test.ts +20 -9
  87. package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
  88. package/src/__tests__/provider-meta-persistence.test.ts +67 -0
  89. package/src/__tests__/reaction-persistence.test.ts +22 -198
  90. package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
  91. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
  92. package/src/__tests__/skill-load-tool.test.ts +27 -0
  93. package/src/__tests__/skills.test.ts +34 -1
  94. package/src/__tests__/strip-memory-injections.test.ts +3 -4
  95. package/src/__tests__/terminal-tools.test.ts +9 -0
  96. package/src/__tests__/thread-backfill.test.ts +5 -3
  97. package/src/__tests__/unified-turn-context-location.test.ts +76 -0
  98. package/src/__tests__/voice-session-bridge.test.ts +18 -0
  99. package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
  100. package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
  101. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
  102. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
  103. package/src/agent/attachments.ts +13 -2
  104. package/src/agent/loop.ts +3 -19
  105. package/src/api/README.md +9 -5
  106. package/src/api/index.ts +9 -8
  107. package/src/api/package.json +1 -0
  108. package/src/api/responses/conversation-message.ts +8 -0
  109. package/src/api/responses/home.ts +7 -18
  110. package/src/api/surfaces.ts +114 -7
  111. package/src/approvals/AGENTS.md +1 -1
  112. package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
  113. package/src/channels/__tests__/types.test.ts +22 -1
  114. package/src/channels/gateway-guardian-requests.ts +0 -22
  115. package/src/channels/types.ts +8 -6
  116. package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
  117. package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
  118. package/src/cli/commands/channels/index.help.ts +19 -6
  119. package/src/cli/commands/channels/index.ts +6 -2
  120. package/src/cli/commands/db/__tests__/status.test.ts +22 -0
  121. package/src/cli/commands/db/index.help.ts +1 -1
  122. package/src/cli/commands/db/status.ts +172 -1
  123. package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
  124. package/src/cli/commands/platform/connect.ts +14 -5
  125. package/src/cli/commands/plugins.help.ts +6 -5
  126. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
  127. package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
  128. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
  129. package/src/cli/lib/bundled-marketplace.json +14 -0
  130. package/src/cli/lib/install-from-github.ts +9 -5
  131. package/src/cli/lib/install-from-platform.ts +12 -1
  132. package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
  133. package/src/config/assistant-initiated-threads-gate.ts +50 -0
  134. package/src/config/bundled-skills/acp/SKILL.md +10 -3
  135. package/src/config/bundled-skills/schedule/SKILL.md +25 -11
  136. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
  137. package/src/config/call-site-defaults.ts +4 -1
  138. package/src/config/feature-flag-registry.json +21 -4
  139. package/src/config/schemas/memory-v3.ts +4 -3
  140. package/src/context/strip-injections.ts +17 -56
  141. package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
  142. package/src/conversations/message-consolidation.ts +4 -3
  143. package/src/credential-health/credential-health-service.ts +130 -0
  144. package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
  145. package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
  146. package/src/daemon/conversation-error.ts +2 -0
  147. package/src/daemon/conversation-messaging.ts +195 -47
  148. package/src/daemon/conversation-process.ts +20 -5
  149. package/src/daemon/conversation-runtime-assembly.ts +48 -39
  150. package/src/daemon/conversation-store.ts +159 -11
  151. package/src/daemon/conversation-tool-setup.ts +9 -2
  152. package/src/daemon/conversation.ts +290 -19
  153. package/src/daemon/dictation-text-processing.ts +2 -7
  154. package/src/daemon/handlers/config-channels.ts +33 -3
  155. package/src/daemon/handlers/config-model.test.ts +1 -0
  156. package/src/daemon/handlers/shared.ts +9 -0
  157. package/src/daemon/port-oversized-content.test.ts +117 -0
  158. package/src/daemon/port-oversized-content.ts +110 -0
  159. package/src/daemon/process-message.ts +87 -16
  160. package/src/daemon/reaction-record.test.ts +23 -11
  161. package/src/daemon/reaction-record.ts +6 -9
  162. package/src/documents/document-store.ts +1 -5
  163. package/src/home/conversation-starter-validation.ts +1 -5
  164. package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
  165. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
  166. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
  167. package/src/live-voice/live-voice-photo.ts +544 -61
  168. package/src/live-voice/live-voice-session.ts +6 -2
  169. package/src/live-voice/protocol.ts +20 -1
  170. package/src/messaging/provider-message-metadata.ts +40 -1
  171. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
  172. package/src/messaging/providers/channel-transport.ts +28 -0
  173. package/src/messaging/providers/discord/send.ts +3 -2
  174. package/src/messaging/providers/slack/api.ts +11 -1
  175. package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
  176. package/src/messaging/providers/slack/message-metadata.ts +141 -1
  177. package/src/messaging/providers/slack/send.test.ts +58 -0
  178. package/src/messaging/providers/slack/send.ts +4 -4
  179. package/src/messaging/providers/slack/transport.ts +28 -2
  180. package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
  181. package/src/messaging/providers/telegram-bot/send.ts +93 -0
  182. package/src/messaging/providers/telegram-bot/transport.ts +71 -0
  183. package/src/messaging/reaction-envelopes.test.ts +73 -0
  184. package/src/messaging/reaction-envelopes.ts +33 -5
  185. package/src/messaging/read-provider-metadata.ts +4 -3
  186. package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
  187. package/src/monitoring/recovery/db.ts +35 -0
  188. package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
  189. package/src/monitoring/recovery/run-recovery.ts +6 -2
  190. package/src/monitoring/recovery/stale-processing.ts +3 -20
  191. package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
  192. package/src/notifications/AGENTS.md +2 -2
  193. package/src/notifications/README.md +2 -2
  194. package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
  195. package/src/notifications/__tests__/broadcaster.test.ts +35 -4
  196. package/src/notifications/__tests__/notification-utils.test.ts +31 -0
  197. package/src/notifications/access-request-copy.ts +126 -114
  198. package/src/notifications/adapters/discord.ts +9 -5
  199. package/src/notifications/adapters/shared.ts +17 -4
  200. package/src/notifications/adapters/slack.ts +14 -4
  201. package/src/notifications/adapters/telegram.ts +8 -4
  202. package/src/notifications/approval-card-data.ts +5 -2
  203. package/src/notifications/assistant-reply-producer.ts +7 -7
  204. package/src/notifications/broadcaster.ts +57 -25
  205. package/src/notifications/conversation-pairing.ts +68 -2
  206. package/src/notifications/copy-composer.ts +13 -32
  207. package/src/notifications/decision-engine.ts +56 -237
  208. package/src/notifications/guardian-delivery-recorder.ts +5 -9
  209. package/src/notifications/guardian-feed-projection.ts +0 -15
  210. package/src/notifications/guardian-question-mode.ts +147 -6
  211. package/src/notifications/notification-utils.ts +117 -1
  212. package/src/permissions/confirmation-guardian-request.ts +2 -2
  213. package/src/permissions/prompter.ts +1 -1
  214. package/src/persistence/conversation-crud.ts +93 -14
  215. package/src/persistence/conversation-queries.ts +144 -17
  216. package/src/persistence/conversation-types.ts +39 -5
  217. package/src/persistence/delivery-crud.ts +224 -28
  218. package/src/persistence/delivery-status.ts +21 -2
  219. package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
  220. package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
  221. package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
  222. package/src/persistence/schema/conversations.ts +71 -2
  223. package/src/persistence/schema-contract.test.ts +78 -0
  224. package/src/persistence/schema-contract.ts +96 -0
  225. package/src/persistence/steps.ts +4 -0
  226. package/src/platform/client.ts +55 -0
  227. package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
  228. package/src/plugins/defaults/injector-order.ts +2 -1
  229. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
  230. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
  231. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
  232. package/src/plugins/defaults/memory/injectors.ts +1 -1
  233. package/src/plugins/defaults/memory/memory-marker.ts +17 -7
  234. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
  235. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
  236. package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
  237. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
  238. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
  239. package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
  240. package/src/plugins/defaults/memory/v3/injector.ts +14 -15
  241. package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
  242. package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
  243. package/src/plugins/defaults/memory/v3/types.ts +15 -4
  244. package/src/plugins/defaults/turn-context/injectors.ts +3 -0
  245. package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
  246. package/src/plugins/mcp-servers.ts +14 -2
  247. package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
  248. package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
  249. package/src/providers/anthropic/client.ts +21 -24
  250. package/src/providers/call-site-routing.ts +5 -4
  251. package/src/providers/connection-resolution.ts +29 -1
  252. package/src/providers/content-block-size.test.ts +82 -0
  253. package/src/providers/content-block-size.ts +99 -0
  254. package/src/providers/file-block-text.test.ts +64 -0
  255. package/src/providers/file-block-text.ts +28 -0
  256. package/src/providers/gemini/client.ts +15 -9
  257. package/src/providers/inference/auth.ts +6 -6
  258. package/src/providers/media-resolve.ts +14 -0
  259. package/src/providers/model-catalog.ts +47 -34
  260. package/src/providers/openai/chat-completions-provider.ts +9 -25
  261. package/src/providers/openai/responses-provider.ts +11 -18
  262. package/src/providers/registry.ts +10 -7
  263. package/src/providers/routing-identity.ts +2 -1
  264. package/src/providers/types.ts +1 -4
  265. package/src/providers/vellum-model-routing.ts +3 -2
  266. package/src/runtime/AGENTS.md +40 -15
  267. package/src/runtime/approval-message-composer.ts +1 -6
  268. package/src/runtime/assistant-event-hub.ts +2 -39
  269. package/src/runtime/channel-approval-types.ts +1 -5
  270. package/src/runtime/channel-reply-delivery.ts +182 -115
  271. package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
  272. package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
  273. package/src/runtime/channel-retry-sweep.ts +102 -1
  274. package/src/runtime/finalize-event-delivery.ts +4 -4
  275. package/src/runtime/guardian-action-message-composer.ts +1 -4
  276. package/src/runtime/guardian-reply-router.ts +4 -75
  277. package/src/runtime/http-router.ts +1 -5
  278. package/src/runtime/question-request-guardian-bridge.ts +2 -3
  279. package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
  280. package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
  281. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
  282. package/src/runtime/routes/acp-routes.ts +1 -1
  283. package/src/runtime/routes/canned-reply-release.ts +19 -9
  284. package/src/runtime/routes/channel-route-shared.ts +0 -39
  285. package/src/runtime/routes/channel-verification-routes.ts +4 -1
  286. package/src/runtime/routes/conversation-list-routes.ts +44 -3
  287. package/src/runtime/routes/conversation-management-routes.ts +2 -1
  288. package/src/runtime/routes/conversation-routes.ts +138 -59
  289. package/src/runtime/routes/diagnostics-routes.ts +111 -60
  290. package/src/runtime/routes/guardian-action-routes.ts +10 -14
  291. package/src/runtime/routes/guardian-approval-interception.ts +0 -5
  292. package/src/runtime/routes/inbound-message-handler.ts +170 -48
  293. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
  294. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
  295. package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
  296. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
  297. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
  298. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
  299. package/src/runtime/routes/index.ts +2 -0
  300. package/src/runtime/routes/platform-routes.ts +99 -10
  301. package/src/runtime/routes/sight-frame-routes.ts +136 -0
  302. package/src/runtime/sync/resource-sync-events.ts +14 -37
  303. package/src/runtime/sync/sync-publisher.test.ts +5 -4
  304. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  305. package/src/tools/client-os.ts +11 -1
  306. package/src/tools/host-filesystem/edit.ts +2 -2
  307. package/src/tools/host-filesystem/read.ts +2 -2
  308. package/src/tools/host-filesystem/transfer.ts +2 -2
  309. package/src/tools/host-filesystem/write.ts +2 -2
  310. package/src/tools/host-terminal/host-shell.ts +2 -2
  311. package/src/tools/skills/load.ts +1 -1
  312. package/src/tools/terminal/safe-env.ts +4 -0
  313. package/src/tools/tool-input-schemas.ts +2 -0
  314. package/src/tools/tool-manifest.ts +2 -0
  315. package/src/tools/ui-surface/definitions.ts +4 -3
  316. package/src/tools/watch/watch-retro-report.ts +205 -0
  317. package/src/watch/__tests__/watch-retro.test.ts +268 -40
  318. package/src/watch/watch-retro.ts +190 -77
  319. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
  320. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
  321. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
  322. package/src/workspace/migrations/registry.ts +4 -0
  323. package/docker-kata-pip-chroot.sh +0 -22
  324. package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
  325. package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
  326. package/src/api/events/conversation-list-invalidated.ts +0 -38
@@ -1,6 +1,7 @@
1
1
  import type { Command } from "commander";
2
2
 
3
3
  import { cliIpcCall, exitFromIpcResult } from "../../../ipc/cli-client.js";
4
+ import type { PlatformConnectResponse } from "../../../runtime/routes/platform-routes.js";
4
5
  import { subcommand } from "../../lib/cli-command-help.js";
5
6
  import { log } from "../../logger.js";
6
7
  import { shouldOutputJson, writeOutput } from "../../output.js";
@@ -8,11 +9,10 @@ import { shouldOutputJson, writeOutput } from "../../output.js";
8
9
  export function registerPlatformConnectCommand(platform: Command): void {
9
10
  subcommand(platform, "connect").action(
10
11
  async (_opts: Record<string, unknown>, cmd: Command) => {
11
- const r = await cliIpcCall<{
12
- alreadyConnected?: boolean;
13
- baseUrl?: string;
14
- showPlatformLogin?: boolean;
15
- }>("platform_connect", {});
12
+ const r = await cliIpcCall<PlatformConnectResponse>(
13
+ "platform_connect",
14
+ {},
15
+ );
16
16
  if (!r.ok) {
17
17
  return exitFromIpcResult(
18
18
  { ok: false, error: r.error, statusCode: r.statusCode },
@@ -28,6 +28,15 @@ export function registerPlatformConnectCommand(platform: Command): void {
28
28
  `Already connected to platform at ${r.result.baseUrl}. ` +
29
29
  `Run 'assistant platform disconnect' first to reconnect.`,
30
30
  );
31
+ } else if (r.result?.credentialRejected) {
32
+ // No client shows a login screen for the daemon's signal, so this
33
+ // line is what tells the user what replaces the key. A plain sign-in
34
+ // stops at "already logged in" before the credential pass runs;
35
+ // --force runs it.
36
+ log.info(
37
+ "The stored platform credential was rejected by the platform. " +
38
+ "Run 'vellum login --force' to sign in again and replace it.",
39
+ );
31
40
  } else {
32
41
  log.info(
33
42
  "Showing the platform login screen on connected clients. " +
@@ -10,12 +10,13 @@ import {
10
10
 
11
11
  export const pluginsHelp: CliCommandHelp = {
12
12
  name: "plugins",
13
- description: "List, search, install, and manage plugins.",
13
+ description:
14
+ "Manage external plugins, which bundle apps, channels, MCPs, skills and more from other Vellum users",
14
15
  helpText: `
15
- Plugins are superpowers: installable extensions that add skills, tools, integrations,
16
- and so much more from the Vellum Community. When the user asks to set up,
17
- install, connect, or integrate a product, service, or app, run
18
- 'assistant plugins search <name>' first before searching the web.`,
16
+ Plugins are superpowers: installable extensions that add apps, channels, MCPs,
17
+ skills, tools, integrations, and so much more from other Vellum users. When the
18
+ user asks to set up, install, connect, or integrate a product, service, or app,
19
+ run 'assistant plugins search <name>' first before searching the web.`,
19
20
  subcommands: [
20
21
  {
21
22
  name: "install",
@@ -201,14 +201,6 @@ const unusedGitRunner: GitRunner = async (args) => {
201
201
  throw new Error(`git should not run for this install: ${args.join(" ")}`);
202
202
  };
203
203
 
204
- /**
205
- * Read the real, committed caveman adapter stub from the repo so the
206
- * integration test exercises the adapter that ships rather than a fixture
207
- * copy that could drift from it. Returns every stub file keyed by its
208
- * Contents-API path (`plugins/caveman/<rel>`) so the fetch fake
209
- * serves the whole stub — package.json, the adapter, and its templates — to
210
- * the real postinstall runner. Resolves the stub relative to this test file.
211
- */
212
204
  function readRealCavemanStub(): Record<string, string> {
213
205
  const repoRel = "plugins/caveman";
214
206
  const stubDir = join(import.meta.dir, "../../../../../", repoRel);
@@ -177,6 +177,7 @@ describe("installPluginFromPlatform — success", () => {
177
177
  );
178
178
  expect(existsSync(join(target, "README.md"))).toBe(true);
179
179
  expect(existsSync(join(target, "skills", "read", "SKILL.md"))).toBe(true);
180
+ expect(existsSync(join(target, "package.json"))).toBe(true);
180
181
 
181
182
  // Provenance records the pinned commit and the verified ETag.
182
183
  const meta = readInstallMeta(target);
@@ -186,6 +187,77 @@ describe("installPluginFromPlatform — success", () => {
186
187
  expect(meta?.etag?.startsWith('"sha256:')).toBe(true);
187
188
  });
188
189
 
190
+ test("synthesizes a minimal package.json when the tarball ships none", async () => {
191
+ const fetchFn = makeInstallFetch({
192
+ entries: [
193
+ {
194
+ name: "skills/keyword-research/SKILL.md",
195
+ content:
196
+ "---\nname: keyword-research\ndescription: Discover keyword opportunities.\n---\n",
197
+ },
198
+ {
199
+ name: "mcp.json",
200
+ content: JSON.stringify({
201
+ mcpServers: { openseo: { url: "https://app.openseo.so/mcp" } },
202
+ }),
203
+ },
204
+ ],
205
+ ref: "c".repeat(40),
206
+ repo: "every-app/open-seo",
207
+ sourcePath: "plugins/openseo",
208
+ });
209
+
210
+ const result = await installPluginFromPlatform(
211
+ { name: "openseo" },
212
+ {
213
+ fetch: fetchFn,
214
+ platformBaseUrl: PLATFORM,
215
+ workspacePluginsDir: pluginsDir,
216
+ },
217
+ );
218
+
219
+ const target = join(pluginsDir, "openseo");
220
+ expect(result.fileCount).toBe(2);
221
+ expect(
222
+ existsSync(join(target, "skills", "keyword-research", "SKILL.md")),
223
+ ).toBe(true);
224
+ const pkg = JSON.parse(readFileSync(join(target, "package.json"), "utf-8"));
225
+ expect(pkg.name).toBe("openseo");
226
+ expect(pkg.version).toBe("0.0.0");
227
+ expect(pkg.peerDependencies["@vellumai/plugin-api"]).toBeDefined();
228
+ });
229
+
230
+ test("leaves an upstream package.json in place", async () => {
231
+ const fetchFn = makeInstallFetch({
232
+ entries: [
233
+ {
234
+ name: "package.json",
235
+ content: JSON.stringify({
236
+ name: "reading-pal",
237
+ version: "1.2.3",
238
+ peerDependencies: { "@vellumai/plugin-api": ">=0.8.0" },
239
+ }),
240
+ },
241
+ { name: "README.md", content: "# reading pal" },
242
+ ],
243
+ });
244
+
245
+ await installPluginFromPlatform(
246
+ { name: "reading-pal" },
247
+ {
248
+ fetch: fetchFn,
249
+ platformBaseUrl: PLATFORM,
250
+ workspacePluginsDir: pluginsDir,
251
+ },
252
+ );
253
+
254
+ const pkg = JSON.parse(
255
+ readFileSync(join(pluginsDir, "reading-pal", "package.json"), "utf-8"),
256
+ );
257
+ expect(pkg.name).toBe("reading-pal");
258
+ expect(pkg.version).toBe("1.2.3");
259
+ });
260
+
189
261
  test("sends the API key as an Api-Key Authorization header when present", async () => {
190
262
  const captured: { auth: string | null } = { auth: null };
191
263
  const fetchFn = makeInstallFetch({
@@ -5,6 +5,8 @@
5
5
  * time — no network, no filesystem path resolution.
6
6
  */
7
7
 
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
8
10
  import { describe, expect, test } from "bun:test";
9
11
 
10
12
  import bundledManifest from "../bundled-marketplace.json" with { type: "json" };
@@ -12,6 +14,15 @@ import {
12
14
  buildBundledPluginCatalog,
13
15
  readBundledPluginCatalog,
14
16
  } from "../plugin-catalog-local.js";
17
+ import { marketplaceManifestSchema } from "../plugin-marketplace.js";
18
+ import { marketplaceMatch } from "../search-plugins.js";
19
+
20
+ const canonicalManifest = JSON.parse(
21
+ readFileSync(
22
+ join(import.meta.dir, "../../../../../plugins/marketplace.json"),
23
+ "utf8",
24
+ ),
25
+ ) as unknown;
15
26
 
16
27
  describe("buildBundledPluginCatalog", () => {
17
28
  test("yields one match per unique manifest name, sorted alphabetically", () => {
@@ -53,6 +64,28 @@ describe("buildBundledPluginCatalog", () => {
53
64
  });
54
65
  });
55
66
 
67
+ test("projects the OpenSEO subdirectory pin from the canonical catalog", () => {
68
+ const parsed = marketplaceManifestSchema.parse(canonicalManifest);
69
+ const entry = parsed.plugins.find((plugin) => plugin.name === "openseo");
70
+ expect(entry).toBeDefined();
71
+ expect(marketplaceMatch(entry!)).toEqual({
72
+ name: "openseo",
73
+ path: "github:every-app/open-seo/plugins/openseo@c469a48ae90ab58413b198fe3d1ac1aa90a9b070",
74
+ description:
75
+ "Keyword research, competitor analysis, site audits, backlinks, and rank tracking with real SEO data, plus guided workflow skills and the hosted OpenSEO MCP server.",
76
+ category: "marketing",
77
+ homepage: "https://openseo.so",
78
+ license: "MIT",
79
+ icon: "🔎",
80
+ source: {
81
+ kind: "github",
82
+ repo: "every-app/open-seo",
83
+ path: "plugins/openseo",
84
+ ref: "c469a48ae90ab58413b198fe3d1ac1aa90a9b070",
85
+ },
86
+ });
87
+ });
88
+
56
89
  test("throws on a malformed manifest", () => {
57
90
  expect(() => buildBundledPluginCatalog({ plugins: [{}] })).toThrow();
58
91
  });
@@ -318,6 +318,20 @@
318
318
  "license": "MIT",
319
319
  "icon": "💳"
320
320
  },
321
+ {
322
+ "name": "openseo",
323
+ "source": {
324
+ "source": "github",
325
+ "repo": "every-app/open-seo",
326
+ "path": "plugins/openseo",
327
+ "ref": "c469a48ae90ab58413b198fe3d1ac1aa90a9b070"
328
+ },
329
+ "description": "Keyword research, competitor analysis, site audits, backlinks, and rank tracking with real SEO data, plus guided workflow skills and the hosted OpenSEO MCP server.",
330
+ "category": "marketing",
331
+ "homepage": "https://openseo.so",
332
+ "license": "MIT",
333
+ "icon": "🔎"
334
+ },
321
335
  {
322
336
  "name": "originkit",
323
337
  "source": {
@@ -1134,12 +1134,16 @@ function normalizeInstalledManifest(
1134
1134
  * (`buildPluginFromDir`) hard-requires a `package.json` validated against
1135
1135
  * `PluginPackageJsonSchema` and silently skips the plugin when it's missing.
1136
1136
  *
1137
- * The synthesized manifest carries the install name and the default
1138
- * `@vellumai/plugin-api` peer dependency range. No foreign-ecosystem manifest
1139
- * data is read — the install name is the only identity we trust for an
1140
- * untrusted direct install.
1137
+ * Used by both the GitHub clone path and the platform tarball path. The
1138
+ * synthesized manifest carries the install name and the default
1139
+ * `@vellumai/plugin-api` peer dependency range. No foreign-ecosystem
1140
+ * manifest data is read: the install name is the only identity we trust
1141
+ * when upstream ships no Vellum package.json.
1141
1142
  */
1142
- function synthesizeMinimalPackageJson(name: string, stagingDir: string): void {
1143
+ export function synthesizeMinimalPackageJson(
1144
+ name: string,
1145
+ stagingDir: string,
1146
+ ): void {
1143
1147
  const manifestPath = join(stagingDir, "package.json");
1144
1148
 
1145
1149
  const manifest: PackageManifest = {
@@ -8,7 +8,9 @@
8
8
  * and extracts it into the plugin's install directory — it does NOT clone the
9
9
  * plugin from GitHub itself, and it does NOT emit `plugin_installed` telemetry
10
10
  * (hitting this endpoint *is* the recorded install; the server rejects that
11
- * event type on the usual ingest path).
11
+ * event type on the usual ingest path). A tarball that ships no
12
+ * `package.json` gets the same synthesized manifest the GitHub clone path
13
+ * writes, so the loader still registers the plugin's skills.
12
14
  *
13
15
  * GET {PLATFORM_BASE_URL}/v1/plugins/{name}/install/
14
16
  *
@@ -42,6 +44,7 @@ import {
42
44
  PluginNotFoundError,
43
45
  PluginSourceUnavailableError,
44
46
  sanitizePluginName,
47
+ synthesizeMinimalPackageJson,
45
48
  } from "./install-from-github.js";
46
49
 
47
50
  /**
@@ -198,6 +201,14 @@ export async function installPluginFromPlatform(
198
201
  throw new PluginNotFoundError(name, meta.ref ?? "", meta.repo ?? name);
199
202
  }
200
203
 
204
+ // Same default as the GitHub clone path: a marketplace tarball that
205
+ // ships skills/mcp.json but no Vellum package.json still has to load.
206
+ // The loader skips a directory with no manifest, so synthesize one
207
+ // before the fingerprint and swap.
208
+ if (!existsSync(join(stagingDir, "package.json"))) {
209
+ synthesizeMinimalPackageJson(name, stagingDir);
210
+ }
211
+
201
212
  await confirmStagedOrAbort(name, stagingDir, deps.confirmStaged);
202
213
 
203
214
  // Record provenance for future update/version UX. The endpoint re-roots the
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The gate reads a string-valued flag that does not always arrive as a
3
+ * string.
4
+ *
5
+ * The registry and the gateway's persisted store carry the declared `"on"` /
6
+ * `"off"` strings. The gateway's env-override path does not: its parser
7
+ * (`feature-flag-env-overrides.ts`) treats `on` as a truthy *word* and
8
+ * coerces it to boolean `true`, then applies env overrides last — so
9
+ * `VELLUM_FLAG_ASSISTANT_INITIATED_THREADS=on` reaches the daemon as `true`.
10
+ *
11
+ * A strict `=== "on"` therefore read the documented env kill-switch as OFF,
12
+ * and did it silently: the section just never rendered. That shipped in the
13
+ * first cut of this gate and cost an afternoon of QA, which is why both
14
+ * shapes are pinned here rather than only the one the registry emits.
15
+ */
16
+
17
+ import { afterEach, describe, expect, mock, test } from "bun:test";
18
+
19
+ let flagValue: boolean | string = "off";
20
+
21
+ mock.module("../assistant-feature-flags.js", () => ({
22
+ getAssistantFeatureFlagValue: () => flagValue,
23
+ }));
24
+
25
+ const { isAssistantInitiatedThreadsEnabled } = await import(
26
+ "../assistant-initiated-threads-gate.js"
27
+ );
28
+
29
+ afterEach(() => {
30
+ flagValue = "off";
31
+ });
32
+
33
+ describe("isAssistantInitiatedThreadsEnabled", () => {
34
+ test('enabled by the declared string "on" (registry / persisted store)', () => {
35
+ flagValue = "on";
36
+ expect(isAssistantInitiatedThreadsEnabled()).toBe(true);
37
+ });
38
+
39
+ test("enabled by boolean true (the gateway's env-override coercion)", () => {
40
+ // The regression. `VELLUM_FLAG_ASSISTANT_INITIATED_THREADS=on` arrives
41
+ // here as `true`, never as "on".
42
+ flagValue = true;
43
+ expect(isAssistantInitiatedThreadsEnabled()).toBe(true);
44
+ });
45
+
46
+ test('disabled by the declared string "off"', () => {
47
+ flagValue = "off";
48
+ expect(isAssistantInitiatedThreadsEnabled()).toBe(false);
49
+ });
50
+
51
+ test("disabled by boolean false (env `=off` coercion)", () => {
52
+ flagValue = false;
53
+ expect(isAssistantInitiatedThreadsEnabled()).toBe(false);
54
+ });
55
+
56
+ test("fails closed on an unrecognized arm", () => {
57
+ // A future A/B value this gate has no opinion about must not read as on.
58
+ flagValue = "variant-b";
59
+ expect(isAssistantInitiatedThreadsEnabled()).toBe(false);
60
+ });
61
+ });
@@ -0,0 +1,50 @@
1
+ import { getAssistantFeatureFlagValue } from "./assistant-feature-flags.js";
2
+ import type { AssistantConfig } from "./schema.js";
3
+
4
+ const ASSISTANT_INITIATED_THREADS_FLAG_KEY =
5
+ "assistant-initiated-threads" as const;
6
+
7
+ /**
8
+ * Whether the conversations the assistant started on its own are split into
9
+ * their own sidebar section instead of sitting in Chats.
10
+ *
11
+ * Read through {@link getAssistantFeatureFlagValue} rather than
12
+ * `isAssistantFeatureFlagEnabled`, because the flag is string-valued: that
13
+ * helper coerces with `!!`, and `!!"off"` is `true`, so the boolean form would
14
+ * report the off arm as on.
15
+ *
16
+ * A read-side gate only. The rows exist either way — every notification
17
+ * delivery has always materialized one (`notifications/conversation-pairing.ts`)
18
+ * — so turning the flag off puts them back in Chats and takes the `assistant`
19
+ * row out of the section index, with nothing to migrate in either direction.
20
+ *
21
+ * This is the *only* gate on the feature, which is why the flag is
22
+ * assistant-scope rather than `both`. Clients render the section on the
23
+ * presence of the index row, because that row is what says the daemon has
24
+ * withheld those conversations from Chats. A second, client-side gate could
25
+ * only ever hide a section whose rows have already been withheld — losing
26
+ * those threads rather than reverting the feature — so there deliberately
27
+ * isn't one.
28
+ */
29
+ export function isAssistantInitiatedThreadsEnabled(
30
+ config?: AssistantConfig,
31
+ ): boolean {
32
+ const value = getAssistantFeatureFlagValue(
33
+ ASSISTANT_INITIATED_THREADS_FLAG_KEY,
34
+ config,
35
+ );
36
+ /* Both shapes are the same answer, and the on arm arrives as either one
37
+ depending on which layer resolved it.
38
+
39
+ The registry and the gateway's persisted store carry the declared string
40
+ (`"on"`). The gateway's env-override path does not: its parser treats
41
+ `on` as a truthy word (`TRUTHY = {"true","1","yes","on"}` in
42
+ `feature-flag-env-overrides.ts`) and coerces it to boolean `true`, then
43
+ applies env last — so `VELLUM_FLAG_ASSISTANT_INITIATED_THREADS=on`
44
+ reaches the daemon as `true`, not `"on"`.
45
+
46
+ A strict `=== "on"` therefore reads the env kill-switch as OFF, which is
47
+ the exact opposite of what an operator setting it intends, and it fails
48
+ silently: the section simply does not render. Accept both. */
49
+ return value === true || value === "on";
50
+ }
@@ -8,12 +8,11 @@ metadata:
8
8
  display-name: "ACP"
9
9
  category: "development"
10
10
  activation-hints:
11
- - "User wants to set up, install, configure, authenticate, or connect Claude Code or Codex"
12
- - "User asks to use Claude Code or Codex, or delegate a coding task to an ACP agent"
11
+ - "User names Claude Code or Codex, or wants to set up, install, configure, authenticate, connect, use, or delegate work to them"
13
12
  - "User wants an agent to work autonomously and report back later"
14
13
  - "User mentions ACP, claude-agent-acp, or codex-acp"
15
14
  avoid-when:
16
- - "The task is small enough to do inline"
15
+ - "The task is small enough to do inline and the user did not name Claude Code or Codex"
17
16
  ---
18
17
 
19
18
  ACP agent orchestration - spawn external coding agents (Claude Code, Codex) to work on tasks via the Agent Client Protocol. Each agent runs as its own subprocess speaking ACP over stdio and streams results back into the conversation.
@@ -24,6 +23,14 @@ Use `acp_spawn` to delegate a coding task to an external agent. The agent runs a
24
23
 
25
24
  Users can refer to agents by natural names: "claude code", "codex cli", and "openai codex" all resolve to the canonical `claude` and `codex` ids (unless the user's config defines an agent literally keyed by that name, which always wins).
26
25
 
26
+ ## When the user names Claude Code or Codex
27
+
28
+ If they name Claude Code or Codex without asking you to run it here (for example they say that tool will do the work), offer once, in one short sentence, that you can connect and run it in this conversation. Then continue with whatever they were doing.
29
+
30
+ - Do not spawn unless they accept.
31
+ - Skip if you already offered this conversation, they declined, or they are already connected.
32
+ - This is not the missing-token card path. Do not invent setup steps.
33
+
27
34
  ## First-time setup
28
35
 
29
36
  ACP is always available - default profiles for `claude` and `codex` ship out-of-box, so no config edit is needed to start. First-time setup is just making the adapter binary available, then spawning:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: schedule
3
- description: Recurring and one-shot scheduling - cron, RRULE, or single fire-at time
3
+ description: Reminders, automations, and recurring page, dashboard, or status monitoring (cron, RRULE, or fire-at)
4
4
  compatibility: "Designed for Vellum personal assistants"
5
5
  metadata:
6
6
  emoji: "📅"
@@ -8,17 +8,28 @@ metadata:
8
8
  display-name: "Schedule"
9
9
  category: "productivity"
10
10
  activation-hints:
11
- - "User wants to set a reminder for a future time (e.g. 'remind me at 9am tomorrow', 'remind me to take meds at 8pm')"
12
- - "User wants to schedule a recurring task or automation (e.g. 'every weekday at 9am', 'every Monday at noon')"
13
- - "User wants to schedule a one-time future action the assistant should run autonomously (e.g. 'at 5pm check my email and summarize it')"
14
- - "User wants to list, update, cancel, or inspect existing scheduled tasks or reminders"
11
+ - "User wants a reminder"
12
+ - "User wants a recurring task or nightly check"
13
+ - "User wants to monitor a page, dashboard, or status and get alerts"
14
+ - "User wants a one-time future action"
15
+ - "User wants to manage existing schedules"
15
16
  avoid-when:
16
- - "User wants to add something to their task list or queue — use task-list tools instead"
17
- - "User wants to act immediately or run a quick command that completes within the conversation — schedule is only for deferred or recurring execution"
17
+ - "User wants a task-list item"
18
+ - "User wants a one-off that finishes now. Recurring-job setup still uses this skill"
18
19
  ---
19
20
 
20
21
  Manage scheduled automations. Schedules can be **recurring** (cron or RRULE expression) or **one-shot** (a single `fire_at` timestamp). Schedules support four modes: **execute** (run a message through the assistant), **notify** (send a notification to the user), **script** (run a shell command directly without LLM involvement), and **workflow** (run a saved multi-agent workflow by name).
21
22
 
23
+ ## Recurring Monitoring
24
+
25
+ When the user wants something checked on a cadence (a status page, dashboard, site, or similar) and to be told if it is wrong, late, or missing: create a schedule. Do not author a standalone script they have to download, run, or maintain.
26
+
27
+ - Recurrence, cutoff times, escalation, and notifications are schedule and notification primitives. Do not reimplement them in a workspace file the user has to run.
28
+ - Prefer **execute** mode: the scheduled message browses or fetches the source, applies the user's rules, and notifies on exceptions. Use **script** mode only for a cheap deterministic check against a live source the assistant can already reach (curl an API, read a file in the workspace). A script-mode job is still a schedule, not a file you hand the user.
29
+ - Looking at a page, pasting HTML, or gathering a roster is setup for the schedule, not a reason to skip creating one.
30
+ - If browsing cannot reach the page, still create the schedule. Offer the desktop app (https://www.vellum.ai/download) or the Chrome extension (https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne) so a logged-in browser session can run it. Do not replace the schedule with a parser against a pasted export, and do not assign comparison-run homework before anything is scheduled.
31
+ - Watchers cover Gmail, Google Calendar, GitHub, Linear, and Outlook event polling. An arbitrary web page or status dashboard is this skill, not the watcher skill.
32
+
22
33
  ## Schedule Syntax
23
34
 
24
35
  ### Cron
@@ -119,6 +130,8 @@ assistant conversations wake "$id" --hint "Summarize the new items" --external-c
119
130
 
120
131
  **Secrets.** For an OAuth-connected provider (google, slack, notion, …), call its API with `assistant oauth request --provider <p> <url>` — the assistant injects the token, and the script never sees it. For raw secrets with no OAuth provider (PATs, API keys), collect at install time with `assistant credentials prompt --service <s> --field <f> --label "<label>"` (secure input, never printed to chat) and read at runtime with `assistant credentials reveal --service <s> --field <f>`.
121
132
 
133
+ When a scheduled or background run is blocked on a missing or stale credential, tell the user in the notification that the credential needs refreshing and ask them to reply in the thread so you can open the secure prompt. Never put a CLI command in the notification or the tick summary. The user cannot run those from their terminal.
134
+
122
135
  ## Inference Profile
123
136
 
124
137
  Every schedule carries a pinned `inference_profile` (a key from `llm.profiles`), so a recurring task keeps running on the model (and the price) it was created under even when the user later changes their default profile. Creating a schedule without `inference_profile` pins it to the user's current default; pass one explicitly when a task should run on a specific model, e.g. a cost-optimized profile for a high-frequency digest. Passing `inference_profile: null` on update re-pins the schedule to the user's current default rather than unpinning it. The pinned profile is shown on the schedule's details page in settings.
@@ -213,14 +226,15 @@ When `schedule_create` returns, it includes an integration status summary. Cross
213
226
 
214
227
  - If the task involves **email** (reading, sending, OTP verification): an email integration must be connected (check the "email" category)
215
228
  - If the task involves **making calls**: Twilio must be connected
216
- - If the task involves **web browsing or form-filling**: browser automation must be available (check client type)
229
+ - If the task involves **web browsing or form-filling**: browser automation must be available (check client type). A timeout, auth wall, or missing logged-in browser is a setup gap, not a reason to skip the schedule or to write a standalone script.
217
230
  - If the task involves a **multi-step workflow** (e.g., book appointment → read confirmation email), trace the full dependency chain
218
231
 
219
232
  If any required capability is missing:
220
233
 
221
- 1. **Do NOT tell the user the schedule is ready** - instead, explain what's missing and why the schedule won't work yet
222
- 2. Offer to help set up the missing integration first
223
- 3. The schedule is still created (so timing is preserved), but make it clear it won't execute successfully until dependencies are resolved
234
+ 1. **Still create the schedule** so timing is preserved. Do not tell the user it is ready to run.
235
+ 2. Explain what is missing and why the next fire will fail until that is fixed.
236
+ 3. Offer to set up the missing integration first. For a page the assistant cannot reach, offer the desktop app (https://www.vellum.ai/download) or the Chrome extension (https://chromewebstore.google.com/detail/vellum-assistant-browser/hphbdmpffeigpcdjkckleobjmhhokpne) before asking for a screenshot or pasted export.
237
+ 4. Do not replace the schedule with a workspace script the user has to run themselves.
224
238
 
225
239
  ## Delivering Results
226
240
 
@@ -56,4 +56,6 @@ fi
56
56
 
57
57
  ## Tips
58
58
 
59
- Both patterns keep scheduled runs fast and cheap — the script exits immediately when nothing interesting happens, and only reaches for the assistant or notification system when there's something to act on. Use `quiet: true` on the schedule to suppress the per-run completion noise.
59
+ Both patterns keep scheduled runs fast and cheap. The script exits immediately when nothing interesting happens, and only reaches for the assistant or notification system when there's something to act on. Use `quiet: true` on the schedule to suppress the per-run completion noise.
60
+
61
+ A script-mode schedule is still a schedule the user can see, pause, and edit. Do not hand them a standalone file to run themselves.
@@ -123,8 +123,11 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
123
123
  effort: "low",
124
124
  thinking: { enabled: false },
125
125
  },
126
+ // The dictation cleanup pass, and nothing else runs on this site. The
127
+ // caller has already stopped speaking and has nothing on screen until this
128
+ // answers, so it is priced as a latency call.
126
129
  interactionClassifier: {
127
- profile: "cost-optimized",
130
+ profile: "latency-optimized",
128
131
  effort: "low",
129
132
  thinking: { enabled: false },
130
133
  },
@@ -36,6 +36,15 @@
36
36
  "defaultEnabled": "off",
37
37
  "values": ["off", "on"]
38
38
  },
39
+ {
40
+ "id": "assistant-initiated-threads",
41
+ "scope": "assistant",
42
+ "key": "assistant-initiated-threads",
43
+ "label": "Assistant-Initiated Threads",
44
+ "description": "Gates the sidebar section collecting the conversations the assistant started on its own: rows stamped `source='assistant_initiated'` at creation by producers that opt a thread in (heartbeat realizations worth the user's time; nothing stamps it yet, so the section starts empty and holds only new threads by construction). Transactional notification trails keep `source='notification'` and stay in Chats and the bell. When on, the daemon emits an `assistant` row from the section index and withholds member conversations from the Chats bucket; when off, they stay in Chats and no `assistant` row is emitted, which is the shipped behavior. Deliberately daemon-scope only: the index row is self-describing, so clients render the section on its presence rather than on a flag of their own. A second client-side gate could only hide a section whose rows the daemon has already withheld from Chats, which would lose those threads instead of reverting the feature. String-valued so future A/B arms can be added as new values.",
45
+ "defaultEnabled": "off",
46
+ "values": ["off", "on"]
47
+ },
39
48
  {
40
49
  "id": "experiment-activation-flow-2026-06-03",
41
50
  "scope": "both",
@@ -189,6 +198,14 @@
189
198
  "description": "Show a live HUD in the top-right of the conversation with scroll geometry, velocity, update rate, and anchor-preserver activity. Developer diagnostic for investigating scroll jank.",
190
199
  "defaultEnabled": false
191
200
  },
201
+ {
202
+ "id": "camera-gate-debug-hud",
203
+ "scope": "client",
204
+ "key": "camera-gate-debug-hud",
205
+ "label": "Camera Gate Debug HUD",
206
+ "description": "Unlock the camera frame gate's tuning readout: the switch in the camera's view options that turns it on, and the live panel over the composer's sight tile and the voice room's viewfinder showing motion, novelty and detail against their thresholds, the check that decided each frame, and sliders that move those thresholds while the camera runs. Staff sessions get it without the flag; the flag is for local gateway sessions, which carry no platform identity and are where the gate is tuned.",
207
+ "defaultEnabled": false
208
+ },
192
209
  {
193
210
  "id": "compaction-playground",
194
211
  "scope": "assistant",
@@ -391,11 +408,11 @@
391
408
  "values": ["glm-5p3-flash", "glm-5p3", "glm-5p2"]
392
409
  },
393
410
  {
394
- "id": "ios-avatar-app-icon",
411
+ "id": "android-avatar-app-icon",
395
412
  "scope": "client",
396
- "key": "ios-avatar-app-icon",
397
- "label": "iOS Avatar App Icon",
398
- "description": "Shows the App icon picker in Settings -> General, where any user chooses an eyes-on-color home screen icon for iOS, seeds the picker from their assistant's character avatar, or resets to the default. Every change is a press, and iOS confirms each one with an alert of its own. Off hides the picker and leaves the default icon in place.",
413
+ "key": "android-avatar-app-icon",
414
+ "label": "Android Avatar App Icon",
415
+ "description": "Shows the App icon picker in Settings -> General on Android, where any user chooses an eyes-on-color launcher icon, seeds the picker from their assistant's character avatar, or resets to the default. Android applies the change when the app goes to the background, and switching icons can reset a pinned home-screen shortcut. Off hides the picker and leaves the default icon in place.",
399
416
  "defaultEnabled": false
400
417
  },
401
418
  {
@@ -144,11 +144,12 @@ export const MemoryV3LearnedEdgesSchema = z
144
144
  );
145
145
 
146
146
  /**
147
- * Ephemeral section-spotlight tuning: how many of the current turn's selected
147
+ * Per-turn section-spotlight tuning: how many of the current turn's selected
148
148
  * finder hits render their matched section into the `<memory_spotlight>`
149
149
  * block, and how many previous turns' spotlight entries are carried along
150
- * before they age out. The block is strip-and-replaced every turn, so its
151
- * size is bounded by `n × (windowTurns + 1)` entries.
150
+ * before they age out. Each turn keeps the block it was sent with. A new
151
+ * spotlight is added only on the new tail. Size is bounded by
152
+ * `n × (windowTurns + 1)` entries.
152
153
  */
153
154
  export const MemoryV3SpotlightSchema = z
154
155
  .object({