@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -0,0 +1,60 @@
1
+ import type { ConversationNoticeEvent } from "../api/events/conversation-notice.js";
2
+
3
+ export type PendingConversationNotice = Omit<
4
+ ConversationNoticeEvent,
5
+ "type" | "conversationId"
6
+ >;
7
+
8
+ const MAX_TRACKED_CONVERSATIONS = 256;
9
+
10
+ const pendingNotices = new Map<
11
+ string,
12
+ Map<string, PendingConversationNotice>
13
+ >();
14
+
15
+ function touchConversation(
16
+ conversationId: string,
17
+ ): Map<string, PendingConversationNotice> {
18
+ const existing = pendingNotices.get(conversationId);
19
+ if (existing) {
20
+ pendingNotices.delete(conversationId);
21
+ pendingNotices.set(conversationId, existing);
22
+ return existing;
23
+ }
24
+ if (pendingNotices.size >= MAX_TRACKED_CONVERSATIONS) {
25
+ const oldest = pendingNotices.keys().next().value;
26
+ if (oldest !== undefined) pendingNotices.delete(oldest);
27
+ }
28
+ const next = new Map<string, PendingConversationNotice>();
29
+ pendingNotices.set(conversationId, next);
30
+ return next;
31
+ }
32
+
33
+ export function queueConversationNotice(
34
+ conversationId: string,
35
+ key: string,
36
+ notice: PendingConversationNotice,
37
+ ): void {
38
+ touchConversation(conversationId).set(key, notice);
39
+ }
40
+
41
+ export function drainConversationNotices(
42
+ conversationId: string,
43
+ ): ConversationNoticeEvent[] {
44
+ const notices = pendingNotices.get(conversationId);
45
+ if (!notices) return [];
46
+ pendingNotices.delete(conversationId);
47
+ return Array.from(notices.values(), (notice) => ({
48
+ type: "conversation_notice" as const,
49
+ conversationId,
50
+ ...notice,
51
+ }));
52
+ }
53
+
54
+ export function clearConversationNotices(conversationId: string): void {
55
+ pendingNotices.delete(conversationId);
56
+ }
57
+
58
+ export function resetConversationNoticesForTests(): void {
59
+ pendingNotices.clear();
60
+ }
@@ -60,6 +60,21 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
60
60
 
61
61
  const log = getLogger("conversation-process");
62
62
 
63
+ /**
64
+ * Daemon-injected subagent lifecycle notifications carry `subagentNotification`
65
+ * metadata. They are persisted into the parent conversation so the orchestrator
66
+ * wakes and reads the subagent's result, but they are internal scaffolding — the
67
+ * user sees subagent activity through the inline progress card, not a chat turn.
68
+ * Skip the `user_message_echo` broadcast for these so they never render as a live
69
+ * user bubble; the persisted row is filtered from the rendered transcript on the
70
+ * client.
71
+ */
72
+ function isSubagentNotificationMessage(
73
+ metadata: Record<string, unknown> | undefined,
74
+ ): boolean {
75
+ return metadata?.subagentNotification != null;
76
+ }
77
+
63
78
  /** Format the result of a forced compaction into a user-facing message. */
