@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
@@ -4,6 +4,7 @@ const LIVE_VOICE_CLIENT_FRAME_TYPES = [
4
4
  "ptt_release",
5
5
  "interrupt",
6
6
  "end",
7
+ "update_config",
7
8
  ] as const;
8
9
 
9
10
  type LiveVoiceClientFrameType = (typeof LIVE_VOICE_CLIENT_FRAME_TYPES)[number];
@@ -78,8 +79,36 @@ export interface LiveVoiceClientStartFrame {
78
79
  * utterance boundaries and runs repeated utterance→turn cycles.
79
80
  */
80
81
  readonly turnDetection?: LiveVoiceTurnDetectionMode;
82
+ /**
83
+ * Per-session override for the trailing-silence duration (ms) that ends the
84
+ * user's turn — the "pause before reply" the client exposes as a setting.
85
+ * Absent falls back to the daemon `liveVoice.vad.silenceThresholdMs` config.
86
+ * Only meaningful for `turnDetection: "server_vad"`. Bounded to
87
+ * [{@link MIN_SILENCE_THRESHOLD_MS}, {@link MAX_SILENCE_THRESHOLD_MS}].
88
+ */
89
+ readonly silenceThresholdMs?: number;
90
+ /**
91
+ * Per-session override for the sustained speech (ms) required before the
92
+ * user's speech interrupts the assistant mid-reply — the "interrupt
93
+ * sensitivity" the client exposes as a setting (higher = harder to
94
+ * interrupt; 0 disables the guard so barge-in is immediate). Absent falls
95
+ * back to the daemon `liveVoice.vad.bargeInMinSpeechMs` config. Bounded to
96
+ * [{@link MIN_BARGE_IN_MIN_SPEECH_MS}, {@link MAX_BARGE_IN_MIN_SPEECH_MS}].
97
+ */
98
+ readonly bargeInMinSpeechMs?: number;
81
99
  }
82
100
 
