@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  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__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -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__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -27,9 +27,7 @@ import {
27
27
  resolveEffectiveContextWindow,
28
28
  } from "../config/llm-context-resolution.js";
29
29
  import {
30
- isOverrideOrDefaultResolutionEnabled,
31
30
  resolveCallSiteConfig,
32
- resolveDefaultProfileKey,
33
31
  resolveEffectiveProfileKey,
34
32
  resolveProfilelessModelKey,
35
33
  selectWinningProfile,
@@ -256,6 +254,16 @@ async function withAbortWatchdog<T>(
256
254
 
257
255
  // ── runAgentLoop ─────────────────────────────────────────────────────
258
256
 
257
+ /**
258
+ * Steering block appended to the tail user message on turns whose
259
+ * triggering message carries `messageSource: "nav_redirect"` — a UI
260
+ * shortcut sent the message on the user's behalf while the user was
261
+ * exploring the interface. Without it the model treats the canned prompt
262
+ * as a hand-typed question and answers with a capability tour.
263
+ */
264
+ const NAV_REDIRECT_STEERING_BLOCK =
265
+ "<system_reminder>\nThis message was sent by a UI shortcut while the user explored the interface, not typed by hand. Reply in a few sentences at most and end with one concrete, specific next step or question — do not enumerate capabilities or give a tour.\n</system_reminder>";
266
+
259
267
  export async function runAgentLoopImpl(
260
268
  ctx: Conversation,
261
269
  content: string,
@@ -272,6 +280,15 @@ export async function runAgentLoopImpl(
272
280
  * the turn.
273
281
  */
274
282
  isHiddenPrompt?: boolean;
283
+ /**
284
+ * How the triggering message was initiated when it was sent on the
285
+ * user's behalf by the UI rather than typed by hand (`body.source` on
286
+ * `POST /v1/messages`, persisted as `metadata.userMessageSource`).
287
+ * Current value: `"nav_redirect"`, which steers the turn toward a
288
+ * brief, concrete reply. Forwarded onto the user-prompt-submit hook
289
+ * context. Unset for hand-typed messages.
290
+ */
291
+ messageSource?: string;
275
292
  /**
276
293
  * LLM call-site identifier threaded into the per-call provider config.
277
294
  * Adapter callers (heartbeat, filing, scheduler, etc.) pass their own
@@ -367,7 +384,7 @@ export async function runAgentLoopImpl(
367
384
  // contexts (heartbeat, filing, analyze, etc.) pass their own `callSite`. The
368
385
  // provider layer resolves provider/model/maxTokens via `resolveCallSiteConfig`,
369
386
  // picking up any user overrides under `llm.callSites.<id>` (falling back to
370
- // `llm.default` when absent). `resolveTurnCallSite` keeps subagent
387
+ // the shipped call-site defaults when absent). `resolveTurnCallSite` keeps subagent
371
388
  // conversations on `subagentSpawn` when no call site is supplied.
372
389
  const turnCallSite = resolveTurnCallSite(options?.callSite, ctx);
373
390
  // Expose the turn's call site on the live conversation so the runtime
@@ -931,21 +948,16 @@ export async function runAgentLoopImpl(
931
948
  // `modelProfileKey` is the actual profile used for this turn. The
932
949
  // notice key is narrower: it only marks turns where runtime context should
933
950
  // remind the model that the profile changed.
934
- // Under override-or-default semantics the reported key must come from
935
- // the same winner selection dispatch used a hand-rolled chain would
936
- // credit profiles the resolver never consulted (e.g. activeProfile on a
937
- // non-mainAgent turn).
951
+ // The reported key must come from the same winner selection dispatch used —
952
+ // a hand-rolled chain would credit profiles the resolver never consulted
953
+ // (e.g. activeProfile on a non-mainAgent turn).
938
954
  const effectiveProfileKey =
939
- (isOverrideOrDefaultResolutionEnabled()
940
- ? selectWinningProfile(turnCallSite, config.llm, {
941
- ...(turnOverrideProfile != null
942
- ? { overrideProfile: turnOverrideProfile }
943
- : {}),
944
- selectionSeed: ctx.conversationId,
945
- }).profileName
946
- : (turnOverrideProfile ??
947
- config.llm.activeProfile ??
948
- resolveDefaultProfileKey("mainAgent", config.llm))) ??
955
+ selectWinningProfile(turnCallSite, config.llm, {
956
+ ...(turnOverrideProfile != null
957
+ ? { overrideProfile: turnOverrideProfile }
958
+ : {}),
959
+ selectionSeed: ctx.conversationId,
960
+ }).profileName ??
949
961
  resolveProfilelessModelKey(turnCallSite, config.llm, {
950
962
  ...(turnOverrideProfile != null
951
963
  ? { overrideProfile: turnOverrideProfile }
@@ -988,6 +1000,9 @@ export async function runAgentLoopImpl(
988
1000
  requestId: reqId,
989
1001
  prompt: options?.titleText ?? content,
990
1002
  isHiddenPrompt: options?.isHiddenPrompt === true,
1003
+ ...(options?.messageSource
1004
+ ? { messageSource: options.messageSource }
1005
+ : {}),
991
1006
  originalMessages: Object.freeze([...ctx.messages]),
992
1007
  latestMessages: ctx.messages,
993
1008
  modelProfileKey,
@@ -999,7 +1014,29 @@ export async function runAgentLoopImpl(
999
1014
  userPromptCtx,
1000
1015
  );
1001
1016
  latencyTracker.mark("prompt_hook_end");
1002
- const runMessages = finalUserPromptCtx.latestMessages;
1017
+ let runMessages = finalUserPromptCtx.latestMessages;
1018
+
1019
+ // UI-shortcut steering: appended after the hook chain so it lands below
1020
+ // the assembled runtime injections on the tail user message, mirroring
1021
+ // the non-interactive block's placement in `applyRuntimeInjections`.
1022
+ // Turn-scoped nudge, deliberately not persisted for byte-exact reload
1023
+ // rehydration — losing it on reload costs one prefix-cache anchor, not
1024
+ // context correctness.
1025
+ if (options?.messageSource === "nav_redirect") {
1026
+ const userTail = runMessages[runMessages.length - 1];
1027
+ if (userTail && userTail.role === "user") {
1028
+ runMessages = [
1029
+ ...runMessages.slice(0, -1),
1030
+ {
1031
+ ...userTail,
1032
+ content: [
1033
+ ...userTail.content,
1034
+ { type: "text" as const, text: NAV_REDIRECT_STEERING_BLOCK },
1035
+ ],
1036
+ },
1037
+ ];
1038
+ }
1039
+ }
1003
1040
 
1004
1041
  // Reset the manager's turn-scoped overflow-recovery ladder at the turn
1005
1042
  // boundary so a new turn starts the ladder fresh from the emergency rung.
@@ -1590,6 +1627,21 @@ export async function runAgentLoopImpl(
1590
1627
  publishLoopMessagesChanged();
1591
1628
  }
1592
1629
  } finally {
1630
+ // Clear the processing flag first. It is the release that frees the
1631
+ // conversation for its next turn, so nothing that can throw (the
1632
+ // turn-boundary commit, profiling) is allowed to run ahead of it and leave
1633
+ // the row latched "mid-turn". Stamp the turn's abnormal outcome first
1634
+ // because the telemetry reporter's settled-turn barrier only releases this
1635
+ // turn once processing stops; null `abortController` first so a concurrent
1636
+ // abort takes the force-clear branch rather than signalling a dead one.
1637
+ if (abnormalOutcome) {
1638
+ stampTurnOutcome(userMessageId, abnormalOutcome.outcome, {
1639
+ failureCode: abnormalOutcome.failureCode,
1640
+ });
1641
+ }
1642
+ ctx.abortController = null;
1643
+ ctx.setProcessing(false);
1644
+
1593
1645
  if (turnStarted) {
1594
1646
  ctx.turnCount++;
1595
1647
  const config = getConfig();
@@ -1625,24 +1677,12 @@ export async function runAgentLoopImpl(
1625
1677
 
1626
1678
  emitToolProfilingSummary(ctx.conversationId, reqId);
1627
1679
 
1628
- // Tear down this turn's per-turn state. Abort reliably drives the loop to
1680
+ // Tear down the remaining per-turn state. Abort reliably drives the loop to
1629
1681
  // this `finally` within a bounded time — cooperative signal propagation
1630
1682
  // (provider fetch + tool race) backed by the abort watchdog — so a
1631
1683
  // cancelled turn always unwinds before any resend can start a new one.
1632
1684
  // There is therefore only ever one turn alive, and clearing the shared
1633
1685
  // state below cannot clobber a concurrent turn.
1634
- // Stamp the turn's abnormal outcome (failed / cancelled) onto its
1635
- // user-message row BEFORE processing clears: the telemetry reporter's
1636
- // settled-turn barrier only releases this turn once the conversation
1637
- // stops processing, so ordering the stamp first guarantees the turn
1638
- // event ships with the outcome. A normally-replied turn stamps nothing.
1639
- if (abnormalOutcome) {
1640
- stampTurnOutcome(userMessageId, abnormalOutcome.outcome, {
1641
- failureCode: abnormalOutcome.failureCode,
1642
- });
1643
- }
1644
- ctx.abortController = null;
1645
- ctx.setProcessing(false);
1646
1686
  ctx.onConfirmationOutcome = undefined;
1647
1687
  ctx.surfaceActionRequestIds.delete(ctx.currentRequestId ?? "");
1648
1688
  ctx.approvedViaPromptThisTurn = false;
@@ -198,13 +198,21 @@ export interface ErrorContext {
198
198
  * recognized as cancellation too.
199
199
  */
200
200
  export function isUserCancellation(error: unknown, ctx: ErrorContext): boolean {
201
- if (!ctx.aborted) return false;
202
- if (error instanceof DOMException && error.name === "AbortError") return true;
203
- if (error instanceof Error && error.name === "AbortError") return true;
201
+ if (!ctx.aborted) {
202
+ return false;
203
+ }
204
+ if (error instanceof DOMException && error.name === "AbortError") {
205
+ return true;
206
+ }
207
+ if (error instanceof Error && error.name === "AbortError") {
208
+ return true;
209
+ }
204
210
  if (error instanceof ProviderError && isAbortReason(error.abortReason)) {
205
211
  return true;
206
212
  }
207
- if (isAbortReason(error)) return true;
213
+ if (isAbortReason(error)) {
214
+ return true;
215
+ }
208
216
  return false;
209
217
  }
210
218
 
@@ -215,7 +223,9 @@ const MAX_DEBUG_DETAIL_LENGTH = 4000;
215
223
  * Truncate debug details to a reasonable size for transport.
216
224
  */
217
225
  function truncateDebugDetails(details: string): string {
218
- if (details.length <= MAX_DEBUG_DETAIL_LENGTH) return details;
226
+ if (details.length <= MAX_DEBUG_DETAIL_LENGTH) {
227
+ return details;
228
+ }
219
229
  return details.slice(0, MAX_DEBUG_DETAIL_LENGTH) + "\n… (truncated)";
220
230
  }
221
231
 
@@ -287,7 +297,7 @@ export function classifyConversationError(
287
297
  }
288
298
 
289
299
  /**
290
- * Internal throw sites use sentinel pseudo-names (`<llm.default>`,
300
+ * Internal throw sites use sentinel pseudo-names (`<default>`,
291
301
  * `<resolved-callsite>`) when no real connection row is involved; those must
292
302
  * not render as literal connection names.
293
303
  */
@@ -351,7 +361,9 @@ function classifyCore(
351
361
  attribution,
352
362
  message,
353
363
  });
354
- if (c) return c;
364
+ if (c) {
365
+ return c;
366
+ }
355
367
  }
356
368
 
357
369
  // Reason-less (or bad_request/unknown) ProviderError with a status — the
@@ -493,9 +505,13 @@ function extractProviderDetail(message: string): string | undefined {
493
505
  detail = detail.replace(/\s*\[[^\]]*\]\s*$/, "").trim();
494
506
  if (!detail || detail === message.trim()) {
495
507
  // No recognizable prefix — only surface prose that isn't the whole raw line.
496
- if (/API error \(\d+\)/i.test(message)) return undefined;
508
+ if (/API error \(\d+\)/i.test(message)) {
509
+ return undefined;
510
+ }
511
+ }
512
+ if (!detail) {
513
+ return undefined;
497
514
  }
498
- if (!detail) return undefined;
499
515
  return detail.length > 200 ? `${detail.slice(0, 200)}…` : detail;
500
516
  }
501
517
 
@@ -768,19 +784,25 @@ function visionNotSupportedClassification(): Omit<
768
784
  * Build a user-facing message that names the exact profile / connection
769
785
  * to fix when one is known, falling back to a generic phrase otherwise.
770
786
  * Profile is preferred because that's the entity the user picks in the
771
- * chat picker; connection is shown when no profile is in play (e.g.
772
- * `llm.default` direct dispatch) or as a parenthetical when both differ.
787
+ * chat picker; connection is shown when no profile is in play (e.g. the
788
+ * profileless anchor dispatch) or as a parenthetical when both differ.
773
789
  */
774
790
  function describeAttribution(
775
791
  attribution: ConversationErrorAttribution | undefined,
776
792
  ): string {
777
- if (!attribution) return "";
793
+ if (!attribution) {
794
+ return "";
795
+ }
778
796
  const { profileName, connectionName } = attribution;
779
797
  if (profileName && connectionName && profileName !== connectionName) {
780
798
  return ` for profile "${profileName}" (connection "${connectionName}")`;
781
799
  }
782
- if (profileName) return ` for profile "${profileName}"`;
783
- if (connectionName) return ` for connection "${connectionName}"`;
800
+ if (profileName) {
801
+ return ` for profile "${profileName}"`;
802
+ }
803
+ if (connectionName) {
804
+ return ` for connection "${connectionName}"`;
805
+ }
784
806
  return "";
785
807
  }
786
808
 
@@ -23,6 +23,7 @@ import { extractPreferences } from "../notifications/preference-extractor.js";
23
23
  import { createPreference } from "../notifications/preferences-store.js";
24
24
  import {
25
25
  addMessage,
26
+ getUserMessageSourceMetadata,
26
27
  isHiddenMessageMetadata,
27
28
  provenanceFromTrustContext,
28
29
  recordConversationPersistedSeq,
@@ -1049,6 +1050,7 @@ async function drainSingleMessage(
1049
1050
  isUserMessage?: boolean;
1050
1051
  titleText?: string;
1051
1052
  isHiddenPrompt?: boolean;
1053
+ messageSource?: string;
1052
1054
  } = { isUserMessage: true };
1053
1055
  if (next.isInteractive !== undefined) {
1054
1056
  drainLoopOptions.isInteractive = next.isInteractive;
@@ -1059,6 +1061,10 @@ async function drainSingleMessage(
1059
1061
  if (isHiddenMessageMetadata(next.metadata)) {
1060
1062
  drainLoopOptions.isHiddenPrompt = true;
1061
1063
  }
1064
+ const queuedMessageSource = getUserMessageSourceMetadata(next.metadata);
1065
+ if (queuedMessageSource !== undefined) {
1066
+ drainLoopOptions.messageSource = queuedMessageSource;
1067
+ }
1062
1068
 
1063
1069
  conversation
1064
1070
  .runAgentLoop(agentLoopContent, userMessageId, {
@@ -1500,6 +1506,7 @@ async function drainBatch(
1500
1506
  isUserMessage?: boolean;
1501
1507
  titleText?: string;
1502
1508
  isHiddenPrompt?: boolean;
1509
+ messageSource?: string;
1503
1510
  } = { isUserMessage: true };
1504
1511
  // Source interactive flag from the last successfully-persisted sibling so
1505
1512
  // a trailing failed tail doesn't flip the agent loop's interactivity.
@@ -1519,6 +1526,21 @@ async function drainBatch(
1519
1526
  ) {
1520
1527
  drainLoopOptions.isHiddenPrompt = true;
1521
1528
  }
1529
+ // Same all-or-nothing rule for the send-source steering: a batch counts
1530
+ // as UI-initiated only when every message in it carries the same source
1531
+ // tag — one hand-typed message justifies a full answer.
1532
+ const batchMessageSource =
1533
+ successfulBatch.length > 0
1534
+ ? getUserMessageSourceMetadata(successfulBatch[0].metadata)
1535
+ : undefined;
1536
+ if (
1537
+ batchMessageSource !== undefined &&
1538
+ successfulBatch.every(
1539
+ (qm) => getUserMessageSourceMetadata(qm.metadata) === batchMessageSource,
1540
+ )
1541
+ ) {
1542
+ drainLoopOptions.messageSource = batchMessageSource;
1543
+ }
1522
1544
 
1523
1545
  // Fire-and-forget: runAgentLoop's finally block recursively calls drainQueue
1524
1546
  // when this run completes. Mirrors drainSingleMessage.
@@ -13,6 +13,12 @@
13
13
  */
14
14
 
15
15
  import { getConfig } from "../config/loader.js";
16
+ import {
17
+ ADOPTABLE_CONVERSATION_ID_RE,
18
+ createConversation,
19
+ ensureConversationExists,
20
+ getConversation,
21
+ } from "../persistence/conversation-crud.js";
16
22
  import { wrapWithCallSiteRouting } from "../providers/call-site-routing.js";
17
23
  import {
18
24
  mainAgentResolutionError,
@@ -157,6 +163,35 @@ export async function getOrCreateConversation(
157
163
  },
158
164
  );
159
165
  newConversation.updateClient(sendToClient, true);
166
+
167
+ // Ensure the conversations row exists before hydrating from DB.
168
+ // `getOrCreateConversation` builds the in-memory Conversation, but
169
+ // the persisted row is what `loadFromDb` reads for conversationType,
170
+ // source, and other metadata. If the row doesn't exist yet (brand-new
171
+ // conversation), create it now so hydration caches the right fields.
172
+ //
173
+ // When `conversationType` is provided (e.g. "background" for
174
+ // plugin-driven conversations), create the row with that type so it
175
+ // is hidden from the sidebar. The ID is validated against the same
176
+ // pattern as `ensureConversationExists` to prevent path traversal.
177
+ // Otherwise use `ensureConversationExists` directly, which validates
178
+ // and creates a standard row.
179
+ if (!getConversation(conversationId)) {
180
+ if (storedOptions?.conversationType) {
181
+ if (!ADOPTABLE_CONVERSATION_ID_RE.test(conversationId)) {
182
+ throw new Error(
183
+ `Refusing to adopt unsafe conversation id: ${JSON.stringify(conversationId)}`,
184
+ );
185
+ }
186
+ createConversation({
187
+ id: conversationId,
188
+ conversationType: storedOptions.conversationType,
189
+ });
190
+ } else {
191
+ ensureConversationExists(conversationId);
192
+ }
193
+ }
194
+
160
195
  await newConversation.loadFromDb();
161
196
  if (storedOptions?.assistantId) {
162
197
  newConversation.setAssistantId(storedOptions.assistantId);
@@ -11,7 +11,6 @@ import {
11
11
  getApp,
12
12
  getAppDirPath,
13
13
  getAppPreview,
14
- isMultifileApp,
15
14
  listAppsByConversation,
16
15
  resolveAppDir,
17
16
  resolveEffectiveAppHtml,
@@ -1825,6 +1824,17 @@ function completeSurfaceFromAction(
1825
1824
  markSurfaceCompleted(ctx, surfaceId, summary);
1826
1825
  }
1827
1826
 
1827
+ // One-shot interactive surfaces auto-complete once their action message is
1828
+ // accepted (they never accept further actions).
1829
+ const ONE_SHOT_SURFACE_TYPES = [
1830
+ "choice",
1831
+ "oauth_connect",
1832
+ "form",
1833
+ "confirmation",
1834
+ "file_upload",
1835
+ "task_preferences",
1836
+ ];
1837
+
1828
1838
  export async function handleSurfaceAction(
1829
1839
  ctx: SurfaceConversationContext,
1830
1840
  surfaceId: string,
@@ -2395,14 +2405,6 @@ export async function handleSurfaceAction(
2395
2405
  // One-shot interactive surfaces — auto-complete now that the message has
2396
2406
  // been accepted. Deferred until after rejection check so the surface stays
2397
2407
  // active and retryable if the queue was full.
2398
- const ONE_SHOT_SURFACE_TYPES = [
2399
- "choice",
2400
- "oauth_connect",
2401
- "form",
2402
- "confirmation",
2403
- "file_upload",
2404
- "task_preferences",
2405
- ];
2406
2408
  if (
2407
2409
  requestedCompletionSummary ||
2408
2410
  ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)
@@ -3459,22 +3461,20 @@ export async function surfaceProxyResolver(
3459
3461
  const storedPreview = getAppPreview(app.id);
3460
3462
  const { dirName } = resolveAppDir(app.id);
3461
3463
 
3462
- // For multifile TSX apps, auto-compile if dist is missing, then
3463
- // resolve HTML from compiled dist/index.html with inlined assets.
3464
- if (isMultifileApp(app)) {
3465
- const { existsSync } = await import("node:fs");
3466
- const { join } = await import("node:path");
3467
- const appDir = getAppDirPath(app.id);
3468
- const distIndex = join(appDir, "dist", "index.html");
3469
- if (!existsSync(distIndex)) {
3470
- const { compileApp } = await import("../bundler/app-compiler.js");
3471
- const result = await compileApp(appDir);
3472
- if (!result.ok) {
3473
- log.warn(
3474
- { appId, errors: result.errors },
3475
- "Auto-compile failed on app_open",
3476
- );
3477
- }
3464
+ // Auto-compile if dist is missing, then resolve HTML from compiled
3465
+ // dist/index.html with inlined assets.
3466
+ const { existsSync } = await import("node:fs");
3467
+ const { join } = await import("node:path");
3468
+ const appDir = getAppDirPath(app.id);
3469
+ const distIndex = join(appDir, "dist", "index.html");
3470
+ if (!existsSync(distIndex)) {
3471
+ const { compileApp } = await import("../bundler/app-compiler.js");
3472
+ const result = await compileApp(appDir);
3473
+ if (!result.ok) {
3474
+ log.warn(
3475
+ { appId, errors: result.errors },
3476
+ "Auto-compile failed on app_open",
3477
+ );
3478
3478
  }
3479
3479
  }
3480
3480
  const html = resolveEffectiveAppHtml(app);
@@ -16,6 +16,7 @@ import type { PermissionPrompter } from "../permissions/prompter.js";
16
16
  import type { SecretPrompter } from "../permissions/secret-prompter.js";
17
17
  import { getBindingByConversation } from "../persistence/external-conversation-store.js";
18
18
  import { getAllDefaultPluginNames } from "../plugins/defaults/main.js";
19
+ import { isActivationSession } from "../plugins/defaults/memory/activation-session-store.js";
19
20
  import { isPluginDisabled } from "../plugins/disabled-state.js";
20
21
  import type { Message, ToolDefinition } from "../providers/types.js";
21
22
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
@@ -50,6 +51,10 @@ import {
50
51
  type ToolContext,
51
52
  type ToolExecutionResult,
52
53
  } from "../tools/types.js";
54
+ import {
55
+ injectActivationMomentParam,
56
+ projectUiToolsForChannel,
57
+ } from "../tools/ui-surface/channel-variants.js";
53
58
  import { loadWorkspaceTools } from "../tools/workspace-tools/loader.js";
54
59
  import {
55
60
  resolveUsageAttribution,
@@ -194,10 +199,16 @@ export function createToolExecutor(
194
199
  const rejectNonAllowlistedTool = (
195
200
  toolName: string,
196
201
  ): ToolExecutionResult | null => {
202
+ const allowlist = ctx.subagentAllowedTools;
203
+ // Record any attempt at a tool outside the subagent's role allowlist — in
204
+ // both wire and execution gate modes — so the parent can be told what the
205
+ // subagent needed but lacked. Pure observation; the gating is below.
206
+ if (allowlist !== undefined && !allowlist.has(toolName)) {
207
+ ctx.subagentDeniedToolNames?.add(toolName);
208
+ }
197
209
  if (ctx.subagentToolGateMode !== "execution") {
198
210
  return null;
199
211
  }
200
- const allowlist = ctx.subagentAllowedTools;
201
212
  if (!allowlist || allowlist.has(toolName)) {
202
213
  return null;
203
214
  }
@@ -539,7 +550,7 @@ const CROSS_CLIENT_EXPOSED_CAPABILITIES = new Set<HostProxyCapability>([
539
550
  "host_browser",
540
551
  ]);
541
552
  // Tools that require a connected client but no specific host proxy capability.
542
- const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["app_open", "ask_question"]);
553
+ const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["ask_question"]);
543
554
  const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
544
555
 
545
556
  /**
@@ -822,11 +833,26 @@ export function createResolveToolsCallback(
822
833
  ? currentWorkspaceDefs.filter((d) => wireAllowlist.has(d.name))
823
834
  : currentWorkspaceDefs;
824
835
  const excluded = new Set(getConfig().tools.exclude);
825
- const allBaseDefs = [
826
- ...scopedCoreDefs,
827
- ...scopedWorkspaceDefs,
828
- ...scopedMcpDefs,
829
- ].filter((d) => !excluded.has(d.name));
836
+ // Swap UI surface tools for channel-appropriate variants (e.g. Slack's
837
+ // task_progress-only ui_show). Mirrors the pin handling in
838
+ // `isToolActiveForContext`: execution-gate-mode wakes pin channel
839
+ // capabilities to undefined, which resolves to the unprojected defs.
840
+ const channelForUiTools = ctx.toolContextPin
841
+ ? undefined
842
+ : ctx.channelCapabilities?.channel;
843
+ let allBaseDefs = projectUiToolsForChannel(
844
+ [...scopedCoreDefs, ...scopedWorkspaceDefs, ...scopedMcpDefs].filter(
845
+ (d) => !excluded.has(d.name),
846
+ ),
847
+ channelForUiTools,
848
+ );
849
+ // Activation-rail conversations carry the optional `activation_moment`
850
+ // telemetry param on ui_show. The marker is written before the first
851
+ // tool resolution (see `applyBootstrapTemplate` in system-prompt.ts), so
852
+ // the projected schema is stable for the conversation's lifetime.
853
+ if (isActivationSession(ctx.conversationId)) {
854
+ allBaseDefs = injectActivationMomentParam(allBaseDefs);
855
+ }
830
856
 
831
857
  const effectivePreactivated = [
832
858
  ...DEFAULT_PREACTIVATED_SKILL_IDS,
@@ -34,6 +34,10 @@ import {
34
34
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
35
35
  import { getConfig } from "../config/loader.js";
36
36
  import type { LLMCallSite, Speed } from "../config/schemas/llm.js";
37
+ import {
38
+ derefToolResultReReads,
39
+ postTurnTruncateToolResults,
40
+ } from "../context/post-turn-tool-result-truncation.js";
37
41
  import { PermissionPrompter } from "../permissions/prompter.js";
38
42
  import { SecretPrompter } from "../permissions/secret-prompter.js";
39
43
  import type { UserDecision } from "../permissions/types.js";
@@ -315,6 +319,14 @@ export class Conversation {
315
319
  /** @internal */ toolsDisabledDepth = 0;
316
320
  /** @internal */ preactivatedSkillIds?: string[];
317
321
  /** @internal */ subagentAllowedTools?: Set<string>;
322
+ /**
323
+ * Tool names a subagent attempted but that its role allowlist
324
+ * ({@link subagentAllowedTools}) denied. Recorded by the tool executor;
325
+ * surfaced to the parent in the terminal notification so it can re-spawn with
326
+ * a role that includes them. Ephemeral, never persisted.
327
+ * @internal
328
+ */
329
+ subagentDeniedToolNames = new Set<string>();
318
330
  /**
319
331
  * How {@link subagentAllowedTools} is enforced — see
320
332
  * {@link SubagentToolGateMode}. Set and restored alongside the allowlist
@@ -1258,7 +1270,38 @@ export class Conversation {
1258
1270
  "Repaired persisted history",
1259
1271
  );
1260
1272
  }
1261
- this.messages = repairedMessages;
1273
+
1274
+ // Recreate the post-turn tool-result view on the reloaded history. Tools
1275
+ // exempt from the result-time spool (file_read/host_file_read, web_fetch)
1276
+ // persist their full oversized content, while the pre-eviction in-memory
1277
+ // history carried the post-turn stubs — so a reload would otherwise feed
1278
+ // the provider the full content those turns already consumed. Re-running
1279
+ // the deterministic finalize passes (deref, then truncate — same order)
1280
+ // restores that byte-identical view (same stub bytes, same
1281
+ // `.tool-results/` paths), keeping the provider prefix cache matching and
1282
+ // the rebuilt context as lean as it was before eviction/restart.
1283
+ // Best-effort like the finalize pass: a failure degrades to full-content
1284
+ // history, never a failed load.
1285
+ let messagesForHistory = repairedMessages;
1286
+ if (conv) {
1287
+ try {
1288
+ messagesForHistory = postTurnTruncateToolResults(
1289
+ derefToolResultReReads(repairedMessages).messages,
1290
+ {
1291
+ conversationDir: getResolvedConversationDirPath(
1292
+ this.conversationId,
1293
+ conv.createdAt,
1294
+ ),
1295
+ },
1296
+ ).messages;
1297
+ } catch (err) {
1298
+ log.warn(
1299
+ { conversationId: this.conversationId, err },
1300
+ "Load-time tool result truncation failed (non-fatal)",
1301
+ );
1302
+ }
1303
+ }
1304
+ this.messages = messagesForHistory;
1262
1305
 
1263
1306
  if (contextSummaryForHistory) {
1264
1307
  this.messages.unshift(
@@ -1465,10 +1508,10 @@ export class Conversation {
1465
1508
  * Mutate the server-authoritative `processing` flag. Web/Capacitor/CLI
1466
1509
  * caches treat this flag as the source of truth for the avatar streaming
1467
1510
  * ring and thinking indicator, so the `true → false` clear must announce
1468
- * itself: the daemon flips it in the agent-loop `finally` (after an awaited
1469
- * turn-boundary commit), which is later than the user-visible terminal SSE
1470
- * events, and a racing metadata refetch can otherwise re-read the
1471
- * not-yet-cleared `true` and clobber the client's optimistic `false`.
1511
+ * itself: the daemon flips it in the agent-loop `finally`, which runs after
1512
+ * the user-visible terminal SSE events, and a racing metadata refetch can
1513
+ * otherwise re-read the not-yet-cleared `true` and clobber the client's
1514
+ * optimistic `false`.
1472
1515
  *
1473
1516
  * Emitting a metadata invalidation on the clear lets every client GET the
1474
1517
  * authoritative `false`, per the multi-client-sync contract in AGENTS.md
@@ -1976,6 +2019,7 @@ export class Conversation {
1976
2019
  }
1977
2020
  return await this.runCompaction(true, undefined, {
1978
2021
  fixedTailStartIndex: tailIndex,
2022
+ fixedBoundaryRowIndex: boundaryRowIndex,
1979
2023
  // Slack projections gate on the persisted watermark, not on
1980
2024
  // `contextCompactedMessageCount` — without an advance, the summarized
1981
2025
  // rows would reappear verbatim in the projection alongside the new
@@ -2027,6 +2071,8 @@ export class Conversation {
2027
2071
  * `/compact`); without it the manager no-ops below the threshold.
2028
2072
  * `opts.fixedTailStartIndex` pins the kept tail to a caller-chosen history
2029
2073
  * index ("summarize up to here") instead of the token-budget cut;
2074
+ * `opts.fixedBoundaryRowIndex` is the same boundary in row space, which
2075
+ * bounds the compactor's image manifest to rows being summarized away;
2030
2076
  * `opts.fixedBoundarySlackWatermarkTs` is that path's row-space-derived
2031
2077
  * Slack watermark (the auto/forced Slack path derives its own from the
2032
2078
  * chronological projection).
@@ -2036,6 +2082,7 @@ export class Conversation {
2036
2082
  sizing?: CompactionSizing,
2037
2083
  opts?: {
2038
2084
  fixedTailStartIndex?: number;
2085
+ fixedBoundaryRowIndex?: number;
2039
2086
  fixedBoundarySlackWatermarkTs?: string | null;
2040
2087
  },
2041
2088
  ): Promise<ContextWindowResult> {
@@ -2097,6 +2144,7 @@ export class Conversation {
2097
2144
  overrideProfile,
2098
2145
  actorTrustClass: this.trustContext?.trustClass,
2099
2146
  fixedTailStartIndex: opts?.fixedTailStartIndex,
2147
+ fixedBoundaryRowIndex: opts?.fixedBoundaryRowIndex,
2100
2148
  });
2101
2149
  // Track circuit-breaker state for every compaction that ran a summary
2102
2150
  // call — user-initiated `/compact`, other forced paths, and the wake's
@@ -2316,6 +2364,8 @@ export class Conversation {
2316
2364
  titleText?: string;
2317
2365
  /** See {@link runAgentLoopImpl} — hidden machine-signal turn marker. */
2318
2366
  isHiddenPrompt?: boolean;
2367
+ /** See {@link runAgentLoopImpl} — UI-initiated send-source tag. */
2368
+ messageSource?: string;
2319
2369
  callSite?: LLMCallSite;
2320
2370
  /**
2321
2371
  * Optional ad-hoc inference-profile override applied to every LLM call