@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
@@ -33,7 +33,13 @@ const log = getLogger("diagnostics-routes");
33
33
  // Dictation
34
34
  // ---------------------------------------------------------------------------
35
35
 
36
- type DictationMode = "dictation" | "command" | "action";
36
+ /**
37
+ * `command` is a selection changed by the words, `question` a selection the
38
+ * words asked about. Both need selected text; which one comes back is the
39
+ * model's reading of the words, so a client that holds a key over a passage
40
+ * and says "what does this mean" is not handed a rewrite of it.
41
+ */
42
+ type DictationMode = "dictation" | "command" | "action" | "question";
37
43
 
38
44
  const DICTATION_CLASSIFICATION_TIMEOUT_MS = 5000;
39
45
  const MAX_WINDOW_TITLE_LENGTH = 100;
@@ -65,61 +71,47 @@ function buildCombinedDictationPrompt(
65
71
  body: DictationBody,
66
72
  stylePrompt?: string,
67
73
  ): string {
74
+ // Every line here is read on every hold, so the prompt says each thing
75
+ // once and leaves the model no reasoning to write.
68
76
  const sections = [
69
- "You are a voice input assistant. You will receive a speech transcription and must:",
70
- '1. Classify it as "dictation" (text to insert) or "action" (task for an assistant to execute)',
71
- "2. If dictation, clean up the text. If action, return the raw transcription.",
77
+ "You are a voice input assistant. Given a speech transcription, classify it and, if it is dictation, clean it up.",
72
78
  "",
73
79
  "## Classification",
74
- 'DICTATION examples: "Hey how are you doing", "I think we should move forward with the proposal", "Dear team comma please review the attached document"',
75
- 'ACTION examples: "Message Aaron on Slack saying hey what\'s up", "Send an email to the team about the meeting", "Open Spotify and play my playlist", "Search for flights to Denver", "Create a new document in Google Docs"',
76
- "",
77
- "Key signals for ACTION: the user is addressing an assistant and asking it to DO something (send, message, open, search, create, schedule, etc.)",
78
- "Key signals for DICTATION: the user is composing text content that should be typed out as-is",
79
- `Cursor in text field: ${body.context.cursorInTextField ? "yes" : "no"} -- if yes, lean toward dictation unless the intent to command is clear.`,
80
+ "dictation: the user is composing text to be typed as-is.",
81
+ "action: the user is asking an assistant to do something (send, message, open, search, create, schedule). Return the transcription unchanged.",
82
+ `Cursor in text field: ${body.context.cursorInTextField ? "yes" : "no"}. If yes, lean toward dictation unless the intent to command is clear.`,
80
83
  "",
81
- "## Cleanup Rules (for dictation mode only)",
82
- "- Fix grammar, punctuation, and capitalization",
83
- "- Remove filler words (um, uh, like, you know)",
84
- '- Rewrite vague or hedging language ("so yeah probably", "I guess maybe") into clear, confident statements',
85
- "- Maintain the speaker's intent and meaning",
84
+ "## Cleanup",
85
+ "- Fix grammar, punctuation and capitalization; remove filler words",
86
+ "- Rewrite hedging into clear statements, keeping the speaker's meaning",
87
+ "- When the speaker enumerates items, lay them out as a list, one per line",
88
+ "- Keep the user's natural voice; do not over-formalize casual speech",
86
89
  ];
87
90
 
88
91
  if (stylePrompt) {
89
92
  sections.push(
90
93
  "",
91
- "## User Style (HIGHEST PRIORITY)",
92
- "The user has configured these style preferences. They OVERRIDE the default tone adaptation below.",
93
- "Follow these instructions precisely -- they reflect the user's personal writing voice and preferences.",
94
+ "## User Style (highest priority)",
95
+ "The user's own writing preferences. They override the tone guidance below.",
94
96
  "",
95
97
  stylePrompt,
96
98
  );
97
99
  }
98
100
 
99
- sections.push("", "## Tone Adaptation");
100
-
101
- if (stylePrompt) {
102
- sections.push(
103
- "Use these as fallback guidance only when the User Style above does not cover a specific aspect:",
104
- );
105
- } else {
106
- sections.push("Adapt your output tone based on the active application:");
107
- }
108
-
109
101
  sections.push(
110
- "- Email apps (Gmail, Mail): Professional but warm. Use proper greetings and sign-offs if appropriate.",
111
- "- Slack: Casual and conversational. Match typical chat style.",
112
- "- Code editors (VS Code, Xcode): Technical and concise. Code comments style.",
113
- "- Terminal: Command-like, terse.",
114
- "- Messages/iMessage: Very casual, texting style. Short sentences.",
115
- "- Notes/Docs: Neutral, clear writing.",
116
- "- Default: Match the user's natural voice.",
117
102
  "",
118
- "## Context Clues",
119
- "- Window title may contain recipient name (Slack DMs, email compose)",
120
- "- If you can identify a recipient, adapt formality to the apparent relationship",
121
- "- Maintain the user's natural voice -- don't over-formalize casual speech",
122
- "- The user's writing patterns and preferences may be available from memory context -- follow those when present",
103
+ "## Tone",
104
+ stylePrompt
105
+ ? "Fallback guidance where the User Style above is silent:"
106
+ : "Adapt tone to the active application:",
107
+ "- Email: professional but warm, greetings and sign-offs where they fit",
108
+ "- Slack: casual and conversational",
109
+ "- Code editors: technical and concise",
110
+ "- Terminal: terse, command-like",
111
+ "- Messages: very casual, short sentences",
112
+ "- Notes and docs: neutral, clear writing",
113
+ "- Otherwise: the user's natural voice",
114
+ "The window title may name the recipient; adapt formality to the apparent relationship.",
123
115
  "",
124
116
  buildAppMetadataBlock(body.context),
125
117
  );
@@ -129,12 +121,12 @@ function buildCombinedDictationPrompt(
129
121
 
130
122
  function buildCommandPrompt(body: DictationBody, stylePrompt?: string): string {
131
123
  const sections = [
132
- "You are a text transformation assistant. The user has selected text and given a voice command to transform it.",
124
+ "You are a text transformation assistant. The user has selected text and spoken. The words are either an instruction to change the selected text or a question about it.",
133
125
  "",
134
126
  "## Rules",
135
- "- Apply the instruction to the selected text",
136
- "- Return ONLY the transformed text, nothing else",
137
- "- Do NOT add explanations or commentary",
127
+ "- If the words ask for a changed version of the selected text (rewrite, shorten, translate, fix, make it lighter, turn it into a list), kind is edit and text is ONLY the transformed text, nothing else",
128
+ "- If the words ask about the selected text rather than for a changed version of it (what does this mean, is this right, who said this, summarize it for me), kind is answer and text is empty",
129
+ "- Do NOT add explanations or commentary to an edit",
138
130
  ];
139
131
 
140
132
  if (stylePrompt) {
@@ -233,6 +225,9 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
233
225
  // Non-command: single LLM call that classifies AND cleans in one shot
234
226
  const transcription = expandSnippets(body.transcription, profile.snippets);
235
227
 
228
+ // Covers provider resolution as well as the call, which is what the caller
229
+ // waits for.
230
+ const modelStartedAt = Date.now();
236
231
  try {
237
232
  const provider = await getConfiguredProvider("interactionClassifier");
238
233
  if (!provider) {
@@ -289,12 +284,8 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
289
284
  description:
290
285
  "If dictation: the cleaned/formatted text ready for insertion. If action: the raw transcription unchanged.",
291
286
  },
292
- reasoning: {
293
- type: "string",
294
- description: "Brief reasoning for the classification",
295
- },
296
287
  },
297
- required: ["mode", "text", "reasoning"],
288
+ required: ["mode", "text"],
298
289
  },
299
290
  },
300
291
  ],
@@ -317,12 +308,16 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
317
308
  const input = toolBlock.input as {
318
309
  mode?: string;
319
310
  text?: string;
320
- reasoning?: string;
321
311
  };
322
312
  const mode: DictationMode =
323
313
  input.mode === "action" ? "action" : "dictation";
324
314
  log.info(
325
- { mode, reasoning: input.reasoning },
315
+ {
316
+ mode,
317
+ modelMs: Date.now() - modelStartedAt,
318
+ inChars: transcription.length,
319
+ outChars: input.text?.length ?? 0,
320
+ },
326
321
  "LLM dictation classify+clean",
327
322
  );
328
323
 
@@ -343,14 +338,17 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
343
338
  };
