@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
@@ -12,6 +12,19 @@ export const VALID_STT_PROVIDERS = [
12
12
  "vellum",
13
13
  ] as const;
14
14
 
15
+ /**
16
+ * Forgiving aliases normalized to a canonical provider id before the enum
17
+ * check, so a natural value like `openai` or `whisper` is accepted rather than
18
+ * silently reset (which cascades into a full `services` section reset).
19
+ */
20
+ const STT_PROVIDER_ALIASES: Record<
21
+ string,
22
+ (typeof VALID_STT_PROVIDERS)[number]
23
+ > = {
24
+ openai: "openai-whisper",
25
+ whisper: "openai-whisper",
26
+ };
27
+
15
28
  /**
16
29
  * Sparse provider config map under `services.stt.providers`.
17
30
  *
@@ -33,11 +46,12 @@ export type SttProviders = z.infer<typeof SttProvidersSchema>;
33
46
  /**
34
47
  * Canonical STT service configuration.
35
48
  *
36
- * `mode` selects between the user's own provider key (`"your-own"`) and
37
- * Vellum-managed transcription billed to Vellum credits (`"managed"`).
38
- * The `provider` field always holds the user's BYOK choice so switching
39
- * back from managed mode restores it; use {@link effectiveSttProvider}
40
- * to resolve which provider is actually active.
49
+ * Managed transcription (through the user's Vellum account, billed to Vellum
50
+ * credits) is selectable two ways: `provider: "vellum"` directly, or
51
+ * `mode: "managed"` alongside a BYOK `provider` the form the mode toggle
52
+ * writes, which leaves the BYOK choice untouched so switching back to
53
+ * `"your-own"` restores it. Use {@link effectiveSttProvider} to resolve
54
+ * which provider is actually active.
41
55
  */
42
56
  export const SttServiceSchema = z
43
57
  .object({
@@ -50,22 +64,21 @@ export const SttServiceSchema = z
50
64
  'STT service mode -- "your-own" uses the configured provider with your API key; "managed" transcribes through your Vellum account',
51
65
  ),
52
66
  provider: z
53
- .enum(VALID_STT_PROVIDERS, {
54
- error: `services.stt.provider must be one of: ${VALID_STT_PROVIDERS.join(", ")}`,
55
- })
67
+ .preprocess(
68
+ (v) => {
69
+ if (typeof v !== "string") {
70
+ return v;
71
+ }
72
+ const k = v.trim().toLowerCase();
73
+ return STT_PROVIDER_ALIASES[k] ?? k;
74
+ },
75
+ z.enum(VALID_STT_PROVIDERS, {
76
+ error: `services.stt.provider must be one of: ${VALID_STT_PROVIDERS.join(", ")} (aliases: openai/whisper -> openai-whisper)`,
77
+ }),
78
+ )
56
79
  .describe("Active STT provider used for speech-to-text transcription"),
57
80
  providers: SttProvidersSchema.default({}),
58
81
  })
59
- .superRefine((service, ctx) => {
60
- if (service.provider === "vellum" && service.mode !== "managed") {
61
- ctx.addIssue({
62
- code: "custom",
63
- path: ["provider"],
64
- message:
65
- 'services.stt.provider "vellum" requires services.stt.mode "managed"',
66
- });
67
- }
68
- })
69
82
  .describe(
70
83
  "Speech-to-text service configuration -- provider selection and per-provider settings",
71
84
  );
@@ -75,13 +88,17 @@ export type SttService = z.infer<typeof SttServiceSchema>;
75
88
  /**
76
89
  * Resolve the provider that is actually active for the service config.
77
90
  *
78
- * Managed mode always routes to the `vellum` provider while leaving the
79
- * user's BYOK `provider` choice untouched, so toggling back to
80
- * `"your-own"` restores their previous setup.
91
+ * `provider: "vellum"` selects managed transcription directly. Otherwise
92
+ * `mode: "managed"` routes to `vellum` while leaving the user's BYOK
93
+ * `provider` choice untouched, so toggling back to `"your-own"` restores
94
+ * their previous setup.
81
95
  */
82
96
  export function effectiveSttProvider(service: {
83
97
  mode: SttService["mode"];
84
98
  provider: string;
85
99
  }): string {
100
+ if (service.provider === "vellum") {
101
+ return "vellum";
102
+ }
86
103
  return service.mode === "managed" ? "vellum" : service.provider;
87
104
  }
