@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
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Canonical vocabulary for a reaction's emoji, shared by every service that
3
+ * handles one: the gateway that normalizes it, the daemon that stores and
4
+ * projects it, and the web that renders it.
5
+ *
6
+ * The kind is said by the channel rather than inferred from how the emoji
7
+ * is spelled. Modelled on Zulip's `reaction_type`, the one surveyed system
8
+ * that separates the namespace from the name.
9
+ *
10
+ * `shortcode` is a name in a channel's own namespace whose kind the channel
11
+ * does not disclose: Slack sends `+1` for the standard emoji and `blob_wave`
12
+ * for a workspace upload with nothing to tell them apart, and only the
13
+ * workspace token can resolve the second. It is a distinct kind from
14
+ * `unicode`, not a stand-in for an unknown one.
15
+ */
16
+ import { z } from "zod";
17
+
18
+ export const REACTION_EMOJI_KINDS = ["unicode", "shortcode", "custom"] as const;
19
+ export type ReactionEmojiKind = (typeof REACTION_EMOJI_KINDS)[number];
20
+
21
+ /**
22
+ * The typed emoji fields every schema that carries a reaction spreads in,
23
+ * so the wire contract, the stored envelopes, and the response projection
24
+ * describe one shape. Optional throughout: a persisted row or a replayed
25
+ * payload may carry only the spelling.
26
+ */
27
+ export const ReactionEmojiFieldsSchema = z.object({
28
+ /** Which namespace the emoji was drawn from. */
29
+ emojiKind: z.enum(REACTION_EMOJI_KINDS).optional(),
30
+ /**
31
+ * The emoji's name in that namespace: the character itself for `unicode`,
32
+ * the bare name for `shortcode` and `custom`. Never a mention form.
33
+ */
34
+ emojiName: z.string().optional(),
35
+ /** The channel's id for a `custom` emoji, absent for every other kind. */
36
+ emojiId: z.string().optional(),
37
+ /** Whether a `custom` emoji animates. Absent for every other kind. */
38
+ emojiAnimated: z.boolean().optional(),
39
+ });
40
+ export type ReactionEmojiFields = z.infer<typeof ReactionEmojiFieldsSchema>;
41
+
42
+ /**
43
+ * The typed emoji fields a source actually carries, with undefined ones
44
+ * omitted: an absent key and a present-but-undefined one serialize alike, but the
45
+ * stored envelope and the response should carry only what was declared. Every writer of a reaction shape (the wire
46
+ * payload, both stored envelopes, the response projection) copies the
47
+ * fields through this rather than restating the four-way pick.
48
+ */
49
+ export function pickReactionEmojiFields(
50
+ source: ReactionEmojiFields,
51
+ ): ReactionEmojiFields {
52
+ return {
53
+ ...(source.emojiKind !== undefined ? { emojiKind: source.emojiKind } : {}),
54
+ ...(source.emojiName !== undefined ? { emojiName: source.emojiName } : {}),
55
+ ...(source.emojiId !== undefined ? { emojiId: source.emojiId } : {}),
56
+ ...(source.emojiAnimated !== undefined
57
+ ? { emojiAnimated: source.emojiAnimated }
58
+ : {}),
59
+ };
60
+ }
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.11.8
6
+ version: 0.11.9
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -4076,6 +4076,59 @@ paths:
4076
4076
  responses:
4077
4077
  "200":
4078
4078
  description: Successful response