101
+ /**
102
+ * Bounds for the per-session turn-detection overrides carried on the start
103
+ * frame. Kept deliberately wide — they only reject nonsensical values (a
104
+ * sub-100 ms pause would end turns mid-word; a 10 s barge-in guard would make
105
+ * the assistant uninterruptible). The daemon config defaults sit inside these.
106
+ */
107
+ export const MIN_SILENCE_THRESHOLD_MS = 100;
108
+ export const MAX_SILENCE_THRESHOLD_MS = 5_000;
109
+ export const MIN_BARGE_IN_MIN_SPEECH_MS = 0;
110
+ export const MAX_BARGE_IN_MIN_SPEECH_MS = 3_000;
111
+
83
112
  export interface LiveVoiceClientAudioFrame {
84
113
  readonly type: "audio";
85
114
  readonly dataBase64: string;
@@ -97,12 +126,26 @@ export interface LiveVoiceClientEndFrame {
97
126
  readonly type: "end";
98
127
  }
99
128
 
129
+ /**
130
+ * Mid-session tuning update: applies the same turn-detection knobs the start
131
+ * frame carries to the *running* session, so the client can retune "pause
132
+ * before reply" / "interrupt sensitivity" without reconnecting. Each field is
133
+ * optional and independently applied; the same bounds as the start frame apply.
134
+ * Only meaningful for `server_vad` sessions.
135
+ */
136
+ export interface LiveVoiceClientUpdateConfigFrame {
137
+ readonly type: "update_config";
138
+ readonly silenceThresholdMs?: number;
139
+ readonly bargeInMinSpeechMs?: number;
140
+ }
141
+
100
142
  export type LiveVoiceClientFrame =
101
143
  | LiveVoiceClientStartFrame
102
144
  | LiveVoiceClientAudioFrame
103
145
  | LiveVoiceClientPttReleaseFrame
104
146
  | LiveVoiceClientInterruptFrame
105
- | LiveVoiceClientEndFrame;
147
+ | LiveVoiceClientEndFrame
148
+ | LiveVoiceClientUpdateConfigFrame;
106
149
 
107
150
  interface LiveVoiceBinaryAudioFrame {
108
151
  readonly type: "binary_audio";
@@ -358,9 +401,60 @@ export function validateLiveVoiceClientFrame(
358
401
  return { ok: true, frame: { type: "interrupt" } };
359
402
  case "end":
360
403
  return { ok: true, frame: { type: "end" } };
404
+ case "update_config":
405
+ return validateUpdateConfigFrame(value);
361
406
  }
362
407
  }
363
408
 
409
+ function validateUpdateConfigFrame(
410
+ value: Record<string, unknown>,
411
+ ): LiveVoiceParseResult<LiveVoiceClientUpdateConfigFrame> {
412
+ if (
413
+ "silenceThresholdMs" in value &&
414
+ !isIntInRange(
415
+ value.silenceThresholdMs,
416
+ MIN_SILENCE_THRESHOLD_MS,
417
+ MAX_SILENCE_THRESHOLD_MS,
418
+ )
419
+ ) {
420
+ return protocolError(
421
+ "invalid_field",
422
+ `update_config field silenceThresholdMs must be an integer in [${MIN_SILENCE_THRESHOLD_MS}, ${MAX_SILENCE_THRESHOLD_MS}]`,
423
+ "silenceThresholdMs",
424
+ "update_config",
425
+ );
426
+ }
427
+
428
+ if (
429
+ "bargeInMinSpeechMs" in value &&
430
+ !isIntInRange(
431
+ value.bargeInMinSpeechMs,
432
+ MIN_BARGE_IN_MIN_SPEECH_MS,
433
+ MAX_BARGE_IN_MIN_SPEECH_MS,
434
+ )
435
+ ) {
436
+ return protocolError(
437
+ "invalid_field",
438
+ `update_config field bargeInMinSpeechMs must be an integer in [${MIN_BARGE_IN_MIN_SPEECH_MS}, ${MAX_BARGE_IN_MIN_SPEECH_MS}]`,
439
+ "bargeInMinSpeechMs",
440
+ "update_config",
441
+ );
442
+ }
443
+
444
+ return {
445
+ ok: true,
446
+ frame: {
447
+ type: "update_config",
448
+ ...(typeof value.silenceThresholdMs === "number"
449
+ ? { silenceThresholdMs: value.silenceThresholdMs }
450
+ : {}),
451
+ ...(typeof value.bargeInMinSpeechMs === "number"
452
+ ? { bargeInMinSpeechMs: value.bargeInMinSpeechMs }
453
+ : {}),
454
+ },
455
+ };
456
+ }
457
+
364
458
  export function parseLiveVoiceBinaryAudioFrame(
365
459
  data: unknown,
366
460
  ): LiveVoiceParseResult<LiveVoiceBinaryAudioFrame> {
@@ -448,6 +542,38 @@ function validateStartFrame(
448
542
  );
449
543
  }
450
544
 
545
+ if (
546
+ "silenceThresholdMs" in value &&
547
+ !isIntInRange(
548
+ value.silenceThresholdMs,
549
+ MIN_SILENCE_THRESHOLD_MS,
550
+ MAX_SILENCE_THRESHOLD_MS,
551
+ )
552
+ ) {
553
+ return protocolError(
554
+ "invalid_field",
555
+ `start frame field silenceThresholdMs must be an integer in [${MIN_SILENCE_THRESHOLD_MS}, ${MAX_SILENCE_THRESHOLD_MS}]`,
556
+ "silenceThresholdMs",
557
+ "start",
558
+ );
559
+ }
560
+
561
+ if (
562
+ "bargeInMinSpeechMs" in value &&
563
+ !isIntInRange(
564
+ value.bargeInMinSpeechMs,
565
+ MIN_BARGE_IN_MIN_SPEECH_MS,
566
+ MAX_BARGE_IN_MIN_SPEECH_MS,
567
+ )
568
+ ) {
569
+ return protocolError(
570
+ "invalid_field",
571
+ `start frame field bargeInMinSpeechMs must be an integer in [${MIN_BARGE_IN_MIN_SPEECH_MS}, ${MAX_BARGE_IN_MIN_SPEECH_MS}]`,
572
+ "bargeInMinSpeechMs",
573
+ "start",
574
+ );
575
+ }
576
+
451
577
  return {
452
578
  ok: true,
453
579
  frame: {
@@ -459,10 +585,26 @@ function validateStartFrame(
459
585
  ...(isLiveVoiceTurnDetectionMode(value.turnDetection)
460
586
  ? { turnDetection: value.turnDetection }
461
587
  : {}),
588
+ ...(typeof value.silenceThresholdMs === "number"
589
+ ? { silenceThresholdMs: value.silenceThresholdMs }
590
+ : {}),
591
+ ...(typeof value.bargeInMinSpeechMs === "number"
592
+ ? { bargeInMinSpeechMs: value.bargeInMinSpeechMs }
593
+ : {}),
462
594
  },
463
595
  };
464
596
  }
465
597
 
598
+ /** Whether `value` is an integer within the inclusive `[min, max]` range. */
599
+ function isIntInRange(value: unknown, min: number, max: number): boolean {
600
+ return (
601
+ typeof value === "number" &&
602
+ Number.isInteger(value) &&
603
+ value >= min &&
604
+ value <= max
605
+ );
606
+ }
607
+
466
608
  function validateAudioFrame(
467
609
  value: Record<string, unknown>,
468
610
  ): LiveVoiceParseResult<LiveVoiceClientAudioFrame> {
@@ -46,8 +46,10 @@ afterAll(() => {
46
46
  rmSync(ROOT, { recursive: true, force: true });
47
47
  });
48
48
 
49
- const { collectSourceVersions, createSourceWatchState, runSourceWatchPass } =
49
+ const { createSourceWatchState, runSourceWatchPass } =
50
50
  await import("../plugin-source-watch.js");
51
+ const { collectSourceVersions } =
52
+ await import("../../plugins/collect-source-versions.js");
51
53
  const { getSourceVersionsPath, readSourceVersions } =
52
54
  await import("../../plugins/source-versions.js");
53
55
 
@@ -25,14 +25,14 @@
25
25
  import {
26
26
  existsSync,
27
27
  mkdirSync,
28
+ readdirSync,
28
29
  renameSync,
29
30
  statSync,
30
31
  writeFileSync,
31
32
  } from "node:fs";
32
- import { readdirSync } from "node:fs";
33
33
  import { join } from "node:path";
34
34
 
35
- import { snapshotPluginSource } from "../plugins/source-fingerprint.js";
35
+ import { collectSourceVersions } from "../plugins/collect-source-versions.js";
36
36
  import type {
37
37
  PluginSourceVersion,
38
38
  SourceVersionsDocument,
@@ -43,69 +43,10 @@ import {
43
43
  SOURCE_VERSIONS_FORMAT,
44
44
  } from "../plugins/source-versions.js";
45
45
  import { getLogger } from "../util/logger.js";
46
- import {
47
- getMonitoringDataDir,
48
- getWorkspaceHooksDir,
49
- getWorkspacePluginsDir,
50
- } from "../util/platform.js";
46
+ import { getMonitoringDataDir } from "../util/platform.js";
51
47
 
52
48
  const log = getLogger("plugin-source-watch");
53
49
 
54
- /**
55
- * Collect the current source version of every watched directory: each
56
- * plugin directory under `<workspace>/plugins/` (same discovery rule as the
57
- * daemon's plugin scan — a directory with a `package.json`), plus the
58
- * standalone workspace hooks directory when it exists. Disabled plugins are
59
- * fingerprinted too: consumers need fresh state the moment a plugin is
60
- * re-enabled, and the `disabled` field is how they observe the toggle
61
- * itself (dotfiles are excluded from the fingerprint).
62
- */
63
- export function collectSourceVersions(): Record<string, PluginSourceVersion> {
64
- const out: Record<string, PluginSourceVersion> = {};
65
-
66
- const pluginsDir = getWorkspacePluginsDir();
67
- let entries: string[] = [];
68
- try {
69
- entries = readdirSync(pluginsDir);
70
- } catch {
71
- // No plugins directory yet — nothing to watch there.
72
- }
73
- for (const entry of entries) {
74
- if (entry.startsWith(".")) {
75
- continue;
76
- }
77
- const dir = join(pluginsDir, entry);
78
- try {
79
- if (!statSync(dir).isDirectory()) {
80
- continue;
81
- }
82
- } catch {
83
- continue;
84
- }
85
- if (!existsSync(join(dir, "package.json"))) {
86
- continue;
87
- }
88
- const snapshot = snapshotPluginSource(dir);
89
- out[dir] = {
90
- fingerprint: snapshot.fingerprint,
91
- evictionPaths: snapshot.evictionPaths,
92
- disabled: existsSync(join(dir, ".disabled")),
93
- };
94
- }
95
-
96
- const workspaceHooksDir = getWorkspaceHooksDir();
97
- if (existsSync(workspaceHooksDir)) {
98
- const snapshot = snapshotPluginSource(workspaceHooksDir);
99
- out[workspaceHooksDir] = {
100
- fingerprint: snapshot.fingerprint,
101
- evictionPaths: snapshot.evictionPaths,
102
- disabled: false,
103
- };
104
- }
105
-
106
- return out;
107
- }
108
-
109
50
  /**
110
51
  * Whether two version maps describe the same source state. Compares key
111
52
  * sets, fingerprints, and disabled flags; eviction paths are derived from
@@ -550,6 +550,6 @@ from the unified `llm` block. Override defaults by setting either of:
550
550
  | `llm.callSites.notificationDecision` | object | _(unset)_ | Provider/model/effort/etc. override for the decision engine call site |
551
551
  | `llm.callSites.preferenceExtraction` | object | _(unset)_ | Provider/model/effort/etc. override for the preference extractor call site |
552
552
 
553
- When a call site override is unset, the resolver falls back to `llm.default`.
553
+ When a call site override is unset, the resolver falls back to the shipped call-site default profile.
554
554
 
555
555
  The notification pipeline is always active -- signals are processed and dispatched as soon as the daemon is running. The audit trail (events, decisions, deliveries) is written for every signal.
@@ -6,7 +6,10 @@ import { getIsContainerized } from "../config/env-registry.js";
6
6
  import { getConfig } from "../config/loader.js";
7
7
  import { loadSkillCatalog, resolveSkillSelector } from "../config/skills.js";
8
8
  import { ipcClassifyRisk } from "../ipc/gateway-client.js";
9
- import { MEMORY_RETROSPECTIVE_ORIGIN } from "../plugins/defaults/memory/memory-retrospective-constants.js";
9
+ import {
10
+ MEMORY_RETROSPECTIVE_ORIGIN,
11
+ SKILL_MANAGEMENT_SKILL_ID,
12
+ } from "../plugins/defaults/memory/memory-retrospective-constants.js";
10
13
  import { indexCatalogById } from "../skills/include-graph.js";
11
14
  import { getSkillRoots } from "../skills/path-classifier.js";
12
15
  import { computeTransitiveSkillVersionHash } from "../skills/transitive-version-hash.js";
@@ -718,7 +721,10 @@ export async function classifyRisk(
718
721
  workingDir,
719
722
  manifestOverride,
720
723
  );
721
- const gatewayResult = await ipcClassifyRisk(ipcParams);
724
+ const gatewayResult = await ipcClassifyRisk(ipcParams, signal);
725
+ // A mid-retry cancellation should surface as an AbortError, not the
726
+ // misleading fail-closed "gateway unreachable" message.
727
+ signal?.throwIfAborted();
722
728
 
723
729
  if (!gatewayResult) {
724
730
  throw new Error(
@@ -799,8 +805,6 @@ export async function classifyRisk(
799
805
  // The grant is intentionally narrow: it matches exactly these tools AND the
800
806
  // retrospective origin on a v3-live assistant, so no interactive session, other
801
807
  // origin, or non-v3-live install is affected.
802
- const SKILL_MANAGEMENT_SKILL_ID = "skill-management";
803
-
804
808
  function isRetrospectiveSkillAuthoringGrant(
805
809
  toolName: string,
806
810
  input: Record<string, unknown>,
@@ -11,10 +11,36 @@
11
11
  import { describe, expect, test } from "bun:test";
12
12
 
13
13
  import { initializeDb } from "../db-init.js";
14
+ import { migrationSteps } from "../steps.js";
14
15
 
15
16
  describe("initializeDb — migrationsOk return contract", () => {
16
17
  test("resolves to { migrationsOk: true } on a clean init", async () => {
17
18
  const result = await initializeDb();
18
19
  expect(result).toEqual({ migrationsOk: true });
19
20
  });
21
+
22
+ test("resolves to { migrationsOk: false } when a step is deferred", async () => {
23
+ // initializeDb reads the shared migrationSteps array, so appending a step
24
+ // whose dependsOn names a checkpoint that can never exist exercises the
25
+ // real deferral path end-to-end. The step is deferred (never run, never
26
+ // checkpointed), so removing it in finally leaves the ledger untouched.
27
+ const syntheticStep = {
28
+ name: "dbInitTestSyntheticDeferredStep",
29
+ run: () => {
30
+ throw new Error("deferred step must not run");
31
+ },
32
+ dependsOn: ["dbInitTestMissingPrerequisite"],
33
+ };
34
+ migrationSteps.push(syntheticStep);
35
+ try {
36
+ const result = await initializeDb();
37
+ expect(result).toEqual({ migrationsOk: false });
38
+ } finally {
39
+ migrationSteps.splice(migrationSteps.indexOf(syntheticStep), 1);
40
+ }
41
+
42
+ // The deferred step wrote nothing to the ledger, so a clean re-init
43
+ // reports ready again.
44
+ expect(await initializeDb()).toEqual({ migrationsOk: true });
45
+ });
20
46
  });
@@ -236,6 +236,25 @@ export const messageMetadataSchema = z
236
236
  * treat message text as organic user input.
237
237
  */
238
238
  hidden: z.boolean().optional(),
239
+ /**
240
+ * How the send was initiated when the user message was sent on the
241
+ * user's behalf by the UI rather than typed by hand (`body.source` on
242
+ * `POST /v1/messages`). Current value: `"nav_redirect"` — a navigation
243
+ * shortcut redirected the user into chat with a pre-filled message.
244
+ * Read back by turn telemetry (`turn-events-store`) so analytics can
245
+ * separate UI-initiated turns from hand-typed ones, and by the queue
246
+ * drain to steer the turn. Kept as a plain string so new sources never
247
+ * fail metadata validation. Absent on hand-typed messages.
248
+ */
249
+ userMessageSource: z.string().optional(),
250
+ /**
251
+ * Discriminates daemon-authored rows from ordinary turns.
252
+ * `"system_card"` marks pre-composed status cards (the /compact, /clean,
253
+ * and summarize-up-to results); see {@link SYSTEM_CARD_MESSAGE_KIND}.
254
+ * Kept as a plain string so unknown future kinds never fail metadata
255
+ * validation.
256
+ */
257
+ messageKind: z.string().optional(),
239
258
  /**
240
259
  * Structured terminal record stamped onto a `<background_event
241
260
  * source="background-tool">` wake so the web can rebuild the inline
@@ -291,6 +310,61 @@ export function isHiddenMessageMetadata(
291
310
  return metadata?.hidden === true;
292
311
  }
293
312
 
313
+ /**
314
+ * Shared accessor for the send-source tag on user-message metadata (see the
315
+ * `userMessageSource` field on {@link messageMetadataSchema}). Returns the
316
+ * tag (e.g. `"nav_redirect"`) or undefined when the message was typed by
317
+ * hand or predates the field.
318
+ */
319
+ export function getUserMessageSourceMetadata(
320
+ metadata: Record<string, unknown> | null | undefined,
321
+ ): string | undefined {
322
+ const source = metadata?.userMessageSource;
323
+ return typeof source === "string" && source.length > 0 ? source : undefined;
324
+ }
325
+
326
+ /**
327
+ * `messageKind` value marking a daemon-authored system card — a pre-composed
328
+ * status reply (the /compact, /clean, and summarize-up-to result cards) that
329
+ * bypasses the agent loop. Cards render as standalone system notices, never
330
+ * as the assistant persona speaking, and never merge into adjacent assistant
331
+ * display turns.
332
+ */
333
+ export const SYSTEM_CARD_MESSAGE_KIND = "system_card";
334
+
335
+ /**
336
+ * Shared predicate for the system-card marker on assistant-message metadata
337
+ * (see the `messageKind` field on {@link messageMetadataSchema}). One
338
+ * definition so display merging, transcript rendering, and turn grouping
339
+ * cannot drift.
340
+ */
341
+ export function isSystemCardMetadata(
342
+ metadata: Record<string, unknown> | null | undefined,
343
+ ): boolean {
344
+ return metadata?.messageKind === SYSTEM_CARD_MESSAGE_KIND;
345
+ }
346
+
347
+ /**
348
+ * Row-level variant of {@link isSystemCardMetadata} for callers holding the
349
+ * raw persisted `metadata` JSON string. The single place the parse lives so
350
+ * display merging and turn grouping agree on what a card is.
351
+ */
352
+ export function isSystemCardMessage(
353
+ role: string,
354
+ metadata: string | null,
355
+ ): boolean {
356
+ if (role !== "assistant" || !metadata) {
357
+ return false;
358
+ }
359
+ try {
360
+ return isSystemCardMetadata(
361
+ JSON.parse(metadata) as Record<string, unknown>,
362
+ );
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+
294
368
  /**
295
369
  * Parse a persisted message's metadata JSON against {@link messageMetadataSchema}
296
370
  * — the single source of truth for its shape — returning the validated fields,
@@ -829,7 +903,7 @@ export function createConversation(
829
903
  * web client's `crypto.randomUUID()` / `draft-<ts>-<hex>` drafts while
830
904
  * rejecting anything with path separators, `..`, or other traversal vectors.
831
905
  */
832
- const ADOPTABLE_CONVERSATION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
906
+ export const ADOPTABLE_CONVERSATION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
833
907
 
834
908
  /**
835
909
  * Ensure a `conversations` row exists for `id`, creating one with default
@@ -3830,6 +3904,12 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3830
3904
  return [messageId];
3831
3905
  }
3832
3906
 
3907
+ // A system card is its own single-row group — its linked calls (e.g. the
3908
+ // summarize-up-to compaction call) never mix into a neighbouring turn.
3909
+ if (isSystemCardMessage(target.role, target.metadata)) {
3910
+ return [target.id];
3911
+ }
3912
+
3833
3913
  // Walk backward from the target message to find the turn boundary.
3834
3914
  // Limit to 50 rows — sufficient for even aggressive tool-use loops.
3835
3915
  const backwardRows = db
@@ -3838,6 +3918,7 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3838
3918
  role: messages.role,
3839
3919
  content: messages.content,
3840
3920
  createdAt: messages.createdAt,
3921
+ metadata: messages.metadata,
3841
3922
  })
3842
3923
  .from(messages)
3843
3924
  .where(
@@ -3855,6 +3936,12 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3855
3936
 
3856
3937
  for (const row of backwardRows) {
3857
3938
  if (row.role === "assistant") {
3939
+ if (isSystemCardMessage(row.role, row.metadata)) {
3940
+ // A system card closes the groups on either side of it — rows
3941
+ // before the card belong to an earlier display group.
3942
+ boundaryCreatedAt = row.createdAt;
3943
+ break;
3944
+ }
3858
3945
  assistantIds.push(row.id);
3859
3946
  } else if (row.role === "user") {
3860
3947
  if (isToolResultMessage(row.role, row.content)) {
@@ -3876,6 +3963,7 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3876
3963
  role: messages.role,
3877
3964
  content: messages.content,
3878
3965
  createdAt: messages.createdAt,
3966
+ metadata: messages.metadata,
3879
3967
  })
3880
3968
  .from(messages)
3881
3969
  .where(
@@ -3890,6 +3978,15 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3890
3978
 
3891
3979
  for (const row of forwardRows) {
3892
3980
  if (row.role === "assistant") {
3981
+ if (isSystemCardMessage(row.role, row.metadata)) {
3982
+ // A card that is the queried user message's only reply (e.g. the
3983
+ // /compact result) IS the turn's response; otherwise the card
3984
+ // closes the group.
3985
+ if (assistantIds.length === 0) {
3986
+ assistantIds.push(row.id);
3987
+ }
3988
+ break;
3989
+ }
3893
3990
  if (!assistantIds.includes(row.id)) {
3894
3991
  assistantIds.push(row.id);
3895
3992
  }
@@ -3912,6 +4009,7 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3912
4009
  id: messages.id,
3913
4010
  role: messages.role,
3914
4011
  createdAt: messages.createdAt,
4012
+ metadata: messages.metadata,
3915
4013
  })
3916
4014
  .from(messages)
3917
4015
  .where(
@@ -3925,7 +4023,11 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3925
4023
  .all();
3926
4024
 
3927
4025
  for (const row of gapRows) {
3928
- if (row.role === "assistant" && !assistantIds.includes(row.id)) {
4026
+ if (
4027
+ row.role === "assistant" &&
4028
+ !isSystemCardMessage(row.role, row.metadata) &&
4029
+ !assistantIds.includes(row.id)
4030
+ ) {
3929
4031
  assistantIds.push(row.id);
3930
4032
  }
3931
4033
  }
@@ -87,7 +87,7 @@ export async function initializeDb(): Promise<{ migrationsOk: boolean }> {
87
87
  // broken migration doesn't prevent independent later ones from succeeding.
88
88
  // The runner creates the checkpoint ledger, recovers crashed migrations, then
89
89
  // records each step so an already-migrated database skips it on later boots.
90
- const { applied, failed, skipped } = await runMigrationSteps(
90
+ const { applied, failed, skipped, deferred } = await runMigrationSteps(
91
91
  database,
92
92
  migrationSteps,
93
93
  );
@@ -108,6 +108,13 @@ export async function initializeDb(): Promise<{ migrationsOk: boolean }> {
108
108
  );
109
109
  }
110
110
 
111
+ if (deferred.length > 0) {
112
+ log.error(
113
+ { deferredMigrations: deferred, count: deferred.length },
114
+ `DB initialization completed with ${deferred.length} deferred migration(s) whose prerequisites are not applied`,
115
+ );
116
+ }
117
+
111
118
  // A passing post-run validation is part of readiness: validateMigrationState
112
119
  // flags schema inconsistencies (e.g. a completed step missing a declared
113
120
  // dependsOn checkpoint) that no individual step body surfaces as a failure.
@@ -119,7 +126,11 @@ export async function initializeDb(): Promise<{ migrationsOk: boolean }> {
119
126
  log.error({ err }, "validateMigrationState failed");
120
127
  }
121
128
 
122
- // migrationsOk reflects BOTH no failed migration steps AND a passing
129
+ // migrationsOk requires no failed steps, no deferred steps, and a passing
123
130
  // post-run validation, so an inconsistent schema keeps /readyz at 503.
124
- return { migrationsOk: failed.length === 0 && validationOk };
131
+ // Deferrals count because a mis-declared dependsOn would otherwise leave a
132
+ // step silently never running while the daemon reports ready.
133
+ return {
134
+ migrationsOk: failed.length === 0 && deferred.length === 0 && validationOk,
135
+ };
125
136
  }
@@ -38,8 +38,13 @@ export async function pruneOldLlmRequestLogsJob(
38
38
  ? rawRetention
39
39
  : config.memory.cleanup.llmRequestLogRetentionMs;
40
40
 
41
- // null means "keep forever" — skip pruning entirely
42
- if (retentionMs === null || retentionMs === undefined) return;
41
+ // null/0 means "keep forever" — skip pruning entirely. 0 is excluded (not
42
+ // just null) to match the scheduler in maybeEnqueueScheduledCleanupJobs,
43
+ // which no longer enqueues a retention-0 prune; guarding here too ensures a
44
+ // job left pending from before that fix does not wipe every log on its next
45
+ // run.
46
+ if (retentionMs === null || retentionMs === undefined || retentionMs <= 0)
47
+ return;
43
48
 
44
49
  const cutoffMs = Math.floor(Date.now() - retentionMs);
45
50
  if (!Number.isFinite(cutoffMs)) return;
@@ -104,9 +109,10 @@ export async function pruneOldToolInvocationsJob(
104
109
  ? rawRetention
105
110
  : config.auditLog.retentionDays;
106
111
 
107
- // 0 means disabled. rotateToolInvocations no-ops on this too, but
108
- // short-circuit so we don't dispatch a pointless async query.
109
- if (!retentionDays) return;
112
+ // 0 (or any non-positive window) means disabled keep forever. Guarding
113
+ // <= 0 mirrors the LLM-log prune and short-circuits before dispatching a
114
+ // pointless async query (rotateToolInvocations no-ops on this too).
115
+ if (retentionDays <= 0) return;
110
116
 
111
117
  await rotateToolInvocations(retentionDays);
112
118
  }
@@ -158,8 +164,10 @@ export function pruneOldConversationsJob(
158
164
  ? job.payload.retentionDays
159
165
  : config.memory.cleanup.conversationRetentionDays;
160
166
 
161
- // 0 means disabled
162
- if (retentionDays === 0) return;
167
+ // 0 (or any non-positive window) means disabled — keep forever. Guarding
168
+ // <= 0 mirrors the LLM-log prune so a stray non-positive window can never
169
+ // produce a cutoff at/after `now` that deletes live conversations.
170
+ if (retentionDays <= 0) return;
163
171
 
164
172
  const cutoffMs = Date.now() - retentionDays * 86_400_000;
165
173