@vellumai/assistant 0.11.1 → 0.11.2-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 (318) hide show
  1. package/Dockerfile +1 -3
  2. package/README.md +1 -1
  3. package/eslint.config.mjs +28 -8
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  12. package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  13. package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
  14. package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
  15. package/openapi.yaml +292 -5
  16. package/package.json +1 -1
  17. package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
  18. package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
  19. package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
  20. package/src/__tests__/compaction-events.test.ts +139 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +35 -3
  22. package/src/__tests__/config-schema.test.ts +1 -0
  23. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
  24. package/src/__tests__/conversation-agent-loop.test.ts +180 -7
  25. package/src/__tests__/conversation-error.test.ts +211 -4
  26. package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
  27. package/src/__tests__/conversation-queue.test.ts +126 -0
  28. package/src/__tests__/conversation-retry-route.test.ts +46 -7
  29. package/src/__tests__/conversation-slash.test.ts +4 -2
  30. package/src/__tests__/conversation-summarize-route.test.ts +7 -1
  31. package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
  32. package/src/__tests__/document-create-dedupe.test.ts +2 -1
  33. package/src/__tests__/document-find-replace.test.ts +2 -1
  34. package/src/__tests__/document-tool-security.test.ts +2 -1
  35. package/src/__tests__/document-update-default-surface.test.ts +2 -1
  36. package/src/__tests__/document-workspace-file.test.ts +467 -0
  37. package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
  38. package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
  39. package/src/__tests__/http-user-message-parity.test.ts +66 -0
  40. package/src/__tests__/list-messages-attachments.test.ts +87 -0
  41. package/src/__tests__/list-messages-provider-error.test.ts +143 -0
  42. package/src/__tests__/list-messages-system-card.test.ts +100 -0
  43. package/src/__tests__/llm-resolver.test.ts +19 -9
  44. package/src/__tests__/managed-profile-guard.test.ts +23 -0
  45. package/src/__tests__/notification-platform-adapter.test.ts +130 -2
  46. package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
  47. package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
  48. package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
  49. package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
  50. package/src/__tests__/provider-error-scenarios.test.ts +140 -0
  51. package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
  52. package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
  53. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
  54. package/src/__tests__/skills.test.ts +27 -0
  55. package/src/__tests__/slack-channels-routes.test.ts +0 -2
  56. package/src/__tests__/slack-share-routes.test.ts +0 -3
  57. package/src/__tests__/slack-users-routes.test.ts +0 -2
  58. package/src/__tests__/subagent-tools.test.ts +11 -0
  59. package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
  60. package/src/__tests__/tool-result-spool.test.ts +5 -4
  61. package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
  62. package/src/__tests__/turn-events-store.test.ts +26 -0
  63. package/src/__tests__/ui-visual-surface.test.ts +695 -0
  64. package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
  65. package/src/__tests__/visible-app-context.test.ts +189 -0
  66. package/src/__tests__/workspace-git-service.test.ts +173 -33
  67. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
  68. package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
  69. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
  70. package/src/agent/loop.ts +36 -7
  71. package/src/api/events/context-window-usage.ts +31 -0
  72. package/src/api/events/notification-intent.ts +8 -0
  73. package/src/api/events/ui-surface-pending.ts +35 -0
  74. package/src/api/index.ts +14 -0
  75. package/src/api/responses/conversation-message.ts +25 -4
  76. package/src/api/surfaces.ts +90 -1
  77. package/src/approvals/guardian-card-withdrawal.ts +66 -31
  78. package/src/approvals/guardian-decision-primitive.ts +4 -0
  79. package/src/calls/__tests__/call-setup-router.test.ts +156 -26
  80. package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
  81. package/src/calls/call-setup-router.ts +80 -36
  82. package/src/calls/voice-session-bridge.ts +173 -26
  83. package/src/cli/commands/inference.help.ts +3 -3
  84. package/src/cli/commands/notifications.help.ts +3 -2
  85. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
  86. package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
  87. package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
  88. package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
  89. package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
  90. package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
  91. package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
  92. package/src/cli/commands/platform/index.help.ts +92 -0
  93. package/src/cli/commands/platform/index.ts +7 -0
  94. package/src/cli/commands/platform/invoices.ts +132 -0
  95. package/src/cli/commands/usage.help.ts +1 -1
  96. package/src/cli/lib/list-installed-plugins.ts +2 -1
  97. package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
  98. package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
  99. package/src/config/assistant-feature-flags.ts +7 -2
  100. package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
  101. package/src/config/bundled-skills/subagent/SKILL.md +3 -1
  102. package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
  103. package/src/config/bundled-skills/visualize/SKILL.md +163 -0
  104. package/src/config/call-site-defaults.ts +3 -2
  105. package/src/config/default-profile-catalog.ts +75 -70
  106. package/src/config/default-profile-names.ts +13 -28
  107. package/src/config/env-registry.ts +1 -0
  108. package/src/config/feature-flag-registry.json +16 -0
  109. package/src/config/llm-resolver.ts +21 -5
  110. package/src/config/loader.ts +18 -11
  111. package/src/config/schemas/llm.ts +1 -9
  112. package/src/config/schemas/memory-retrospective.ts +9 -0
  113. package/src/config/schemas/monitoring.ts +28 -2
  114. package/src/config/schemas/workspace-git.ts +15 -0
  115. package/src/config/seed-inference-profiles.ts +19 -5
  116. package/src/context/post-turn-tool-result-truncation.ts +2 -2
  117. package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
  118. package/src/conversations/message-consolidation.ts +17 -15
  119. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
  120. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
  121. package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
  122. package/src/daemon/conversation-agent-loop.ts +107 -22
  123. package/src/daemon/conversation-error.ts +169 -48
  124. package/src/daemon/conversation-messaging.ts +87 -4
  125. package/src/daemon/conversation-process.ts +57 -45
  126. package/src/daemon/conversation-runtime-assembly.ts +57 -0
  127. package/src/daemon/conversation-store.ts +36 -1
  128. package/src/daemon/conversation-surfaces.ts +68 -6
  129. package/src/daemon/conversation-turn-finalize.ts +76 -18
  130. package/src/daemon/conversation.ts +122 -27
  131. package/src/daemon/lifecycle.ts +9 -0
  132. package/src/daemon/message-types/conversations.ts +8 -0
  133. package/src/daemon/message-types/surfaces.ts +3 -0
  134. package/src/documents/document-store.ts +247 -10
  135. package/src/home/__tests__/feed-types.test.ts +43 -8
  136. package/src/home/__tests__/feed-writer.test.ts +59 -0
  137. package/src/home/feed-types.ts +34 -7
  138. package/src/home/feed-writer.ts +15 -6
  139. package/src/live-voice/__tests__/activity-label.test.ts +95 -0
  140. package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
  141. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
  142. package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
  143. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
  144. package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
  145. package/src/live-voice/activity-label.ts +169 -0
  146. package/src/live-voice/live-activity-reporter.ts +35 -6
  147. package/src/live-voice/live-voice-session.ts +282 -41
  148. package/src/live-voice/protocol.ts +35 -0
  149. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
  150. package/src/messaging/providers/slack/adapter.ts +86 -19
  151. package/src/messaging/providers/slack/api.test.ts +85 -1
  152. package/src/messaging/providers/slack/api.ts +121 -288
  153. package/src/messaging/providers/slack/client.ts +20 -251
  154. package/src/messaging/providers/slack/send.test.ts +4 -9
  155. package/src/messaging/providers/slack/send.ts +1 -1
  156. package/src/messaging/providers/slack/types.ts +5 -0
  157. package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
  158. package/src/messaging/providers/slack/web-api-transport.ts +367 -0
  159. package/src/messaging/providers/slack/withdraw.ts +6 -14
  160. package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
  161. package/src/messaging/providers/telegram-bot/send.ts +23 -2
  162. package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
  163. package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
  164. package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
  165. package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
  166. package/src/monitoring/file-descriptors.ts +262 -0
  167. package/src/monitoring/process-memory.ts +4 -11
  168. package/src/monitoring/resource-sampler.ts +5 -0
  169. package/src/monitoring/worker.ts +12 -0
  170. package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
  171. package/src/notifications/__tests__/broadcaster.test.ts +347 -7
  172. package/src/notifications/__tests__/copy-composer.test.ts +53 -3
  173. package/src/notifications/__tests__/decision-engine.test.ts +349 -29
  174. package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
  175. package/src/notifications/__tests__/edit-notification.test.ts +330 -0
  176. package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
  177. package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
  178. package/src/notifications/adapters/macos.ts +3 -0
  179. package/src/notifications/adapters/platform.ts +90 -14
  180. package/src/notifications/adapters/telegram.ts +6 -4
  181. package/src/notifications/assistant-reply-producer.ts +219 -0
  182. package/src/notifications/broadcaster.ts +523 -317
  183. package/src/notifications/copy-composer.ts +27 -6
  184. package/src/notifications/decision-engine.ts +154 -77
  185. package/src/notifications/deterministic-checks.ts +6 -7
  186. package/src/notifications/edit-notification.ts +8 -4
  187. package/src/notifications/emit-signal.ts +49 -16
  188. package/src/notifications/guardian-delivery-recorder.ts +8 -2
  189. package/src/notifications/home-feed-side-effect.ts +43 -6
  190. package/src/notifications/notification-utils.ts +39 -4
  191. package/src/notifications/signal.ts +10 -0
  192. package/src/notifications/types.ts +17 -0
  193. package/src/persistence/bookmark-crud.ts +18 -9
  194. package/src/persistence/conversation-attention-store.ts +26 -0
  195. package/src/persistence/conversation-crud.ts +79 -32
  196. package/src/persistence/conversation-queries.ts +30 -15
  197. package/src/persistence/conversation-title-service.ts +5 -170
  198. package/src/persistence/conversation-types.ts +192 -1
  199. package/src/persistence/db-init.ts +14 -1
  200. package/src/persistence/db-maintenance.ts +5 -4
  201. package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
  202. package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
  203. package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
  204. package/src/persistence/embeddings/embedding-local.ts +2 -1
  205. package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
  206. package/src/persistence/embeddings/plugin-index.ts +84 -12
  207. package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
  208. package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
  209. package/src/persistence/planner-statistics.ts +14 -0
  210. package/src/persistence/schema/documents.ts +26 -11
  211. package/src/persistence/steps.ts +2 -0
  212. package/src/platform/client.test.ts +172 -2
  213. package/src/platform/client.ts +178 -55
  214. package/src/plugin-api/conversation-turn.ts +13 -0
  215. package/src/plugin-api/model-profiles.test.ts +6 -2
  216. package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
  217. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
  218. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
  219. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
  220. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
  224. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
  225. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
  226. package/src/plugins/defaults/memory/host-utils.ts +5 -0
  227. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
  228. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
  229. package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
  230. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
  231. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
  232. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
  233. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
  234. package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
  235. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
  236. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
  237. package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
  238. package/src/plugins/defaults/memory/v2/injection.ts +48 -3
  239. package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
  240. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
  241. package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
  242. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  243. package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
  244. package/src/plugins/types.ts +17 -0
  245. package/src/prompts/templates/system-sections.ts +2 -2
  246. package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
  247. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
  248. package/src/providers/call-site-routing.ts +77 -14
  249. package/src/providers/connection-resolution.ts +42 -2
  250. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
  251. package/src/providers/inference/adapter-factory.ts +70 -12
  252. package/src/providers/model-catalog.ts +0 -11
  253. package/src/providers/model-intents.ts +11 -0
  254. package/src/providers/registry.ts +8 -0
  255. package/src/providers/retry.ts +85 -17
  256. package/src/providers/types.ts +8 -1
  257. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  258. package/src/runtime/agent-wake.ts +132 -9
  259. package/src/runtime/channel-approval-types.ts +25 -0
  260. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
  261. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
  262. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
  263. package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
  264. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
  265. package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
  266. package/src/runtime/routes/consolidation-routes.ts +1 -1
  267. package/src/runtime/routes/conversation-management-routes.ts +18 -6
  268. package/src/runtime/routes/conversation-query-routes.ts +14 -23
  269. package/src/runtime/routes/conversation-routes.ts +113 -8
  270. package/src/runtime/routes/credential-routes.ts +1 -5
  271. package/src/runtime/routes/documents-routes.ts +207 -12
  272. package/src/runtime/routes/identity-routes.ts +3 -93
  273. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
  274. package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
  275. package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
  276. package/src/runtime/routes/notification-routes.ts +4 -1
  277. package/src/runtime/routes/platform-routes.ts +238 -3
  278. package/src/runtime/routes/playground/guard.ts +1 -2
  279. package/src/runtime/routes/slack-channel-routes.ts +2 -4
  280. package/src/runtime/routes/workspace-commit-routes.ts +49 -2
  281. package/src/runtime/routes/workspace-routes.ts +11 -12
  282. package/src/runtime/routes/workspace-utils.ts +49 -2
  283. package/src/runtime/services/conversation-serializer.ts +2 -4
  284. package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
  285. package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
  286. package/src/subagent/__tests__/consult-context.test.ts +84 -0
  287. package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
  288. package/src/subagent/consult-context.ts +410 -0
  289. package/src/subagent/consult-prompt.ts +38 -5
  290. package/src/subagent/manager.ts +9 -4
  291. package/src/subagent/types.ts +8 -0
  292. package/src/telemetry/telemetry-event-sources.ts +7 -0
  293. package/src/telemetry/telemetry-wire-source.json +1 -1
  294. package/src/telemetry/telemetry-wire.generated.ts +1 -0
  295. package/src/telemetry/turn-events-store.ts +30 -1
  296. package/src/telemetry/types.ts +24 -0
  297. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  298. package/src/tools/browser/pinned-tabs.ts +3 -1
  299. package/src/tools/subagent/spawn.ts +23 -1
  300. package/src/tools/terminal/safe-env.ts +1 -0
  301. package/src/tools/ui-surface/definitions.ts +39 -1
  302. package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
  303. package/src/tools/ui-surface/visual-validation.ts +787 -0
  304. package/src/tools/workflows/run-workflow.test.ts +1 -0
  305. package/src/util/__tests__/short-title.test.ts +229 -0
  306. package/src/util/__tests__/worker-compute.test.ts +65 -0
  307. package/src/util/cgroup-cpu.ts +93 -0
  308. package/src/util/errors.ts +27 -0
  309. package/src/util/process-tree.ts +19 -0
  310. package/src/util/short-title.ts +189 -0
  311. package/src/util/worker-compute.ts +83 -0
  312. package/src/workspace/byok-default-profile-ensure.ts +25 -15
  313. package/src/workspace/git-service.ts +79 -30
  314. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
  315. package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
  316. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
  317. package/src/workspace/migrations/registry.ts +6 -0
  318. package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