344
339
  }
345
340
 
346
- log.warn("No tool_use block in combined dictation call, using heuristic");
341
+ log.warn(
342
+ { modelMs: Date.now() - modelStartedAt },
343
+ "No tool_use block in combined dictation call, using heuristic",
344
+ );
347
345
  } finally {
348
346
  cleanup();
349
347
  }
350
348
  } catch (err) {
351
349
  const message = err instanceof Error ? err.message : String(err);
352
350
  log.warn(
353
- { err: message },
351
+ { err: message, modelMs: Date.now() - modelStartedAt },
354
352
  "Combined dictation LLM call failed, using heuristic",
355
353
  );
356
354
  }
@@ -407,20 +405,69 @@ async function handleCommandMode(
407
405
  };
408
406
  }
409
407
 
408
+ const modelStartedAt = Date.now();
410
409
  const response = await provider.sendMessage(
411
410
  [userMessage(body.transcription)],
412
411
  {
413
- tools: [],
412
+ tools: [
413
+ {
414
+ name: "transform_selection",
415
+ description:
416
+ "Return the selected text changed as instructed, or say the words were a question about it",
417
+ input_schema: {
418
+ type: "object" as const,
419
+ properties: {
420
+ kind: {
421
+ type: "string",
422
+ enum: ["edit", "answer"],
423
+ description:
424
+ "edit = the words ask for a changed version of the selected text. answer = the words ask about the selected text and no changed version is wanted.",
425
+ },
426
+ text: {
427
+ type: "string",
428
+ description:
429
+ "If edit: the transformed text only, ready to replace the selection. If answer: empty.",
430
+ },
431
+ },
432
+ required: ["kind", "text"],
433
+ },
434
+ },
435
+ ],
414
436
  systemPrompt,
415
- config: { callSite: "interactionClassifier", max_tokens: maxTokens },
437
+ config: {
438
+ callSite: "interactionClassifier",
439
+ max_tokens: maxTokens,
440
+ tool_choice: { type: "tool" as const, name: "transform_selection" },
441
+ },
416
442
  },
417
443
  );