4079
+ content:
4080
+ application/json:
4081
+ schema:
4082
+ type: object
4083
+ properties:
4084
+ success:
4085
+ type: boolean
4086
+ bound:
4087
+ type: boolean
4088
+ channel:
4089
+ type: string
4090
+ enum:
4091
+ - telegram
4092
+ - phone
4093
+ - vellum
4094
+ - whatsapp
4095
+ - slack
4096
+ - email
4097
+ - platform
4098
+ - a2a
4099
+ - discord
4100
+ - plugin
4101
+ assistantId:
4102
+ type: string
4103
+ hasPendingChallenge:
4104
+ type: boolean
4105
+ guardianExternalUserId:
4106
+ type: string
4107
+ guardianDeliveryChatId:
4108
+ type: string
4109
+ guardianUsername:
4110
+ type: string
4111
+ guardianDisplayName:
4112
+ type: string
4113
+ verificationSessionId:
4114
+ type: string
4115
+ expiresAt:
4116
+ type: number
4117
+ nextResendAt:
4118
+ anyOf:
4119
+ - type: number
4120
+ - type: "null"
4121
+ sendCount:
4122
+ type: number
4123
+ pendingBootstrap:
4124
+ type: boolean
4125
+ required:
4126
+ - success
4127
+ - bound
4128
+ - channel
4129
+ - assistantId
4130
+ - hasPendingChallenge
4131
+ additionalProperties: false
4079
4132
  /v1/channels/available:
4080
4133
  get:
4081
4134
  operationId: channels_available_get
@@ -4210,6 +4263,18 @@ paths:
4210
4263
  - removed
4211
4264
  emoji:
4212
4265
  type: string
4266
+ emojiKind:
4267
+ type: string
4268
+ enum:
4269
+ - unicode
4270
+ - shortcode
4271
+ - custom
4272
+ emojiName:
4273
+ type: string
4274
+ emojiId:
4275
+ type: string
4276
+ emojiAnimated:
4277
+ type: boolean
4213
4278
  targetMessageId:
4214
4279
  type: string
4215
4280
  required:
@@ -8136,6 +8201,54 @@ paths:
8136
8201
  description: The assistant is currently responding
8137
8202
  "422":
8138
8203
  description: No user message exists to retry from
8204
+ /v1/conversations/{id}/sight-frame:
8205
+ post:
8206
+ operationId: conversations_by_id_sightframe_post
8207
+ summary: Persist an ambient camera frame
8208
+ description:
8209
+ Add an already-uploaded camera frame to a conversation as its own message, tagged so retention can age the
8210
+ image out of the model's context while the transcript keeps it. No reply is generated. Returns persisted=false
8211
+ when the frame was dropped, in which case the assistant owns the upload and there is nothing to clean up.
8212
+ tags:
8213
+ - conversations
8214
+ parameters:
8215
+ - name: id
8216
+ in: path
8217
+ required: true
8218
+ schema:
8219
+ type: string
8220
+ requestBody:
8221
+ required: true
8222
+ content:
8223
+ application/json:
8224
+ schema:
8225
+ type: object
8226
+ properties:
8227
+ attachmentId:
8228
+ type: string
8229
+ description: Id of the uploaded attachment holding the frame.
8230
+ required:
8231
+ - attachmentId
8232
+ responses:
8233
+ "200":
8234
+ description: Successful response
8235
+ content:
8236
+ application/json:
8237
+ schema:
8238
+ type: object
8239
+ properties:
8240
+ persisted:
8241
+ type: boolean
8242
+ messageId:
8243
+ description: The message the frame landed on, when one was written.
8244
+ type: string
8245
+ required:
8246
+ - persisted
8247
+ additionalProperties: false
8248
+ "400":
8249
+ description: Missing or empty attachmentId
8250
+ "404":
8251
+ description: Conversation not found
8139
8252
  /v1/conversations/{id}/slash:
8140
8253
  post:
8141
8254
  operationId: conversations_by_id_slash_post
@@ -9865,7 +9978,11 @@ paths:
9865
9978
  unassigned conversations, Chats) with total and unread counts and no conversation rows. Lets a client know which
9866
9979
  sections exist, and what their badges say, without fetching any conversation list. Totals follow the standard
9867
9980
  listing visibility; unread applies the same rules as GET /v1/conversations/unread-count scoped to the section.