@@ -254,13 +254,13 @@ describe("LiveVoiceSession archive and metrics events", () => {
254
254
  userMessageInterface: "macos",
255
255
  assistantMessageInterface: "macos",
256
256
  // Pins the full production control prompt for the FRONT-DOOR leg (the
257
- // first leg of every routed turn): the no-UI rule (voice turns are
258
- // non-interactive everything is conveyed in speech) and the shared
259
- // no-setup-flows rule (no OAuth/browser flows mid-call). The [-1]
257
+ // first leg of every routed turn): the speech-first rule (which now
258
+ // permits a surface, since the room reveals one by itself) and the
259
+ // shared no-setup-flows rule (no OAuth/browser flows mid-call). The [-1]
260
260
  // room-minimize teaching is deliberately absent — only the escalated
261
261
  // leg learns it (see live-voice-triage-escalate.test.ts).
262
262
  voiceControlPrompt:
263
- "You are speaking in a local live voice session. Keep replies brief and conversational. You cannot display cards, forms, or any on-screen UI during the call convey everything in speech. " +
263
+ "You are speaking in a local live voice session. Keep replies brief and conversational. Speech is the main channel: say the answer, and do not narrate a surface instead of answering. You can also put something on screen when it genuinely helps (a form, a list to pick from, a progress card for long work); the call overlay minimizes by itself once you finish speaking, so the user sees it without doing anything. Never tell the user you cannot show them something. " +
264
264
  VOICE_NO_SETUP_FLOWS_RULE,
265
265
  });