64
79
  export function formatCompactResult(result: ContextWindowResult): string {
65
80
  const fmt = (n: number | undefined) => (n ?? 0).toLocaleString("en-US");
@@ -853,14 +868,16 @@ async function drainSingleMessage(
853
868
 
854
869
  // Broadcast the user message to all hub subscribers so passive devices
855
870
  // see the user turn before the assistant reply starts streaming.
856
- next.onEvent({
857
- type: "user_message_echo",
858
- text: resolvedContent,
859
- conversationId: conversation.conversationId,
860
- messageId: userMessageId,
861
- requestId: next.requestId,
862
- clientMessageId: next.clientMessageId,
863
- });
871
+ if (!isSubagentNotificationMessage(next.metadata)) {
872
+ next.onEvent({
873
+ type: "user_message_echo",
874
+ text: resolvedContent,
875
+ conversationId: conversation.conversationId,
876
+ messageId: userMessageId,
877
+ requestId: next.requestId,
878
+ clientMessageId: next.clientMessageId,
879
+ });
880
+ }
864
881
  publishConversationMessagesChanged(conversation.conversationId);
865
882
 
866
883
  // Set the active surface for the dequeued message so runAgentLoop can inject context
@@ -1204,14 +1221,16 @@ async function drainBatch(
1204
1221
 
1205
1222
  // Broadcast the user message to all hub subscribers so passive devices
1206
1223
  // see each batched user turn before the assistant reply starts streaming.
1207
- qm.onEvent({
1208
- type: "user_message_echo",
1209
- text: qmContent,
1210
- conversationId: conversation.conversationId,
1211
- messageId: lastUserMessageId,
1212
- requestId: qm.requestId,
1213
- clientMessageId: qm.clientMessageId,
1214
- });
1224
+ if (!isSubagentNotificationMessage(qm.metadata)) {
1225
+ qm.onEvent({
1226
+ type: "user_message_echo",
1227
+ text: qmContent,
1228
+ conversationId: conversation.conversationId,
1229
+ messageId: lastUserMessageId,
1230
+ requestId: qm.requestId,
1231
+ clientMessageId: qm.clientMessageId,
1232
+ });
1233
+ }
1215
1234
  publishConversationMessagesChanged(conversation.conversationId);
1216
1235
 
1217
1236
  // Persist succeeded. Update last-successful markers so a later tail
@@ -3,7 +3,10 @@ import { v4 as uuid } from "uuid";
3
3
  import { z } from "zod";
4
4
 
5
5
  import { SurfaceActionSchema } from "../api/events/ui-surface-show.js";
6
- import { CardSurfaceDataSchema } from "../api/surfaces.js";
6
+ import {
7
+ CardSurfaceDataSchema,
8
+ FileUploadSurfaceDataSchema,
9
+ } from "../api/surfaces.js";
7
10
  import { isActivationSession } from "../memory/activation-session-store.js";
8
11
  import {
9
12
  addAppConversationId,
@@ -56,6 +59,7 @@ import type {
56
59
  ConfirmationSurfaceData,
57
60
  CopyBlockSurfaceData,
58
61
  DynamicPageSurfaceData,
62
+ FileUploadSurfaceData,
59
63
  FormSurfaceData,
60
64
  ListSurfaceData,
61
65
  OAuthConnectSurfaceData,
@@ -583,12 +587,20 @@ function normalizeCardShowData(
583
587
  (typeof input[k] === "string" &&
584
588
  (input[k] as string).trim().length > 0),
585
589
  );
586
- Sentry.addBreadcrumb({
587
- category: "card-normalization",
588
- message: `alias recovery: ${usedAliases.join(", ")} → body`,
589
- level: "info",
590
- data: { usedAliases, candidateCount: candidates.length },
591
- });
590
+ try {
591
+ Sentry.withScope((scope) => {
592
+ scope.setLevel("info");
593
+ scope.setTag("card_normalization", "alias_recovery");
594
+ scope.setTag("target_field", "body");
595
+ scope.setContext("card_normalization", {
596
+ used_aliases: usedAliases,
597
+ candidate_count: candidates.length,
598
+ });
599
+ Sentry.captureMessage("card_normalization:alias_recovery:body");
600
+ });
601
+ } catch {
602
+ // Never let telemetry break card rendering.
603
+ }
592
604
  }
593
605
  }
594
606
  for (const key of bodyAliasKeys) {
@@ -605,11 +617,16 @@ function normalizeCardShowData(
605
617
  ]);
606
618
  if (aliased !== undefined) {
607
619
  normalized.title = aliased;
608
- Sentry.addBreadcrumb({
609
- category: "card-normalization",
610
- message: `alias recovery: title`,
611
- level: "info",
612
- });
620
+ try {
621
+ Sentry.withScope((scope) => {
622
+ scope.setLevel("info");
623
+ scope.setTag("card_normalization", "alias_recovery");
624
+ scope.setTag("target_field", "title");
625
+ Sentry.captureMessage("card_normalization:alias_recovery:title");
626
+ });
627
+ } catch {
628
+ // Never let telemetry break card rendering.
629
+ }
613
630
  }
614
631
  }
615
632
  for (const key of titleAliasKeys) {
@@ -634,11 +651,16 @@ function normalizeCardShowData(
634
651
  ]);
635
652
  if (aliased !== undefined) {
636
653
  normalized.subtitle = aliased;
637
- Sentry.addBreadcrumb({
638
- category: "card-normalization",
639
- message: `alias recovery: subtitle`,
640
- level: "info",
641
- });
654
+ try {
655
+ Sentry.withScope((scope) => {
656
+ scope.setLevel("info");
657
+ scope.setTag("card_normalization", "alias_recovery");
658
+ scope.setTag("target_field", "subtitle");
659
+ Sentry.captureMessage("card_normalization:alias_recovery:subtitle");
660
+ });
661
+ } catch {
662
+ // Never let telemetry break card rendering.
663
+ }
642
664
  }
643
665
  }
644
666
  for (const key of subtitleAliasKeys) {
@@ -688,12 +710,21 @@ function normalizeCardShowData(
688
710
  { droppedKeys },
689
711
  "ui_show card data carried keys the card contract does not model; their content will not render",
690
712
  );
691
- Sentry.addBreadcrumb({
692
- category: "card-normalization",
693
- message: `dropped keys: ${droppedKeys.join(", ")}`,
694
- level: "warning",
695
- data: { droppedKeys },
696
- });
713
+ try {
714
+ Sentry.withScope((scope) => {
715
+ scope.setLevel("warning");
716
+ scope.setTag("card_normalization", "dropped_keys");
717
+ scope.setContext("card_normalization", {
718
+ dropped_count: droppedKeys.length,
719
+ });
720
+ // Key names are model-controlled, so they ride in `extra`, which
721
+ // beforeSend redacts (it does not scrub `contexts`) — see instrument.ts.
722
+ scope.setExtra("dropped_keys", droppedKeys);
723
+ Sentry.captureMessage("card_normalization:dropped_keys");
724
+ });
725
+ } catch {
726
+ // Never let telemetry break card rendering.
727
+ }
697
728
  }
698
729
  const parsed = CardSurfaceDataSchema.safeParse(normalized);
699
730
  if (parsed.success) {
@@ -829,6 +860,24 @@ function normalizeOAuthConnectShowData(
829
860
  };
830
861
  }
831
862
 
863
+ function normalizeFileUploadShowData(
864
+ rawData: Record<string, unknown>,
865
+ ): FileUploadSurfaceData {
866
+ // Parse against the canonical schema so the surface carries the shape the
867
+ // renderer expects. The schema is tolerant (every field optional and coerced)
868
+ // and recovers the common malformed `acceptedTypes` shapes — a comma-joined or
869
+ // bare string — into the `string[]` the renderer requires.
870
+ const parsed = FileUploadSurfaceDataSchema.safeParse(rawData);
871
+ if (parsed.success) {
872
+ return parsed.data;
873
+ }
874
+ log.warn(
875
+ { issues: parsed.error.issues },
876
+ "ui_show file_upload data failed FileUploadSurfaceDataSchema; rendering an empty file_upload surface",
877
+ );
878
+ return {};
879
+ }
880
+
832
881
  function buildChoiceActions(data: ChoiceSurfaceData): Array<{
833
882
  id: string;
834
883
  label: string;
@@ -2895,7 +2944,9 @@ export async function surfaceProxyResolver(
2895
2944
  ? normalizeOAuthConnectShowData(rawData)
2896
2945
  : surfaceType === "dynamic_page"
2897
2946
  ? normalizeDynamicPageShowData(input, rawData)
2898
- : (rawData as SurfaceData);
2947
+ : surfaceType === "file_upload"
2948
+ ? normalizeFileUploadShowData(rawData)
2949
+ : (rawData as SurfaceData);
2899
2950
  // Parse actions through the schema instead of typecasting raw model output.
2900
2951
  // The model may place actions inside `data` instead of the top-level
2901
2952
  // `actions` param — recover them so they aren't silently dropped.
@@ -2912,18 +2963,27 @@ export async function surfaceProxyResolver(
2912
2963
  if (result.success) {
2913
2964
  valid.push(result.data);
2914
2965
  } else {
2915
- Sentry.addBreadcrumb({
2916
- category: "card-normalization",
2917
- message: "action parse failure (individual)",
2918
- level: "warning",
2919
- data: {
2920
- issuePaths: result.error.issues.map((i) => i.path.join(".")),
2921
- keys:
2966
+ try {
2967
+ Sentry.withScope((scope) => {
2968
+ scope.setLevel("warning");
2969
+ scope.setTag("card_normalization", "action_parse_failure");
2970
+ scope.setContext("card_normalization", {
2971
+ issue_paths: result.error.issues.map((i) => i.path.join(".")),
2972
+ });
2973
+ // raw object keys are model-controlled, so they ride in `extra`,
2974
+ // which beforeSend redacts (it does not scrub `contexts`) — see
2975
+ // instrument.ts.
2976
+ scope.setExtra(
2977
+ "raw_keys",
2922
2978
  typeof raw === "object" && raw !== null
2923
2979
  ? Object.keys(raw)
2924
2980
  : [typeof raw],
2925
- },
2926
- });
2981
+ );
2982
+ Sentry.captureMessage("card_normalization:action_parse_failure");
2983
+ });
2984
+ } catch {
2985
+ // Never let telemetry break card rendering.
2986
+ }
2927
2987
  }
2928
2988
  }
2929
2989
  inputActions = valid.length > 0 ? valid : undefined;
@@ -2961,11 +3021,24 @@ export async function surfaceProxyResolver(
2961
3021
  !hasTemplate &&
2962
3022
  !hasActions
2963
3023
  ) {
2964
- Sentry.addBreadcrumb({
2965
- category: "card-normalization",
2966
- message: "empty card rejected",
2967
- level: "warning",
2968
- });
3024
+ try {
3025
+ Sentry.withScope((scope) => {
3026
+ scope.setLevel("warning");
3027
+ scope.setTag("card_normalization", "empty_card_rejected");
3028
+ scope.setContext("card_normalization", {
3029
+ surface_type: surfaceType,
3030
+ has_title: hasTitle,
3031
+ has_body: hasBody,
3032
+ has_subtitle: hasSubtitle,
3033
+ has_metadata: hasMetadata,
3034
+ has_template: hasTemplate,
3035
+ has_actions: hasActions,
3036
+ });
3037
+ Sentry.captureMessage("card_normalization:empty_card_rejected");
3038
+ });
3039
+ } catch {
3040
+ // Never let telemetry break card rendering.
3041
+ }
2969
3042
  return {
2970
3043
  content:
2971
3044
  "Error: ui_show card requires content — provide `data.body`, a `template` (e.g. task_progress with steps), `data.metadata`, `data.subtitle`, a `title`, or `actions`. The surface was not displayed because it carried no renderable content. Resend ui_show with populated card content.",
@@ -17,12 +17,17 @@ import type { LLMCallSite } from "../config/schemas/llm.js";
17
17
  import { getBindingByConversation } from "../memory/external-conversation-store.js";
18
18
  import type { PermissionPrompter } from "../permissions/prompter.js";
19
19
  import type { SecretPrompter } from "../permissions/secret-prompter.js";
20
- import { advisorEnabledForProfile } from "../plugins/defaults/advisor/advisor-gate.js";
21
20
  import type { Message, ToolDefinition } from "../providers/types.js";
22
21
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
23
22
  import { registerConversationSender } from "../tools/browser/browser-screencast.js";
24
23
  import type { ToolExecutor } from "../tools/executor.js";
25
- import { getMcpToolDefinitions, getTool } from "../tools/registry.js";
24
+ import {
25
+ getMcpToolDefinitions,
26
+ getPluginToolDefinitions,
27
+ getTool,
28
+ getWorkspaceToolDefinitions,
29
+ getWorkspaceToolNames,
30
+ } from "../tools/registry.js";
26
31
  import {
27
32
  ACTIVITY_SKIP_SET,
28
33
  injectActivityField,
@@ -41,6 +46,7 @@ import {
41
46
  type ToolExecutionResult,
42
47
  type ToolLifecycleEventHandler,
43
48
  } from "../tools/types.js";
49
+ import { loadWorkspaceTools } from "../tools/workspace-tools/loader.js";
44
50
  import {
45
51
  resolveUsageAttribution,
46
52
  type UsageAttributionSnapshot,
@@ -238,7 +244,10 @@ export function createToolExecutor(
238
244
  });
239
245
  }
240
246
  },
241
- isInteractive: !ctx.hasNoClient && !ctx.headlessLock,
247
+ isInteractive:
248
+ ctx.currentTurnIsNonInteractive !== undefined
249
+ ? !ctx.currentTurnIsNonInteractive
250
+ : !ctx.hasNoClient && !ctx.headlessLock,
242
251
  proxyToolResolver: (
243
252
  toolName: string,
244
253
  proxyInput: Record<string, unknown>,
@@ -506,6 +515,7 @@ const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
506
515
  */
507
516
  export const SUBAGENT_ONLY_TOOL_NAMES = new Set<string>([
508
517
  "file_list",
518
+ "code_search",
509
519
  "notify_parent",
510
520
  ]);
511
521
 
@@ -564,15 +574,6 @@ export function isToolActiveForContext(
564
574
  return true;
565
575
  }
566
576
  }
567
- if (name === "advisor") {
568
- // Gated per chat-profile (`ProfileEntry.advisorEnabled`): when the resolved
569
- // profile disables the advisor, omit the tool from the wire list so the
570
- // model never sees a tool it can only no-op on. Resolves the profile the
571
- // same way the advisor's execution-time guard does (the per-turn override,
572
- // else the active profile). The wire list is fixed before PRE_MODEL_CALL
573
- // hooks run, so later profile changes from hooks are not reflected here.
574
- return advisorEnabledForProfile(ctx.currentTurnOverrideProfile ?? null);
575
- }
576
577
  if (UI_SURFACE_TOOL_NAMES.has(name)) {
577
578
  if (
578
579
  channelCapabilities?.channel === "slack" &&
@@ -650,11 +651,13 @@ export function isToolActiveForContext(
650
651
  * allowedToolNames so newly-activated skill tools aren't blocked by
651
652
  * the executor's stale gate.
652
653
  *
653
- * Core (non-MCP) tool definitions are captured at conversation creation and
654
- * reused on each turn. MCP tool definitions are re-read from the global
655
- * registry on each turn so that tools registered after conversation creation
656
- * (e.g. via `vellum mcp reload`) are automatically picked up without
657
- * requiring conversation disposal or app restart.
654
+ * Core (non-MCP, non-workspace) tool definitions are captured at conversation
655
+ * creation and reused on each turn. MCP and workspace tool definitions are
656
+ * re-read from the global registry on each turn so that tools registered or
657
+ * changed after conversation creation are automatically picked up without
658
+ * requiring conversation disposal or app restart — MCP via `vellum mcp
659
+ * reload`, workspace tools via edits under `<workspaceDir>/tools/` that the
660
+ * per-turn reconcile (kicked below) folds into the registry.
658
661
  */
659
662
  export function createResolveToolsCallback(
660
663
  toolDefs: ToolDefinition[],
@@ -662,16 +665,30 @@ export function createResolveToolsCallback(
662
665
  ): ((history: Message[]) => ToolDefinition[]) | undefined {
663
666
  if (toolDefs.length === 0) return undefined;
664
667
 
665
- // Separate the initial tool defs into core (stable) and MCP (dynamic).
666
- // We keep core tools from the snapshot and re-read MCP tools each turn.
668
+ // Separate the initial tool defs into core (stable) and the dynamic
669
+ // categories (MCP, workspace, plugin). We keep core tools from the snapshot
670
+ // and re-read the dynamic categories from the registry each turn. They differ
671
+ // downstream: plugin tools flow through the same context filter + subagent
672
+ // allowlist as core, while MCP and workspace tools are added raw.
667
673
  const initialMcpDefs = getMcpToolDefinitions();
674
+ const initialPluginDefs = getPluginToolDefinitions();
668
675
  const initialMcpNames = new Set(initialMcpDefs.map((d) => d.name));
669
- const coreToolDefs = toolDefs.filter((d) => !initialMcpNames.has(d.name));
676
+ const initialWorkspaceNames = new Set(getWorkspaceToolNames());
677
+ const initialPluginNames = new Set(initialPluginDefs.map((d) => d.name));
678
+ const coreToolDefs = toolDefs.filter(
679
+ (d) =>
680
+ !initialMcpNames.has(d.name) &&
681
+ !initialWorkspaceNames.has(d.name) &&
682
+ !initialPluginNames.has(d.name),
683
+ );
670
684
  log.debug(
671
685
  {
672
686
  coreCount: coreToolDefs.length,
673
687
  mcpCount: initialMcpDefs.length,
674
688
  mcpTools: initialMcpDefs.map((d) => d.name),
689
+ workspaceCount: initialWorkspaceNames.size,
690
+ pluginCount: initialPluginDefs.length,
691
+ pluginTools: initialPluginDefs.map((d) => d.name),
675
692
  },
676
693
  "Conversation tool resolver initialized",
677
694
  );
@@ -685,11 +702,25 @@ export function createResolveToolsCallback(
685
702
  return [];
686
703
  }
687
704
 
688
- // Filter core tools based on current conversation context so that tools
689
- // irrelevant to this turn (e.g. UI tools when no client is connected)
705
+ // Reconcile workspace tool overrides under `<workspaceDir>/tools/` into
706
+ // the registry, then re-read them below the on-read replacement for a
707
+ // filesystem watcher. Fire-and-forget: the reconcile is idempotent,
708
+ // mtime-cached (a no-op costs one readdir + a stat per file) and
709
+ // serialized, so the registry settles for a subsequent turn to read.
710
+ void loadWorkspaceTools();
711
+
712
+ // Re-read plugin tool definitions from the registry each turn so a plugin
713
+ // installed/removed at runtime (activated by the per-turn scan in
714
+ // `plugins/mtime-cache.ts`) is picked up without recreating the
715
+ // conversation. Plugin tools share core's context filter + allowlist path,
716
+ // so combine them with the core snapshot before filtering.
717
+ const currentPluginDefs = getPluginToolDefinitions();
718
+
719
+ // Filter core + plugin tools based on current conversation context so that
720
+ // tools irrelevant to this turn (e.g. UI tools when no client is connected)
690
721
  // are omitted from the definitions sent to the provider.
691
- const filteredCoreDefs = coreToolDefs.filter((d) =>
692
- isToolActiveForContext(d.name, ctx),
722
+ const filteredCoreDefs = [...coreToolDefs, ...currentPluginDefs].filter(
723
+ (d) => isToolActiveForContext(d.name, ctx),
693
724
  );
694
725
 
695
726
  // When the conversation is acting as a subagent, restrict core tools to
@@ -705,24 +736,34 @@ export function createResolveToolsCallback(
705
736
  ? filteredCoreDefs.filter((d) => wireAllowlist.has(d.name))
706
737
  : filteredCoreDefs;
707
738
 
708
- // Re-read MCP tool definitions from the registry each turn so conversations
709
- // automatically pick up tools added/removed by `vellum mcp reload`.
739
+ // Re-read MCP and workspace tool definitions from the registry each turn
740
+ // so conversations automatically pick up tools added/removed by `vellum
741
+ // mcp reload` and workspace-tool edits reconciled from disk, without
742
+ // recreating the conversation.
710
743
  const currentMcpDefs = getMcpToolDefinitions();
744
+ const currentWorkspaceDefs = getWorkspaceToolDefinitions();
711
745
  log.debug(
712
746
  {
713
747
  coreCount: scopedCoreDefs.length,
714
748
  mcpCount: currentMcpDefs.length,
715
749
  mcpTools: currentMcpDefs.map((d) => d.name),
750
+ workspaceCount: currentWorkspaceDefs.length,
751
+ workspaceTools: currentWorkspaceDefs.map((d) => d.name),
716
752
  },
717
- "MCP tools resolved for turn",
753
+ "MCP and workspace tools resolved for turn",
718
754
  );
719
755
  const scopedMcpDefs = wireAllowlist
720
756
  ? currentMcpDefs.filter((d) => wireAllowlist.has(d.name))
721
757
  : currentMcpDefs;
758
+ const scopedWorkspaceDefs = wireAllowlist
759
+ ? currentWorkspaceDefs.filter((d) => wireAllowlist.has(d.name))
760
+ : currentWorkspaceDefs;
722
761
  const excluded = new Set(getConfig().tools.exclude);
723
- const allBaseDefs = [...scopedCoreDefs, ...scopedMcpDefs].filter(
724
- (d) => !excluded.has(d.name),
725
- );
762
+ const allBaseDefs = [
763
+ ...scopedCoreDefs,
764
+ ...scopedWorkspaceDefs,
765
+ ...scopedMcpDefs,
766
+ ].filter((d) => !excluded.has(d.name));
726
767
 
727
768
  const effectivePreactivated = [
728
769
  ...DEFAULT_PREACTIVATED_SKILL_IDS,
@@ -50,6 +50,7 @@ import {
50
50
  getMessages,
51
51
  resolveOverrideProfile,
52
52
  setConversationHistoryStrippedAt,
53
+ setConversationProcessingStartedAt,
53
54
  } from "../memory/conversation-crud.js";
54
55
  import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
55
56
  import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
@@ -91,7 +92,7 @@ import {
91
92
  isActivationMomentParam,
92
93
  } from "../telemetry/activation-funnel.js";
93
94
  import { ToolExecutor } from "../tools/executor.js";
94
- import { getAllToolDefinitions } from "../tools/registry.js";
95
+ import { getAllToolDefinitions, getTool } from "../tools/registry.js";
95
96
  import type { ToolLifecycleEvent } from "../tools/types.js";
96
97
  import type { OnboardingContext } from "../types/onboarding-context.js";
97
98
  import type { AbortReason } from "../util/abort-reasons.js";
@@ -242,6 +243,14 @@ export interface ConversationConstructorOptions {
242
243
  speedOverride?: Speed;
243
244
  cacheTtl?: "5m" | "1h";
244
245
  modelOverride?: string;
246
+ /**
247
+ * Give this conversation's LLM calls provider-native (server-side) web
248
+ * search when the resolved provider supports it (see
249
+ * {@link AgentLoopConfig.enableNativeWebSearch}). Set by the subagent manager
250
+ * for the tool-less advisor consult so it can ground guidance with live web
251
+ * access; non-native providers get nothing. Defaults to false.
252
+ */
253
+ enableNativeWebSearch?: boolean;
245
254
  }
246
255
 
247
256
  export class Conversation {
@@ -376,6 +385,7 @@ export class Conversation {
376
385
  */
377
386
  wakePersonaOverride?: SystemPromptPersonaOverride;
378
387
  /** @internal */ currentTurnOverrideProfile?: string;
388
+ /** @internal */ currentTurnIsNonInteractive?: boolean;
379
389
  /** @internal */ authContext?: AuthContext;
380
390
  /** @internal */ currentTurnAuthContext?: AuthContext;
381
391
  /** @internal */ currentTurnSourceActorPrincipalId?: string;
@@ -590,6 +600,7 @@ export class Conversation {
590
600
  options?: ConversationConstructorOptions,
591
601
  ) {
592
602
  const { maxTokens, speedOverride, cacheTtl, modelOverride } = options ?? {};
603
+ const enableNativeWebSearch = options?.enableNativeWebSearch ?? false;
593
604
  this.conversationId = conversationId;
594
605
  this.systemPrompt = systemPrompt;
595
606
  this.provider = provider;
@@ -688,6 +699,7 @@ export class Conversation {
688
699
  ? { speed: resolvedSpeed }
689
700
  : {}),
690
701
  ...(cacheTtl ? { cacheTtl } : {}),
702
+ ...(enableNativeWebSearch ? { enableNativeWebSearch: true } : {}),
691
703
  };
692
704
  if (configuredMaxTokens !== undefined) {
693
705
  agentLoopConfig.maxTokens = configuredMaxTokens;
@@ -701,6 +713,9 @@ export class Conversation {
701
713
  tools: toolDefs.length > 0 ? toolDefs : undefined,
702
714
  toolExecutor: toolDefs.length > 0 ? toolExecutor : undefined,
703
715
  resolveTools,
716
+ // A tool the registry marks exclusive (e.g. `advisor`) runs alone in its
717
+ // turn; the loop defers any sibling calls until the next turn.
718
+ isExclusiveTool: (name) => getTool(name)?.exclusive === true,
704
719
  resolveConversationDir: () => {
705
720
  const conv = getConversation(this.conversationId);
706
721
  if (!conv) return null;
@@ -1335,6 +1350,13 @@ export class Conversation {
1335
1350
  setProcessing(value: boolean): void {
1336
1351
  const wasProcessing = this._processing;
1337
1352
  this._processing = value;
1353
+ // Persist the cross-process source of truth so out-of-process callers
1354
+ // (retrospective CLI, future detached workers) can detect mid-turn state
1355
+ // by reading the conversations row directly.
1356
+ setConversationProcessingStartedAt(
1357
+ this.conversationId,
1358
+ value ? Date.now() : null,
1359
+ );
1338
1360
  if (wasProcessing && !value) {
1339
1361
  void publishSyncInvalidation([
1340
1362
  conversationMetadataSyncTag(this.conversationId),
@@ -24,6 +24,12 @@ export const DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT = 90;
24
24
  // clears the warning state, which discards the banner's (state-scoped) dismissal
25
25
  // so it re-appears the moment usage ticks back up.
26
26
  export const DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT = 77;
27
+ // Absolute free-space floor (MiB). Regardless of usage percentage, never enter
28
+ // the warning or critical state while at least this much space remains free. A
29
+ // high usage percentage on a large disk can still leave many gigabytes
30
+ // available, where locking is pointless. Small volumes (where a high percentage
31
+ // genuinely means near-full) drop below the floor and remain protected.
32
+ export const DISK_PRESSURE_MIN_FREE_FLOOR_MB = 2048;
27
33
  export const DISK_PRESSURE_CHECK_INTERVAL_MS = 60_000;
28
34
  export const DISK_PRESSURE_OVERRIDE_CONFIRMATION = "I understand the risks";
29
35
  export const DISK_PRESSURE_BLOCKED_CAPABILITIES = [
@@ -219,7 +225,10 @@ export function evaluateDiskPressureNow(): DiskPressureStatus {
219
225
  const criticalThreshold = state.status.locked
220
226
  ? DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT
221
227
  : DISK_PRESSURE_THRESHOLD_PERCENT;
222
- const isCritical = usagePercent >= criticalThreshold;
228
+ // Absolute free-space floor overrides the percentage thresholds: while ample
229
+ // space remains free, report "ok" no matter how full the volume is by percent.
230
+ const hasAmpleFreeSpace = usageInfo.freeMb >= DISK_PRESSURE_MIN_FREE_FLOOR_MB;
231
+ const isCritical = !hasAmpleFreeSpace && usagePercent >= criticalThreshold;
223
232
  // Mirror the critical deadband for the warning band: once in an active
224
233
  // pressure state (warning or critical), hold warning until usage clears the
225
234
  // lower warning-clear threshold. Treating "critical" as active here matters
@@ -235,7 +244,8 @@ export function evaluateDiskPressureNow(): DiskPressureStatus {
235
244
  const warningThreshold = inActivePressureState
236
245
  ? DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT
237
246
  : DISK_PRESSURE_WARNING_THRESHOLD_PERCENT;
238
- const isWarning = !isCritical && usagePercent >= warningThreshold;
247
+ const isWarning =
248
+ !hasAmpleFreeSpace && !isCritical && usagePercent >= warningThreshold;
239
249
  const lastCheckedAt = new Date().toISOString();
240
250
 
241
251
  if (!isCritical && !isWarning) {