9868
- Chats is always present, even at zero: it is the leftover bucket and renders regardless."
9981
+ Chats is always present, even at zero: it is the leftover bucket and renders regardless. Under the
9982
+ `assistant-initiated-threads` flag an `assistant` section is also present at every count — the threads the
9983
+ assistant started on its own, selectable as a list via `groupId=system:assistant` — and its rows are withheld
9984
+ from Chats so the buckets stay disjoint. With the flag off no `assistant` row is emitted and those conversations
9985
+ are counted in Chats as before."
9869
9986
  tags:
9870
9987
  - conversations
9871
9988
  responses:
@@ -9894,6 +10011,20 @@ paths:
9894
10011
  - total
9895
10012
  - unread
9896
10013
  additionalProperties: false
10014
+ - type: object
10015
+ properties:
10016
+ kind:
10017
+ type: string
10018
+ const: assistant
10019
+ total:
10020
+ type: number
10021
+ unread:
10022
+ type: number
10023
+ required:
10024
+ - kind
10025
+ - total
10026
+ - unread
10027
+ additionalProperties: false
9897
10028
  - type: object
9898
10029
  properties:
9899
10030
  kind:
@@ -10966,7 +11097,9 @@ paths:
10966
11097
  description: Processed text output
10967
11098
  mode:
10968
11099
  type: string
10969
- description: "Detected mode: dictation, command, or action"
11100
+ description:
11101
+ "Detected mode: dictation, command, action, or question (selected text the words asked about rather than
11102
+ changed; text is the transcription)"
10970
11103
  actionPlan:
10971
11104
  description: Action plan (only when mode is action)
10972
11105
  type: string
@@ -20963,6 +21096,18 @@ paths:
20963
21096
  properties:
20964
21097
  emoji:
20965
21098
  type: string
21099
+ emojiKind:
21100
+ type: string
21101
+ enum:
21102
+ - unicode
21103
+ - shortcode
21104
+ - custom
21105
+ emojiName:
21106
+ type: string
21107
+ emojiId:
21108
+ type: string
21109
+ emojiAnimated:
21110
+ type: boolean
20966
21111
  op:
20967
21112
  type: string
20968
21113
  enum:
@@ -21032,6 +21177,18 @@ paths:
21032
21177
  properties:
21033
21178
  emoji:
21034
21179
  type: string
21180
+ emojiKind:
21181
+ type: string
21182
+ enum:
21183
+ - unicode
21184
+ - shortcode
21185
+ - custom
21186
+ emojiName:
21187
+ type: string
21188
+ emojiId:
21189
+ type: string
21190
+ emojiAnimated:
21191
+ type: boolean
21035
21192
  op:
21036
21193
  type: string
21037
21194
  enum:
@@ -21050,6 +21207,8 @@ paths:
21050
21207
  - channelId
21051
21208
  - channelTs
21052
21209
  additionalProperties: false
21210
+ deletedAt:
21211
+ type: number
21053
21212
  queueStatus:
21054
21213
  type: string
21055
21214
  enum:
@@ -24269,7 +24428,9 @@ paths:
24269
24428
  post:
24270
24429
  operationId: platform_connect_post
24271
24430
  summary: Connect to the Vellum Platform
24272
- description: Checks existing credentials and emits the show_platform_login signal for connected clients to show a login UI.
24431
+ description:
24432
+ Checks whether the stored platform credentials still connect. Reports a stored key the platform rejects as
24433
+ credentialRejected, so the caller can direct the user to sign in again and replace it.
24273
24434
  tags:
24274
24435
  - platform
24275
24436
  responses:
@@ -24286,6 +24447,8 @@ paths:
24286
24447
  type: string
24287
24448
  showPlatformLogin:
24288
24449
  type: boolean
24450
+ credentialRejected:
24451
+ type: boolean
24289
24452
  additionalProperties: false
24290
24453
  /v1/platform/credits:
24291
24454
  get:
@@ -24654,6 +24817,32 @@ paths:
24654
24817
  - package