@@ -273,11 +273,12 @@ for (const id of schemaKeys) {
273
273
  /**
274
274
  * Canonical TTS service configuration.
275
275
  *
276
- * `mode` selects between the user's own provider key (`"your-own"`) and
277
- * Vellum-managed synthesis billed to Vellum credits (`"managed"`). The
278
- * `provider` field always holds the user's BYOK choice so switching back
279
- * from managed mode restores it; use {@link effectiveTtsProvider} to
280
- * resolve which provider is actually active.
276
+ * Managed synthesis (through the user's Vellum account, billed to Vellum
277
+ * credits) is selectable two ways: `provider: "vellum"` directly, or
278
+ * `mode: "managed"` alongside a BYOK `provider` the form the mode toggle
279
+ * writes, which leaves the BYOK choice untouched so switching back to
280
+ * `"your-own"` restores it. Use {@link effectiveTtsProvider} to resolve
281
+ * which provider is actually active.
281
282
  */
282
283
  export const TtsServiceSchema = z
283
284
  .object({
@@ -297,16 +298,6 @@ export const TtsServiceSchema = z
297
298
  .describe("Active TTS provider used for speech synthesis"),
298
299
  providers: TtsProvidersSchema.default(TtsProvidersSchema.parse({})),
299
300
  })
300
- .superRefine((service, ctx) => {
301
- if (service.provider === "vellum" && service.mode !== "managed") {
302
- ctx.addIssue({
303
- code: "custom",
304
- path: ["provider"],
305
- message:
306
- 'services.tts.provider "vellum" requires services.tts.mode "managed"',
307
- });
308
- }
309
- })
310
301
  .describe(
311
302
  "Text-to-speech service configuration — provider selection and per-provider settings",
312
303
  );
@@ -316,14 +307,18 @@ export type TtsService = z.infer<typeof TtsServiceSchema>;
316
307
  /**
317
308
  * Resolve the provider that is actually active for the service config.
318
309
  *
319
- * Managed mode always routes to the `vellum` provider while leaving the
320
- * user's BYOK `provider` choice untouched, so toggling back to
321
- * `"your-own"` restores their previous setup.
310
+ * `provider: "vellum"` selects managed synthesis directly. Otherwise
311
+ * `mode: "managed"` routes to `vellum` while leaving the user's BYOK
312
+ * `provider` choice untouched, so toggling back to `"your-own"` restores
313
+ * their previous setup.
322
314
  */
323
315
  export function effectiveTtsProvider(service: {
324
316
  mode: TtsService["mode"];
325
317
  provider?: string;
326
318
  }): string {
319
+ if (service.provider === "vellum") {
320
+ return "vellum";
321
+ }
327
322
  if (service.mode === "managed") {
328
323
  return "vellum";
329
324
  }
@@ -684,25 +684,26 @@ function discoverSkillDirectories(skillsDir: string): string[] {
684
684
  }
685
685
 
686
686
  /**
687
- * Whether `pluginDir` is a recognized installed plugin: it must carry a
688
- * parseable `package.json` whose `name` equals the directory name. This
689
- * mirrors the external plugin loader's recognition gate, which skips any
690
- * directory whose `manifest.name` does not match its directory name.
687
+ * Whether `pluginDir` carries a plugin manifest the runtime can load: a
688
+ * parseable `package.json` with a non-empty string `name`. This mirrors the
689
+ * external plugin loader (`buildPluginFromDir`), which builds a plugin from
690
+ * any such directory and derives the plugin's identity from `package.json`
691
+ * `name` — it imposes no match between that `name` and the directory name.
692
+ *
693
+ * The directory name is the install slug (a marketplace slug or a GitHub path
694
+ * leaf), which routinely differs from the plugin's own `package.json` `name`;
695
+ * requiring the two to match would silently drop the resident skills of every
696
+ * such plugin even though the runtime loads its hooks and tools fine.
691
697
  *
692
698
  * The caller is responsible for the missing-`package.json` case (it emits a
693
699
  * diagnostic warning); this function only judges a manifest that is present.
694
700
  */
695
- function isRecognizedPluginDir(pluginDir: string, dirName: string): boolean {
701
+ function hasLoadablePluginManifest(pluginDir: string): boolean {
696
702
  const manifestPath = join(pluginDir, "package.json");
697
703
  if (!existsSync(manifestPath)) return false;
704
+ let parsed: unknown;
698
705
  try {
699
- const parsed: unknown = JSON.parse(readFileSync(manifestPath, "utf-8"));
700
- return (
701
- typeof parsed === "object" &&
702
- parsed !== null &&
703
- "name" in parsed &&
704
- (parsed as { name: unknown }).name === dirName
705
- );
706
+ parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
706
707
  } catch (err) {
707
708
  log.warn(
708
709
  { err, manifestPath },
@@ -710,12 +711,19 @@ function isRecognizedPluginDir(pluginDir: string, dirName: string): boolean {
710
711
  );
711
712
  return false;
712
713
  }
714
+ return (
715
+ typeof parsed === "object" &&
716
+ parsed !== null &&
717
+ "name" in parsed &&
718
+ typeof (parsed as { name: unknown }).name === "string" &&
719
+ (parsed as { name: string }).name.length > 0
720
+ );
713
721
  }
714
722
 
715
723
  /**
716
724
  * Discover skills shipped on disk inside installed plugins. Each installed
717
725
  * plugin — a directory under `<workspaceDir>/plugins/` recognized by
718
- * {@link isRecognizedPluginDir} — may ship skills at `skills/<id>/SKILL.md`.
726
+ * {@link hasLoadablePluginManifest} — may ship skills at `skills/<id>/SKILL.md`.
719
727
  * Returned summaries are attributed to the owning plugin via their `owner`
720
728
  * descriptor (`{ kind: "plugin", id: <plugin dir name> }`).
721
729
  */
@@ -747,7 +755,7 @@ function discoverPluginResidentSkills(): SkillSummary[] {
747
755
  if (!existsSync(join(pluginDir, "package.json"))) {
748
756
  log.warn(
749
757
  { pluginDir },
750
- "Plugin directory is missing package.json — skipping; its skills will not be available. Add a package.json whose `name` matches the directory.",
758
+ "Plugin directory is missing package.json — skipping; its skills will not be available. Add a package.json with a `name`.",
751
759
  );
752
760
  continue;
753
761
  }
@@ -757,13 +765,15 @@ function discoverPluginResidentSkills(): SkillSummary[] {
757
765
  // tools, so its resident skills must not be loadable either.
758
766
  if (existsSync(join(pluginDir, ".disabled"))) continue;
759
767
 
760
- // Mirror the plugin loader's recognition gate: a directory is a real
761
- // installed plugin only if its `package.json` `name` matches the directory.
762
- // This rejects staging dirs and malformed/mismatched clones (e.g. an
763
- // un-adapted `caveman-installer`) that the loader itself would skip, so the
764
- // catalog never surfaces skills from a directory the runtime would refuse
765
- // to load.
766
- if (!isRecognizedPluginDir(pluginDir, entry.name)) continue;
768
+ // Mirror the plugin loader: a directory contributes its resident skills
769
+ // when it carries a loadable manifest (parseable `package.json` with a
770
+ // non-empty `name`). The manifest `name` need not equal the directory name
771
+ // the loader imposes no such match — so a plugin installed under a
772
+ // marketplace slug or GitHub path leaf that differs from its `package.json`
773
+ // `name` still surfaces its skills, matching the hooks and tools the
774
+ // runtime already loads from it. A parseable manifest still rejects staging
775
+ // dirs and malformed clones the loader would itself refuse.
776
+ if (!hasLoadablePluginManifest(pluginDir)) continue;
767
777
 
768
778
  const skillsDir = join(pluginDir, "skills");
769
779
  if (!existsSync(skillsDir)) continue;
@@ -82,10 +82,13 @@ function recordCompactionRequestLog(
82
82
  conversationId: string,
83
83
  response: ProviderResponse,
84
84
  provider: Provider,
85
- ): void {
86
- if (!response.rawRequest || !response.rawResponse) return;
85
+ ): string | null {
86
+ if (!response.rawRequest || !response.rawResponse) return null;
87
87
  try {
88
- recordRequestLog(
88
+ // Inserted unlinked (no message id) — user-initiated compaction flows
89
+ // (/compact, summarize-up-to) link the row to their result card after
90
+ // it persists, via `linkRequestLogsToMessage`.
91
+ return recordRequestLog(
89
92
  conversationId,
90
93
  JSON.stringify(response.rawRequest),
91
94
  JSON.stringify(response.rawResponse),
@@ -98,6 +101,7 @@ function recordCompactionRequestLog(
98
101
  { err, conversationId },
99
102
  "Failed to persist compaction LLM request log (non-fatal)",
100
103
  );
104
+ return null;
101
105
  }
102
106
  }
103
107
 
@@ -280,6 +284,14 @@ export interface CompactionRunArgs {
280
284
  * `[1, messages.length)` — otherwise the run returns `compacted: false`.
281
285
  */
282
286
  fixedTailStartIndex?: number;
287
+ /**
288
+ * Row-space twin of `fixedTailStartIndex`: index into the conversation's
289
+ * persisted rows of the first message kept verbatim. Bounds the image
290
+ * manifest to rows that are actually being summarized away — kept-tail
291
+ * images stay in context verbatim, so offering them for retention would
292
+ * only duplicate them. Only meaningful alongside `fixedTailStartIndex`.
293
+ */
294
+ fixedBoundaryRowIndex?: number;
283
295
  }
284
296
 
285
297
  export interface CompactionRunResult {
@@ -305,6 +317,14 @@ export interface CompactionRunResult {
305
317
  summaryCacheCreationInputTokens?: number;
306
318
  summaryCacheReadInputTokens?: number;
307
319
  summaryRawResponses?: unknown[];
320
+ /**
321
+ * `llm_request_logs.id` of this pass's compaction call, `null` when the
322
+ * row was not written (logging disabled, missing raw payloads, DB error).
323
+ * User-initiated flows (/compact, summarize-up-to) link the row to their
324
+ * persisted result card so the inspector attributes the call to the card
325
+ * instead of the unlinked-row recovery guessing an enclosing turn.
326
+ */
327
+ summaryRequestLogId?: string | null;
308
328
  summaryText: string;
309
329
  /** Inline structured pending state from the model's `<key_state>` block. */
310
330
  keyState?: string;
@@ -457,15 +477,23 @@ interface ManifestEntry {
457
477
  * `loadFromDb` applies when assembling history — so guardian-only images
458
478
  * never enter the manifest and therefore can never be retained back into
459
479
  * an untrusted actor's view.
480
+ *
481
+ * `endRowIndex` (exclusive, row-space) bounds the walk to rows before a
482
+ * caller-fixed compaction boundary — images in the kept tail survive
483
+ * verbatim and must not be offered for retention. The slice happens before
484
+ * the trust filter because the boundary indexes the full row list.
460
485
  */
461
486
  export function collectImageManifest(
462
487
  conversationId: string,
463
488
  actorTrustClass?: TrustClass,
489
+ endRowIndex?: number,
464
490
  ): ManifestEntry[] {
465
491
  const allRows = getMessages(conversationId);
492
+ const boundedRows =
493
+ endRowIndex != null ? allRows.slice(0, endRowIndex) : allRows;
466
494
  const rows = !resolveCapabilities(actorTrustClass).canAccessMemory
467
- ? filterMessagesForUntrustedActor(allRows)
468
- : allRows;
495
+ ? filterMessagesForUntrustedActor(boundedRows)
496
+ : boundedRows;
469
497
  const entries: ManifestEntry[] = [];
470
498
  for (const row of rows) {
471
499
  const atts = getAttachmentMetadataForMessage(row.id);
@@ -1096,10 +1124,12 @@ export async function runAssistantDrivenCompaction(
1096
1124
  // Build image manifest from the DB before invoking the model so the
1097
1125
  // instruction message carries a faithful picture of available images.
1098
1126
  // Filtered by actor trust so untrusted turns never see guardian-only
1099
- // attachments.
1127
+ // attachments, and bounded to pre-boundary rows on fixed-boundary runs
1128
+ // so kept-tail images are never offered for retention.
1100
1129
  const manifest = collectImageManifest(
1101
1130
  args.conversationId,
1102
1131
  args.actorTrustClass,
1132
+ fixedTailStartIndex != null ? args.fixedBoundaryRowIndex : undefined,
1103
1133
  );
1104
1134
  const manifestText = renderImageManifest(manifest);
1105
1135
  const instruction = buildInstructionMessage(
@@ -1159,7 +1189,11 @@ export async function runAssistantDrivenCompaction(
1159
1189
 
1160
1190
  // Persist the compaction LLM call into `llm_request_logs` with
1161
1191
  // `call_site = "compactionAgent"`. Non-fatal on DB error — see helper.
1162
- recordCompactionRequestLog(args.conversationId, response, args.provider);
1192
+ const summaryRequestLogId = recordCompactionRequestLog(
1193
+ args.conversationId,
1194
+ response,
1195
+ args.provider,
1196
+ );
1163
1197
 
1164
1198
  const rawText = extractTextFromResponse(response.content);
1165
1199
  const parsed = parseCompactionResult(rawText, {
@@ -1182,6 +1216,7 @@ export async function runAssistantDrivenCompaction(
1182
1216
  summaryCallSite: COMPACTION_CALL_SITE,
1183
1217
  summaryOverrideProfile: args.overrideProfile ?? null,
1184
1218
  summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
1219
+ summaryRequestLogId,
1185
1220
  summaryCalls: 1,
1186
1221
  };
1187
1222
  }
@@ -1387,6 +1422,7 @@ export async function runAssistantDrivenCompaction(
1387
1422
  summaryCallSite: COMPACTION_CALL_SITE,
1388
1423
  summaryOverrideProfile: args.overrideProfile ?? null,
1389
1424
  summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
1425
+ summaryRequestLogId,
1390
1426
  summaryCalls: 1,
1391
1427
  };
1392
1428
  }
@@ -1459,6 +1495,7 @@ export async function runAssistantDrivenCompaction(
1459
1495
  response.usage.cacheCreationInputTokens ?? 0,
1460
1496
  summaryCacheReadInputTokens: response.usage.cacheReadInputTokens ?? 0,
1461
1497
  summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
1498
+ summaryRequestLogId,
1462
1499
  summaryText: finalSummaryText,
1463
1500
  keyState: parsed.keyState,
1464
1501
  summaryFailed: false,
@@ -104,7 +104,9 @@ export function getToolResultFilePath(
104
104
  /**
105
105
  * Build the truncated stub that replaces a large tool result in context.
106
106
  * Preserves the first and last halves of TARGET_CHARS, with a middle marker
107
- * indicating how many tokens were omitted and where to find the full result.
107
+ * indicating how many tokens were omitted, where to find the full result, and
108
+ * how to page it back in — the marker is the model's only signal that the
109
+ * omitted content is recoverable at all.
108
110
  */
109
111
  export function buildTruncatedContent(
110
112
  original: string,
@@ -119,7 +121,7 @@ export function buildTruncatedContent(
119
121
  );
120
122
  const omittedChars = original.length - TARGET_CHARS;
121
123
  const estimatedTokens = Math.round(omittedChars / 4);
122
- return `${prefix}\n\n...(${estimatedTokens} tokens omitted ${TRUNCATION_MARKER} ${filePath})\n\n${suffix}`;
124
+ return `${prefix}\n\n...(${estimatedTokens} tokens omitted ${TRUNCATION_MARKER} ${filePath} — use file_read to view)\n\n${suffix}`;
123
125
  }
124
126
 
125
127
  /**
@@ -18,11 +18,24 @@ import {
18
18
  */
19
19
  const AX_TREE_TAG = "<ax-tree>";
20
20
 
21
+ /**
22
+ * Tools whose results are explicit, self-sized reads the model paginates
23
+ * itself: `web_fetch` takes `max_chars`/`start_index` and reports a
24
+ * "Character Window: X-Y of Z", so the model pages by character index against
25
+ * the window it requested. Stubbing such a result at result time hands the
26
+ * model ~a tenth of the window it just sized — and it keeps paging blind
27
+ * against content it never saw. Like the file-read tools, these explicit
28
+ * reads are honored in full for the turn that made them; the post-turn pass
29
+ * still truncates them at turn end, after the model has consumed the content.
30
+ */
31
+ export const RESULT_TIME_SPOOL_EXEMPT_TOOLS = new Set<string>(["web_fetch"]);
32
+
21
33
  /**
22
34
  * Whether a tool result is eligible for the result-time spool/stub pass: the
23
35
  * post-turn pass's shared rules plus the AX-tree exemption, minus the file-read
24
- * tools ({@link FILE_READ_TOOL_NAMES}). The file-read tools are the model's only
25
- * way to page spooled content back into context: stubbing a read of a
36
+ * tools ({@link FILE_READ_TOOL_NAMES}) and the explicit self-sized reads
37
+ * ({@link RESULT_TIME_SPOOL_EXEMPT_TOOLS}). The file-read tools are the model's
38
+ * only way to page spooled content back into context: stubbing a read of a
26
39
  * `.tool-results/` file would spool a fresh copy and hand back another stub, so
27
40
  * oversized content could never be read at all. Explicit reads are therefore
28
41
  * honored in full; the post-turn pass still truncates them at turn end, after
@@ -32,11 +45,19 @@ function isSpoolEligible(
32
45
  tr: ToolResultContent,
33
46
  toolName: string | undefined,
34
47
  ): boolean {
35
- if (!isTruncationEligible(tr, toolName)) return false;
36
- if (toolName !== undefined && FILE_READ_TOOL_NAMES.has(toolName)) {
48
+ if (!isTruncationEligible(tr, toolName)) {
49
+ return false;
50
+ }
51
+ if (
52
+ toolName !== undefined &&
53
+ (FILE_READ_TOOL_NAMES.has(toolName) ||
54
+ RESULT_TIME_SPOOL_EXEMPT_TOOLS.has(toolName))
55
+ ) {
56
+ return false;
57
+ }
58
+ if (tr.content.includes(AX_TREE_TAG)) {
37
59
  return false;
38
60
  }
39
- if (tr.content.includes(AX_TREE_TAG)) return false;
40
61
  return true;
41
62
  }
42
63
 
@@ -333,3 +333,51 @@ describe("mergeConsecutiveAssistantMessages", () => {
333
333
  expect(result).toHaveLength(3);
334
334
  });
335
335
  });
336
+
337
+ describe("system-card boundaries", () => {
338
+ const cardMeta = JSON.stringify({ messageKind: "system_card" });
339
+
340
+ test("a card never merges into the preceding assistant run", () => {
341
+ const { messages } = mergeConsecutiveAssistantMessages([
342
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
343
+ makeMsg(
344
+ "assistant",
345
+ JSON.stringify([{ type: "text", text: "**Conversation summarized**" }]),
346
+ { metadata: cardMeta },
347
+ ),
348
+ ]);
349
+ expect(messages).toHaveLength(2);
350
+ });
351
+
352
+ test("an assistant row never merges into a preceding card", () => {
353
+ const { messages } = mergeConsecutiveAssistantMessages([
354
+ makeMsg(
355
+ "assistant",
356
+ JSON.stringify([{ type: "text", text: "**Context Cleaned**" }]),
357
+ { metadata: cardMeta },
358
+ ),
359
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
360
+ ]);
361
+ expect(messages).toHaveLength(2);
362
+ });
363
+
364
+ test("findDisplayTurnEndIndex treats a card as a single-row turn", () => {
365
+ const rows = [
366
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "card" }]), {
367
+ metadata: cardMeta,
368
+ }),
369
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
370
+ ];
371
+ expect(findDisplayTurnEndIndex(rows, 0)).toBe(0);
372
+ });
373
+
374
+ test("findDisplayTurnEndIndex stops an assistant run before a card", () => {
375
+ const rows = [
376
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
377
+ makeMsg("assistant", JSON.stringify([{ type: "text", text: "card" }]), {
378
+ metadata: cardMeta,
379
+ }),
380
+ ];
381
+ expect(findDisplayTurnEndIndex(rows, 0)).toBe(0);
382
+ });
383
+ });
@@ -32,11 +32,22 @@
32
32
  */
33
33
 
34
34
  import type { MessageRow } from "../persistence/conversation-crud.js";
35
+ import { isSystemCardMessage } from "../persistence/conversation-crud.js";
35
36
  import type { ContentBlock } from "../providers/types.js";
36
37
  import { getLogger } from "../util/logger.js";
37
38
 
38
39
  const log = getLogger("message-consolidation");
39
40
 
41
+ /**
42
+ * True when a row is a daemon-authored system card (`messageKind:
43
+ * "system_card"` metadata). Cards are standalone display turns: they never
44
+ * fold into an adjacent assistant run and adjacent assistant rows never
45
+ * fold into them.
46
+ */
47
+ export function isSystemCardRow(msg: MessageRow): boolean {
48
+ return isSystemCardMessage(msg.role, msg.metadata);
49
+ }
50
+
40
51
  // ── Block predicates ────────────────────────────────────────────────
41
52
 
42
53
  function isToolResultType(type: string): boolean {
@@ -109,6 +120,10 @@ export function findDisplayTurnEndIndex(
109
120
  if (messages[startIdx]?.role !== "assistant") {
110
121
  return startIdx;
111
122
  }
123
+ // A system card is a single-row display turn — never spans neighbours.
124
+ if (isSystemCardRow(messages[startIdx]!)) {
125
+ return startIdx;
126
+ }
112
127
 
113
128
  let endIdx = startIdx;
114
129
  while (endIdx + 1 < messages.length) {
@@ -116,7 +131,7 @@ export function findDisplayTurnEndIndex(
116
131
  if (!next) {
117
132
  break;
118
133
  }
119
- if (next.role === "assistant") {
134
+ if (next.role === "assistant" && !isSystemCardRow(next)) {
120
135
  endIdx += 1;
121
136
  continue;
122
137
  }
@@ -282,10 +297,15 @@ export function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
282
297
 
283
298
  for (const msg of messages) {
284
299
  const lastIdx = result.length - 1;
300
+ // System cards stay standalone in both directions: a card never folds
301
+ // into the preceding assistant run, and the following assistant row
302
+ // never folds into a card.
285
303
  const isConsecutiveAssistant =
286
304
  msg.role === "assistant" &&
305
+ !isSystemCardRow(msg) &&
287
306
  lastIdx >= 0 &&
288
- result[lastIdx].role === "assistant";
307
+ result[lastIdx].role === "assistant" &&
308
+ !isSystemCardRow(result[lastIdx]);
289
309
 
290
310
  if (!isConsecutiveAssistant) {
291
311
  result.push(msg);
@@ -582,19 +582,12 @@ describe("isToolActiveForContext — ask_question macOS gating", () => {
582
582
  ).toBe(false);
583
583
  });
584
584
 
585
- test("other client-capability tools (app_open) are NOT affected by the macos gate", () => {
585
+ test("app_open is skill-owned and not client-capability gated", () => {
586
+ // The app-builder skill provides app_open; its executor degrades
587
+ // gracefully when no client is connected, so the context filter does
588
+ // not hide it even on clientless turns.
586
589
  expect(
587
- isToolActiveForContext(
588
- "app_open",
589
- makeCtx({
590
- hasNoClient: false,
591
- channelCapabilities: {
592
- channel: "macos",
593
- supportsDynamicUi: true,
594
- clientOS: "macos",
595
- },
596
- }),
597
- ),
590
+ isToolActiveForContext("app_open", makeCtx({ hasNoClient: true })),
598
591
  ).toBe(true);
599
592
  });
600
593
  });
@@ -15,7 +15,6 @@ import {
15
15
  getApp,
16
16
  getAppDirPath,
17
17
  getAppsDir,
18
- isMultifileApp,
19
18
  resolveAppIdByDirName,
20
19
  } from "../apps/app-store.js";
21
20
  import { compileApp } from "../bundler/app-compiler.js";
@@ -159,8 +158,8 @@ export class AppSourceWatcher {
159
158
  }
160
159
 
161
160
  /**
162
- * Handle a detected app source file change. Recompiles multifile apps and
163
- * refreshes surfaces across ALL conversations.
161
+ * Handle a detected app source file change. Recompiles the app and refreshes
162
+ * surfaces across ALL conversations.
164
163
  */
165
164
  function handleAppSourceChange(appId: string): void {
166
165
  const app = getApp(appId);
@@ -175,26 +174,21 @@ function handleAppSourceChange(appId: string): void {
175
174
  void updatePublishedAppDeployment(appId);
176
175
  };
177
176
 
178
- if (isMultifileApp(app)) {
179
- const appDir = getAppDirPath(appId);
180
- void compileApp(appDir)
181
- .then((result) => {
182
- if (!result.ok) {
183
- log.warn(
184
- { appId, errors: result.errors },
185
- "Recompile failed on app source change",
186
- );
187
- }
188
- doRefresh();
189
- })
190
- .catch((err) => {
191
- log.warn({ appId, err }, "Recompile threw on app source change");
192
- doRefresh();
193
- });
194
- return;
195
- }
196
-
197
- doRefresh();
177
+ const appDir = getAppDirPath(appId);
178
+ void compileApp(appDir)
179
+ .then((result) => {
180
+ if (!result.ok) {
181
+ log.warn(
182
+ { appId, errors: result.errors },
183
+ "Recompile failed on app source change",
184
+ );
185
+ }
186
+ doRefresh();
187
+ })
188
+ .catch((err) => {
189
+ log.warn({ appId, err }, "Recompile threw on app source change");
190
+ doRefresh();
191
+ });
198
192
  }
199
193
 
200
194
  /** Start watching app source directories for the life of the daemon. */