418
444
 
419
- const textBlock = response.content.find((b) => b.type === "text");
445
+ const toolBlock = extractToolUse(response);
446
+ const input = (toolBlock?.input ?? {}) as { kind?: string; text?: string };
447
+ const edited = input.text?.trim() ?? "";
448
+ const isQuestion =
449
+ input.kind === "answer" || (toolBlock !== undefined && !edited);
450
+ log.info(
451
+ {
452
+ mode: isQuestion ? "question" : "command",
453
+ modelMs: Date.now() - modelStartedAt,
454
+ inChars: inputLength,
455
+ outChars: edited.length,
456
+ },
457
+ "LLM selection transform",
458
+ );
459
+ if (isQuestion) {
460
+ return {
461
+ text: body.transcription,
462
+ mode: "question",
463
+ ...profileMeta,
464
+ };
465
+ }
466
+ // No tool block at all is a model that did not answer the question
467
+ // asked; the selection goes back unchanged, which a client reads as
468
+ // nothing to put in its place.
420
469
  const cleanedText =
421
- textBlock && "text" in textBlock
422
- ? textBlock.text.trim()
423
- : (body.context.selectedText ?? body.transcription);
470
+ edited || (body.context.selectedText ?? body.transcription);
424
471
  const normalizedText = applyDictionary(cleanedText, profile.dictionary);
425
472
  return {
426
473
  text: normalizedText,
@@ -461,7 +508,11 @@ export const ROUTES: RouteDefinition[] = [
461
508
  requestBody: DictationRequestSchema,
462
509
  responseBody: z.object({
463
510
  text: z.string().describe("Processed text output"),
464
- mode: z.string().describe("Detected mode: dictation, command, or action"),
511
+ mode: z
512
+ .string()
513
+ .describe(
514
+ "Detected mode: dictation, command, action, or question (selected text the words asked about rather than changed; text is the transcription)",
515
+ ),
465
516
  actionPlan: z
466
517
  .string()
467
518
  .optional()
@@ -18,6 +18,7 @@ import {
18
18
  listPendingRequestsByScope,
19
19
  } from "../../channels/gateway-guardian-requests.js";
20
20
  import { isHttpAuthDisabled } from "../../config/env.js";
21
+ import { buildAccessRequestInviteDirective } from "../../notifications/access-request-copy.js";
21
22
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
22
23
  import { processGuardianDecision } from "../guardian-action-service.js";
23
24
  import type { GuardianDecisionPrompt } from "../guardian-decision-types.js";
@@ -196,6 +197,12 @@ function mapRequestToPrompt(
196
197
  };
197
198
  }
198
199
 
200
+ /**
201
+ * The prompt's text is the ask. Its `actions` carry the decision, so a
202
+ * typed decision directive here would duplicate them on every client. An
203
+ * access request also carries the invite-flow sentence, because no action
204
+ * anywhere offers that flow and the sentence is the only way to start it.
205
+ */
199
206
  function buildKindAwareQuestionText(req: GuardianRequestWire): string {
200
207
  const baseText =
201
208
  req.questionText ??
@@ -204,20 +211,9 @@ function buildKindAwareQuestionText(req: GuardianRequestWire): string {
204
211
  ? `Approve tool: ${req.toolName} — ${req.activityText}`
205
212
  : `Approve tool: ${req.toolName}`
206
213
  : `Guardian request: ${req.kind}`);
207
-
208
- if (req.kind === "access_request") {
209
- const code = req.requestCode ?? req.id.slice(0, 6).toUpperCase();
210
- const lines = [baseText];
211
- lines.push(
212
- `\nReply "${code} approve" to grant access or "${code} reject" to deny.`,
213
- );
214
- lines.push(
215
- 'Reply "open invite flow" to start Trusted Contacts invite flow.',
216
- );
217
- return lines.join("\n");
218
- }
219
-
220
- return baseText;
214
+ return req.kind === "access_request"
215
+ ? `${baseText}\n${buildAccessRequestInviteDirective()}`
216
+ : baseText;
221
217
  }
222
218
 
223
219
  // ---------------------------------------------------------------------------
@@ -81,11 +81,6 @@ export async function handleApprovalInterception(
81
81
  approvalMessageId,
82
82
  } = params;
83
83
 
84
- // Slack emoji reactions are handled by the guardian decision
85
- // pipeline (`routeGuardianReply`), invoked from the inbound reaction stage:
86
- // it resolves the target request from the reacted card's delivery record.
87
- // See `guardian-reply-router.ts`.
88
-
89
84
  // ── Standard approval interception (existing flow) ──
90
85
  const pendingPrompt = getChannelApprovalPrompt(conversationId);
91
86
  if (!pendingPrompt) {