24655
24818
  - entitlements
24656
24819
  additionalProperties: false
24820
+ /v1/platform/verify-credential:
24821
+ post:
24822
+ operationId: platform_verifycredential_post
24823
+ summary: Verify the Vellum-managed credential against the platform
24824
+ description:
24825
+ Asks the platform whether the stored assistant API key authenticates. POST because it performs a check; the
24826
+ result is returned, not stored.
24827
+ tags:
24828
+ - platform
24829
+ responses:
24830
+ "200":
24831
+ description: Successful response
24832
+ content:
24833
+ application/json:
24834
+ schema:
24835
+ type: object
24836
+ properties:
24837
+ status:
24838
+ type: string
24839
+ enum:
24840
+ - valid
24841
+ - rejected
24842
+ - unknown
24843
+ required:
24844
+ - status
24845
+ additionalProperties: false
24657
24846
  /v1/playground/seed-conversation:
24658
24847
  post:
24659
24848
  operationId: playground_seedconversation_post
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.11.8",
3
+ "version": "0.11.9-staging.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -74,7 +74,7 @@
74
74
  "remark-parse": "11.0.0",
75
75
  "rrule": "2.8.1",
76
76
  "semver": "7.8.0",
77
- "sharp": "0.34.5",
77
+ "sharp": "0.35.4",
78
78
  "stemmer": "2.0.1",
79
79
  "tar-stream": "3.1.7",
80
80
  "tldts": "7.0.25",
@@ -2,7 +2,8 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  buildAccessRequestCardView,
5
- buildAccessRequestContractText,
5
+ buildAccessRequestContextText,
6
+ buildAccessRequestReplyMechanics,
6
7
  parseAccessRequestPayload,
7
8
  } from "../notifications/access-request-copy.js";
8
9
 