266
266
  callbacks?.assistant_text_delta?.(makeTextDelta("Hello there."));
@@ -190,7 +190,7 @@ describe("live-voice triage-and-escalate routing", () => {
190
190
  expect(escalated?.content).toBe(ESCALATION_CONTINUATION_CONTENT);
191
191
  });
192
192
 
193
- test("the [-1] teaching reaches the escalated leg's prompt but never the front-door leg's", async () => {
193
+ test("the screen-reveal teaching reaches the escalated leg's prompt but never the front-door leg's", async () => {
194
194
  const { starter } = scriptedStartVoiceTurn({
195
195
  frontDoor: ["[1] ", "Let me think about that."],
196
196
  escalated: ["The detailed answer is 42."],
@@ -205,10 +205,14 @@ describe("live-voice triage-and-escalate routing", () => {
205
205
  starter.mock.calls[0]?.[0]?.voiceControlPrompt ?? "";
206
206
  const escalatedPrompt =
207
207
  starter.mock.calls[1]?.[0]?.voiceControlPrompt ?? "";
208
- // The toolless fast leg has nothing to show and its decision rule promises
209
- // verbatim speech it must never learn the marker.
208
+ // The toolless fast leg has nothing to show, so it is never told the
209
+ // screen will be revealed.
210
+ expect(frontDoorPrompt).not.toContain("the overlay minimizes");
211
+ expect(escalatedPrompt).toContain("the overlay minimizes");
212
+ // No leg is taught a marker any more: the reveal is decided by whether a
213
+ // ui tool ran, never by anything the model emits.
210
214
  expect(frontDoorPrompt).not.toContain("[-1]");
211
- expect(escalatedPrompt).toContain("[-1]");
215
+ expect(escalatedPrompt).not.toContain("[-1]");
212
216
  });
213
217
 
214
218
  test("the verdict token and any text past the bridge cap never reach the transcript", async () => {
@@ -1207,8 +1207,16 @@ describe("LiveVoiceSession server VAD", () => {
1207
1207
  });
1208
1208
  // The barge-in utterance transcribes to nothing, so no follow-up turn
1209
1209
  // consumes the stash before the interrupt lands.
1210
+ //
1211
+ // "third question" is repeated because finals are handed out by transcriber
1212
+ // creation order, and the session pre-arms a transcriber for the next
1213
+ // utterance. The interrupt below can discard that pre-armed one and re-arm,
1214
+ // which shifts the final utterance onto the following index — with a
1215
+ // 3-entry list it would fall off the end and transcribe to the "utterance
1216
+ // N" placeholder, so the wait for "third question" would time out rather
1217
+ // than fail on the assertion. Both landing spots carry the same text.
1210
1218
  const { frames, session } = createHarness({
1211
- finals: ["first question", "", "third question"],
1219
+ finals: ["first question", "", "third question", "third question"],
1212
1220
  startVoiceTurn,
1213
1221
  streamTtsAudio,
1214
1222
  spawnBackgroundContinuation,
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Turns a tool call into one short user-facing line for the surfaces that show
3
+ * a running voice session.
4
+ *
5
+ * ## Why the copy lives here
6
+ *
7
+ * The iOS Live Activity has two drivers that must carry identical content: the
8
+ * live-voice socket, and an APNs push the daemon dispatches when the app is
9
+ * backgrounded and the web layer is suspended. Only one of them runs web code,
10
+ * so a label composed client-side could not be reproduced by the other. Both
11
+ * are therefore handed the same string, and this is where it is made.
12
+ *
13
+ * That does not contradict the rule that phase wording belongs to the web
14
+ * layer. The reason phase copy lives there is cadence: the iOS shell ships on
15
+ * App Store review while the web bundle deploys continuously, so a native
16
+ * `switch` would fossilize. This module deploys with the daemon, i.e.
17
+ * continuously, and it is the only layer that knows a tool ran at all.
18
+ *
19
+ * ## What makes a good label
20
+ *
21
+ * It is read on a Lock Screen, in a glance, by someone who is mid-conversation
22
+ * and not looking for detail. So: a present-participle phrase, no tool names,
23
+ * no arguments, no punctuation, and short enough to survive one line. Arguments
24
+ * are deliberately absent even though {@link summarizeToolInput} could supply
25
+ * them: a path or a URL on a Lock Screen is unreadable at a glance and can
26
+ * carry content the user would not choose to show a passer-by.
27
+ */
28
+
29
+ /**
30
+ * The tools that put something on screen worth looking at.
31
+ *
32
+ * `app_open` belongs here with the ui-surface tools: it proxies to a connected
33
+ * client to bring an app up, which is the same thing from the user's side, and
34
+ * a list keyed on "ui_" would have missed it.
35
+ *
36
+ * `ui_dismiss` is deliberately absent, and is handled as its own case: it
37
+ * retires a surface, and revealing the screen to show the user something that
38
+ * is no longer there is the opposite of the point.
39
+ */
40
+ const UI_REVEAL_TOOLS: ReadonlySet<string> = new Set([
41
+ "ui_show",
42
+ "ui_update",
43
+ "app_open",
44
+ ]);
45
+
46
+ /** The tool that takes a surface away again. */
47
+ const UI_DISMISS_TOOL = "ui_dismiss";
48
+
49
+ /**
50
+ * Whether running `toolName` leaves something on screen the user should be
51
+ * shown, which is what makes a live-voice turn reveal the screen behind the
52
+ * call overlay.
53
+ */
54
+ export function revealsUiSurface(toolName: string): boolean {
55
+ return UI_REVEAL_TOOLS.has(toolName);
56
+ }
57
+
58
+ /** Whether `toolName` takes a surface off the screen. */
59
+ export function dismissesUiSurface(toolName: string): boolean {
60
+ return toolName === UI_DISMISS_TOOL;
61
+ }
62
+
63
+ /**
64
+ * Present-participle phrases for the built-in tools, by exact name.
65
+ *
66
+ * Grouped by what the user would say is happening, not by how the tool is
67
+ * implemented: `bash` and `host_bash` differ in where they run, which is not a
68
+ * distinction anyone glancing at a Lock Screen is making.
69
+ */
70
+ const TOOL_ACTIVITY_LABELS: Readonly<Record<string, string>> = {
71
+ bash: "Running a command",
72
+ terminal: "Running a command",
73
+ host_bash: "Running a command",
74
+ file_read: "Reading a file",
75
+ host_file_read: "Reading a file",
76
+ file_write: "Writing a file",
77
+ host_file_write: "Writing a file",
78
+ file_edit: "Editing a file",
79
+ host_file_edit: "Editing a file",
80
+ host_file_transfer: "Moving a file",
81
+ file_search: "Searching files",
82
+ web_search: "Searching the web",
83
+ web_fetch: "Reading a page",
84
+ network_request: "Making a request",
85
+ browser: "Using the browser",
86
+ computer_use: "Using the computer",
87
+ memory: "Checking memory",
88
+ schedule: "Checking the schedule",
89
+ ui_show: "Putting something on screen",
90
+ ui_update: "Putting something on screen",
91
+ app_open: "Opening an app",
92
+ };
93
+
94
+ /**
95
+ * Prefix-matched fallbacks, tried in order when a tool has no exact entry.
96
+ *
97
+ * The tool vocabulary is open: plugins, MCP servers, and skills all contribute
98
+ * names this module has never seen, and a new built-in should not have to be
99
+ * registered here to avoid a blank island. Prefixes cover the families whose
100
+ * names are structured; {@link GENERIC_ACTIVITY_LABEL} covers the rest.
101
+ */
102
+ const TOOL_ACTIVITY_LABEL_PREFIXES: ReadonlyArray<readonly [string, string]> = [
103
+ ["browser_", "Using the browser"],
104
+ ["computer_", "Using the computer"],
105
+ ["file_", "Working with files"],
106
+ ["host_file_", "Working with files"],
107
+ ["web_", "Looking something up"],
108
+ ["mcp_", "Using a connected app"],
109
+ ["skill_", "Running a skill"],
110
+ ["subagent", "Working on it"],
111
+ ];
112
+
113
+ /**
114
+ * What an unrecognized tool reads as.
115
+ *
116
+ * Deliberately vague rather than derived from the tool's name. A humanized
117
+ * `slack_conversations_history` is worse than "Working on it": it is longer
118
+ * than the slot, it leaks an internal vocabulary, and it reads like an error
119
+ * message. The label's job is to show the turn is alive and doing something,
120
+ * and this does that job for every tool that will ever exist.
121
+ */
122
+ export const GENERIC_ACTIVITY_LABEL = "Working on it";
123
+
124
+ /**
125
+ * The line to show while `toolName` runs.
126
+ *
127
+ * Total, by construction: every tool gets a label, because the alternative is
128
+ * a surface that goes blank precisely when the assistant is busiest.
129
+ */
130
+ export function activityLabelForTool(toolName: string): string {
131
+ const exact = TOOL_ACTIVITY_LABELS[toolName];
132
+ if (exact !== undefined) {
133
+ return exact;
134
+ }
135
+ for (const [prefix, label] of TOOL_ACTIVITY_LABEL_PREFIXES) {
136
+ if (toolName.startsWith(prefix)) {
137
+ return label;
138
+ }
139
+ }
140
+ return GENERIC_ACTIVITY_LABEL;
141
+ }
142
+
143
+ /**
144
+ * The line to show while the turn is *waiting* on the user for `toolName`,
145
+ * rather than running it.
146
+ *
147
+ * The distinction matters more here than anywhere else this module is read.
148
+ * `tool_use_start` fires before the approval gate blocks, so a surface that
149
+ * showed only {@link activityLabelForTool} would say "Running a command" for
150
+ * the whole time the turn was in fact doing nothing but waiting — the same
151
+ * misstatement the spoken narration was fixed to stop making. This is the
152
+ * island's version of that fix.
153
+ *
154
+ * It keeps the tool's own phrase and appends who is being waited on, because
155
+ * the alternative — a bare "Waiting for your approval" — asks the user to
156
+ * approve something the surface will not name. That phrase is as specific as
157
+ * the island's vocabulary gets: no tool names, no arguments, nothing a
158
+ * passer-by reading a Lock Screen should not see (see the module header).
159
+ * Anyone wanting the detail has the card itself, one tap away.
160
+ */
161
+ export function approvalActivityLabel(toolName: string): string {
162
+ // A confirmation raised outside the tool pipeline (a proxy or network
163
+ // prompter) has no tool to name, and "Working on it — needs your okay" would
164
+ // be a worse sentence than the bare one: the turn is not working.
165
+ if (toolName.length === 0) {
166
+ return "Needs your okay";
167
+ }
168
+ return `${activityLabelForTool(toolName)} — needs your okay`;
169
+ }
@@ -109,21 +109,45 @@ export function phaseForFrame(
109
109
  */
110
110
  export class LiveActivityReporter {
111
111
  private lastPhase: LiveActivityPhase | null = null;
112
+ private lastDetail = "";
112
113
  private ended = false;
113
114
 
114
115
  constructor(private readonly conversationId: string) {}
115
116
 
116
117
  /**
117
- * Report the phase a frame implies, if it changed. Fire-and-forget: callers
118
- * are on the session's send path and must not await this.
118
+ * Report what a frame implies about the phase or the activity line, if
119
+ * either changed. Fire-and-forget: callers are on the session's send path
120
+ * and must not await this.
121
+ *
122
+ * Both travel in one dispatch because they are two fields of one content
123
+ * state: ActivityKit replaces the whole thing per push, so a dispatch that
124
+ * carried only the field that moved would blank the other.
119
125
  */
120
126
  report(frame: LiveVoiceServerFramePayload): void {
127
+ if (this.ended) {
128
+ return;
129
+ }
121
130
  const phase = phaseForFrame(frame, this.lastPhase);
122
- if (phase === null || phase === this.lastPhase || this.ended) {
131
+ // The session sends this frame only on a change it wants surfaced, and
132
+ // sends an empty label when the turn stops working, so it is taken
133
+ // verbatim rather than derived.
134
+ const detail = frame.type === "activity" ? frame.label : this.lastDetail;
135
+ const phaseMoved = phase !== null && phase !== this.lastPhase;
136
+ if (!phaseMoved && detail === this.lastDetail) {
137
+ return;
138
+ }
139
+ if (phase !== null) {
140
+ this.lastPhase = phase;
141
+ }
142
+ this.lastDetail = detail;
143
+ // An activity line can arrive before any phase has been established (a
144
+ // turn whose first tool starts before the phase-bearing frame lands).
145
+ // There is no content state to attach it to yet, and the phase the next
146
+ // frame sets will carry it.
147
+ if (this.lastPhase === null) {
123
148
  return;
124
149
  }
125
- this.lastPhase = phase;
126
- void this.dispatch(phase, "update");
150
+ void this.dispatch(this.lastPhase, "update", this.lastDetail);
127
151
  }
128
152
 
129
153
  /**
@@ -139,13 +163,17 @@ export class LiveActivityReporter {
139
163
  return;
140
164
  }
141
165
  this.ended = true;
142
- void this.dispatch("ending", "end");
166
+ // No detail on the way out: whatever the turn was doing, it is not doing
167
+ // it any more, and this state is the one that lingers on the Lock Screen
168
+ // through the dismissal window.
169
+ void this.dispatch("ending", "end", "");
143
170
  }
144
171
 
145
172
  /** `protected` so a test can observe what would be sent without sending it. */
146
173
  protected async dispatch(
147
174
  phase: LiveActivityPhase,
148
175
  event: "update" | "end",
176
+ detail: string,
149
177
  ): Promise<void> {
150
178
  try {
151
179
  const client = await VellumPlatformClient.create();
@@ -162,6 +190,7 @@ export class LiveActivityReporter {
162
190
  conversation_id: this.conversationId,
163
191
  phase,
164
192
  event,
193
+ detail,
165
194
  }),
166
195
  });
167
196
  if (!response.ok) {