@@ -111,10 +112,10 @@ describe("admitted-mode introduction nudge copy", () => {
111
112
  senderIdentifier: "Alice",
112
113
  sourceChannel: "telegram",
113
114
  };
114
- expect(buildAccessRequestContractText(base)).toContain(
115
+ expect(buildAccessRequestContextText(base)).toContain(
115
116
  "is requesting access to the assistant.",
116
117
  );
117
- const admitted = buildAccessRequestContractText({
118
+ const admitted = buildAccessRequestContextText({
118
119
  ...base,
119
120
  trigger: "admitted",
120
121
  });
@@ -122,8 +123,8 @@ describe("admitted-mode introduction nudge copy", () => {
122
123
  expect(admitted).not.toContain("is requesting access");
123
124
  });
124
125
 
125
- test("admitted contract text keeps the decision directives", () => {
126
- const text = buildAccessRequestContractText({
126
+ test("admitted reply mechanics keep the decision directives", () => {
127
+ const text = buildAccessRequestReplyMechanics({
127
128
  actorDisplayName: "Alice",
128
129
  sourceChannel: "telegram",
129
130
  requestCode: "ab12cd",
@@ -136,10 +136,13 @@ describe("buildAccessRequestSeedContentBlocks", () => {
136
136
  );
137
137
  });
138
138
 
139
- test("text fallback block contains contract text", () => {
139
+ test("text fallback block is the text-only rendering: context, invite directive, and the typed directive", () => {
140
140
  const textBlock = textOf(buildAccessRequestSeedContentBlocks(basePayload));
141
141
  expect(textBlock.text).toContain("requesting access to the assistant");
142
- expect(textBlock.text).toContain("ABC123");
142
+ expect(textBlock.text).toContain('Reply "open invite flow"');
143
+ // A client that cannot draw the card's buttons still gets the directive.
144
+ expect(textBlock.text).toContain('"ABC123 trust"');
145
+ expect(textBlock.text).toContain('"ABC123 block"');
143
146
  });
144
147
 
145
148
  test("body shows fallback when no preview/warnings/permalink", () => {
@@ -1,16 +1,9 @@
1
1
  /**
2
- * Verifies how the loop derives the `mutableLatestUserMessage` cache-anchor
3
- * signal on `SendMessageOptions.config`.
2
+ * Verifies that a persisted memory-v3 spotlight already in history is sent
3
+ * as-is and does not flag the turn-start message as volatile.
4
4
  *
5
- * The signal means "the latest user message's bytes do not recur next turn", so
6
- * it is set from the history actually being sent: only when the latest user
7
- * message carries a memory-v3 `<memory_spotlight>` block, the one injected
8
- * block that is strip-and-replaced on the tail every turn. Every other injected
9
- * block is frozen into history and re-renders byte-identically, so turns
10
- * without a spotlight keep a normal cache anchor even when memory-v3 is live.
11
- *
12
- * When no spotlight is present the field is omitted entirely (not `false`) so
13
- * the wire stays byte-identical for conversations that never see one.
5
+ * Spotlight stays on the user message that was sent. The loop must not
6
+ * attach, strip, or mark that message mutable.
14
7
  */
15
8
 
16
9
  import { describe, expect, test } from "bun:test";
@@ -25,17 +18,18 @@ import type {
25
18
  ToolDefinition,
26
19
  } from "../providers/types.js";
27
20
 
21
+ const spotlightText = wrapMemorySpotlightBlock("recalled: Alice's plan");
22
+
28
23
  const userMessage: Message = {
29
24
  role: "user",
30
25
  content: [{ type: "text", text: "hi" }],
31
26
  };
32
27
 
33
- /** A user turn whose tail block is the ephemeral memory-v3 spotlight. */
34
- const spotlightUserMessage: Message = {
28
+ const userMessageWithSpotlight: Message = {
35
29
  role: "user",
36
30
  content: [
37
31
  { type: "text", text: "hi" },
38
- { type: "text", text: wrapMemorySpotlightBlock("recalled: Alice's plan") },
32
+ { type: "text", text: spotlightText },
39
33
  ],
40
34
  };
41
35
 
@@ -64,22 +58,25 @@ function toolUseResponse(
64
58
  function makeRecordingProvider(responses: ProviderResponse[]): {
65
59
  provider: Provider;
66
60
  configs: () => Array<Record<string, unknown> | undefined>;
61
+ sent: () => Message[][];
67
62
  } {
68
63
  const configs: Array<Record<string, unknown> | undefined> = [];
64
+ const sent: Message[][] = [];
69
65
  let i = 0;
70
66
  const provider: Provider = {
71
67
  name: "mock",
72
68
  async sendMessage(
73
- _messages: Message[],
69
+ messages: Message[],
74
70
  options?: SendMessageOptions,
75
71
  ): Promise<ProviderResponse> {
72
+ sent.push(messages);
76
73
  configs.push(options?.config as Record<string, unknown> | undefined);
77
74
  const response = responses[i] ?? responses[responses.length - 1];
78
75
  i++;
79
76
  return response;
80
77
  },
81
78
  };
82
- return { provider, configs: () => configs };
79
+ return { provider, configs: () => configs, sent: () => sent };
83
80
  }
84
81
 
85
82
  const echoTool: ToolDefinition = {
@@ -91,34 +88,11 @@ const echoTool: ToolDefinition = {
91
88
  },
92
89
  };
93
90
 
94
- describe("AgentLoop.run: mutableLatestUserMessage from spotlight presence", () => {
95
- test("sets mutableLatestUserMessage when the latest user message carries a spotlight block", async () => {
96
- // GIVEN a provider that records the config of each LLM call
97
- const { provider, configs } = makeRecordingProvider([textResponse("done")]);
98
- const loop = new AgentLoop({
99
- provider,
100
- systemPrompt: "system",
101
- conversationId: "test-conversation",
102
- config: { maxTokens: 1024 },
103
- });
104
-
105
- // WHEN the loop runs over a history whose tail carries the spotlight
106
- await loop.run({
107
- requestId: "test-request",
108
- messages: [spotlightUserMessage],
109
- onEvent: () => {},
110
- trust: { sourceChannel: "vellum", trustClass: "unknown" },
111
- callSite: "mainAgent",
112
- });
113
-
114
- // THEN the send carries the cache-anchor signal
115
- expect(configs()).toHaveLength(1);
116
- expect(configs()[0]?.mutableLatestUserMessage).toBe(true);
117
- });
118
-
119
- test("omits mutableLatestUserMessage when the latest user message carries no spotlight", async () => {
120
- // GIVEN a provider that records the config of each LLM call
121
- const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
91
+ describe("AgentLoop.run: persisted spotlight in history", () => {
92
+ test("sends a spotlight already in history and does not flag the turn-start as volatile", async () => {
93
+ const { provider, configs, sent } = makeRecordingProvider([
94
+ textResponse("done"),
95
+ ]);
122
96
  const loop = new AgentLoop({
123
97
  provider,
124
98
  systemPrompt: "system",
@@ -126,24 +100,26 @@ describe("AgentLoop.run: mutableLatestUserMessage from spotlight presence", () =
126
100
  config: { maxTokens: 1024 },
127
101
  });
128
102
 
129
- // WHEN the loop runs over a spotlight-free history: the shape memory-v3
130
- // produces whenever it selects nothing to spotlight
131
- await loop.run({
103
+ const result = await loop.run({
132
104
  requestId: "test-request",
133
- messages: [userMessage],
105
+ messages: [userMessageWithSpotlight],
134
106
  onEvent: () => {},
135
107
  trust: { sourceChannel: "vellum", trustClass: "unknown" },
136
108
  callSite: "mainAgent",
137
109
  });
138
110
 
139
- // THEN the field is omitted entirely, not carried as false/undefined
140
- expect(configs()).toHaveLength(1);
111
+ expect(sent()).toHaveLength(1);
112
+ expect(sent()[0]![0]!.content).toEqual(userMessageWithSpotlight.content);
113
+ expect(result.history[0]!.content).toEqual(
114
+ userMessageWithSpotlight.content,
115
+ );
141
116
  expect("mutableLatestUserMessage" in (configs()[0] ?? {})).toBe(false);
142
117
  });
143
118
 
144
- test("a text block merely opening with the spotlight tag is not treated as volatile", async () => {
145
- // GIVEN a user message discussing the marker rather than carrying one
146
- const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
119
+ test("does not set mutableLatestUserMessage when history has no spotlight", async () => {
120
+ const { provider, sent, configs } = makeRecordingProvider([
121
+ textResponse("hi"),
122
+ ]);
147
123
  const loop = new AgentLoop({
148
124
  provider,
149
125
  systemPrompt: "system",
@@ -151,34 +127,20 @@ describe("AgentLoop.run: mutableLatestUserMessage from spotlight presence", () =
151
127
  config: { maxTokens: 1024 },
152
128
  });
153
129
 
154
- // WHEN the loop runs
155
130
  await loop.run({
156
131
  requestId: "test-request",
157
- messages: [
158
- {
159
- role: "user",
160
- content: [
161
- {
162
- type: "text",
163
- text: "<memory_spotlight>\nwhat does this tag do?",
164
- },
165
- ],
166
- },
167
- ],
132
+ messages: [userMessage],
168
133
  onEvent: () => {},
169
134
  trust: { sourceChannel: "vellum", trustClass: "unknown" },
170
135
  callSite: "mainAgent",
171
136
  });
172
137
 
173
- // THEN the unterminated wrapper does not count: matching the full-wrapper
174
- // requirement the per-turn strip applies
175
- expect(configs()).toHaveLength(1);
138
+ expect(sent()[0]).toEqual([userMessage]);
176
139
  expect("mutableLatestUserMessage" in (configs()[0] ?? {})).toBe(false);
177
140
  });
178
141
 
179
- test("the signal holds for every request in a tool loop", async () => {
180
- // GIVEN a spotlight-bearing opening message and a tool round-trip
181
- const { provider, configs } = makeRecordingProvider([
142
+ test("keeps a persisted spotlight on the opening user message through a tool loop", async () => {
143
+ const { provider, sent, configs } = makeRecordingProvider([
182
144
  toolUseResponse("t1", "echo", { value: "first" }),
183
145
  textResponse("done"),
184
146
  ]);
@@ -191,22 +153,21 @@ describe("AgentLoop.run: mutableLatestUserMessage from spotlight presence", () =
191
153
  toolExecutor: async () => ({ content: "ok", isError: false }),
192
154
  });
193
155
 
194
- // WHEN the loop runs
195
- await loop.run({
156
+ const result = await loop.run({
196
157
  requestId: "test-request",
197
- messages: [spotlightUserMessage],
158
+ messages: [userMessageWithSpotlight],
198
159
  onEvent: () => {},
199
160
  trust: { sourceChannel: "vellum", trustClass: "unknown" },
200
161
  callSite: "mainAgent",
201
162
  });
202
163
 
203
- // THEN both calls report the turn as volatile. The signal describes the
204
- // turn-starting message, not whichever user message happens to be last, so
205
- // the trailing tool-result turn does not clear it. Holding it steady is
206
- // what keeps the provider marking that one block at a single TTL for the
207
- // whole turn instead of writing it twice.
208
- expect(configs()).toHaveLength(2);
209
- expect(configs()[0]?.mutableLatestUserMessage).toBe(true);
210
- expect(configs()[1]?.mutableLatestUserMessage).toBe(true);
164
+ expect(sent()).toHaveLength(2);
165
+ expect(sent()[0]![0]!.content).toEqual(userMessageWithSpotlight.content);
166
+ expect(sent()[1]![0]!.content).toEqual(userMessageWithSpotlight.content);
167
+ expect(result.history[0]!.content).toEqual(
168
+ userMessageWithSpotlight.content,
169
+ );
170
+ expect("mutableLatestUserMessage" in (configs()[0] ?? {})).toBe(false);
171
+ expect("mutableLatestUserMessage" in (configs()[1] ?? {})).toBe(false);
211
172
  });
212
173
  });
@@ -26,7 +26,7 @@ afterAll(() => {
26
26
  });
27
27
 
28
28
  describe("always-loaded tool count", () => {
29
- test("should be exactly 10 with recall occupying the existing slot", async () => {
29
+ test("should be exactly 11 with recall occupying the existing slot", async () => {
30
30
  await initializeTools();
31
31
  const allDefs = getAllToolDefinitions();
32
32
 
@@ -47,6 +47,11 @@ describe("always-loaded tool count", () => {
47
47
  // Host tools (host_bash, host_file_*) are excluded when no client is
48
48
  // connected — without a human in the loop, the guardian auto-approve
49
49
  // path would allow unchecked host command execution.
50
+ //
51
+ // `watch_retro_report` is here for the same reason the ui_surface tools are
52
+ // NOT: a watch retrospective runs clientless, so it can only report through
53
+ // a tool that survives this baseline. Its description and schema are kept
54
+ // deliberately terse because that is the cost of the slot.
50
55
  const expectedNames = [
51
56
  "bash",
52
57
  "file_edit",
@@ -56,12 +61,13 @@ describe("always-loaded tool count", () => {
56
61
  "remember",
57
62
  "skill_execute",
58
63
  "skill_load",
64
+ "watch_retro_report",
59
65
  "web_fetch",
60
66
  "web_search",
61
67
  ].sort();
62
68
 
63
69
  expect(activeNames).toEqual(expectedNames);
64
70
  expect(activeNames.filter((name) => name === "recall")).toHaveLength(1);
65
- expect(activeTools.length).toBe(10);
71
+ expect(activeTools.length).toBe(11);
66
72
  });
67
73
  });