@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +1128 -119
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -1,4 +1,5 @@
1
1
  import { RiskLevel } from "../../permissions/types.js";
2
+ import { isUnparseableToolArgs } from "../../providers/unparseable-tool-args.js";
2
3
  import { registerTool } from "../registry.js";
3
4
  import type {
4
5
  ToolContext,
@@ -9,6 +10,38 @@ import type {
9
10
  /** Envelope keys consumed by `skill_execute` itself, never inner-tool params. */
10
11
  const SKILL_EXECUTE_ENVELOPE_KEYS = new Set(["tool", "input", "activity"]);
11
12
 
13
+ /**
14
+ * Recover a `skill_execute` envelope that the provider layer wrapped under the
15
+ * `_raw` unparseable marker.
16
+ *
17
+ * MiniMax's object→string argument coercion JSON-decodes the inner `input`
18
+ * value after parsing the outer arguments. When the model passes a bare string
19
+ * as `input` (e.g. Markdown body instead of `{ "content": "..." }`), that inner
20
+ * decode fails and the whole call is marked unparseable — even though the outer
21
+ * envelope is valid JSON. Re-parsing `_raw` recovers `{ tool, input, activity }`
22
+ * so the inner tool can still be dispatched. A genuinely truncated/malformed
23
+ * call's `_raw` won't parse and is returned unchanged, preserving the
24
+ * retryable-error path for real stream corruption.
25
+ */
26
+ export function recoverSkillExecuteEnvelope(
27
+ envelope: Record<string, unknown>,
28
+ ): Record<string, unknown> {
29
+ if (!isUnparseableToolArgs(envelope)) return envelope;
30
+ try {
31
+ const parsed: unknown = JSON.parse(envelope._raw);
32
+ if (
33
+ parsed != null &&
34
+ typeof parsed === "object" &&
35
+ !Array.isArray(parsed)
36
+ ) {
37
+ return parsed as Record<string, unknown>;
38
+ }
39
+ } catch {
40
+ // Genuinely malformed/truncated — leave wrapped for the retryable error.
41
+ }
42
+ return envelope;
43
+ }
44
+
12
45
  /**
13
46
  * Resolve the inner tool's parameters from a `skill_execute` envelope.
14
47
  *
@@ -19,15 +52,20 @@ const SKILL_EXECUTE_ENVELOPE_KEYS = new Set(["tool", "input", "activity"]);
19
52
  * Weaker models routinely misplace the parameters. Left unhandled, the inner
20
53
  * tool receives `{}`, fails schema validation ("<field> is required"), and the
21
54
  * model retries the identical malformed call until it gives up — the empty-
22
- * input retry loop. Two common misplacements are rescued so the call can
55
+ * input retry loop. Three common misplacements are rescued so the call can
23
56
  * succeed instead:
24
57
  *
25
58
  * 1. `input` passed as a JSON-encoded string instead of an object.
26
59
  * 2. Parameters spread as top-level siblings of `tool`/`activity`, with `input`
27
60
  * absent or an empty object.
61
+ * 3. The sole required field's value passed bare as `input` (a non-JSON string)
62
+ * — e.g. the full Markdown body as `input` instead of `{ "content": "..." }`.
63
+ * Rescued only when `innerSchema` has exactly one required string field, so
64
+ * the mapping is unambiguous.
28
65
  */
29
66
  export function resolveSkillExecuteInput(
30
67
  envelope: Record<string, unknown>,
68
+ innerSchema?: unknown,
31
69
  ): Record<string, unknown> {
32
70
  const raw = envelope.input;
33
71
 
@@ -48,7 +86,14 @@ export function resolveSkillExecuteInput(
48
86
  return parsed as Record<string, unknown>;
49
87
  }
50
88
  } catch {
51
- // Not JSON — fall through to sibling rescue.
89
+ // Not JSON. A weak model may have placed the inner tool's sole required
90
+ // string value directly as `input` (e.g. the full Markdown body as
91
+ // `document_update`'s `content`) instead of a `{ "content": "..." }`
92
+ // object. When the inner tool has exactly one required string field, map
93
+ // the bare string onto it rather than discarding content the model
94
+ // actually produced.
95
+ const field = soleRequiredStringField(innerSchema);
96
+ if (field) return { [field]: raw };
52
97
  }
53
98
  }
54
99
 
@@ -62,6 +107,58 @@ export function resolveSkillExecuteInput(
62
107
  return {};
63
108
  }
64
109
 
110
+ /**
111
+ * The single required string property of an inner tool's input schema, or
112
+ * `null` when the schema has zero or more than one required field, or its lone
113
+ * required field is not a string. Used to map a bare `input` string onto the
114
+ * one field it can unambiguously belong to.
115
+ */
116
+ function soleRequiredStringField(innerSchema: unknown): string | null {
117
+ if (innerSchema == null || typeof innerSchema !== "object") return null;
118
+ const schema = innerSchema as {
119
+ required?: unknown;
120
+ properties?: Record<string, { type?: unknown } | undefined>;
121
+ };
122
+ const required = Array.isArray(schema.required) ? schema.required : [];
123
+ if (required.length !== 1) return null;
124
+ const field = required[0];
125
+ if (typeof field !== "string") return null;
126
+ return schema.properties?.[field]?.type === "string" ? field : null;
127
+ }
128
+
129
+ /**
130
+ * Augment an inner-tool error with `skill_execute` envelope guidance when the
131
+ * call carried no inner parameters.
132
+ *
133
+ * {@link resolveSkillExecuteInput} rescues *misplaced* parameters (siblings, a
134
+ * JSON-encoded string). It cannot rescue a genuinely empty call — there is
135
+ * nothing to relocate — so the inner tool runs with `{}` and rejects it with a
136
+ * field-level message ("<field> is required") that says nothing about the
137
+ * envelope. Weak models then retry the identical empty shape, oscillating over
138
+ * whether parameters belong under `input`, as siblings, or as a JSON string.
139
+ *
140
+ * Appending the canonical envelope shape gives the next attempt a concrete
141
+ * template. Fires only when the resolved inner input was empty AND the tool
142
+ * errored, so well-formed calls and tools that legitimately accept no
143
+ * parameters are untouched.
144
+ */
145
+ export function augmentSkillExecuteError(
146
+ toolName: string,
147
+ resolvedInput: Record<string, unknown>,
148
+ result: ToolExecutionResult,
149
+ ): ToolExecutionResult {
150
+ if (!result.isError || Object.keys(resolvedInput).length > 0) return result;
151
+
152
+ const guidance =
153
+ `\n\nThis skill_execute call carried no parameters for "${toolName}". ` +
154
+ `Put the tool's parameters inside \`input\`. For example: ` +
155
+ `{"tool": "${toolName}", "input": { /* the tool's parameters */ }, ` +
156
+ `"activity": "..."}. The skill's instructions (from skill_load) list ` +
157
+ `"${toolName}"'s required fields.`;
158
+
159
+ return { ...result, content: result.content + guidance };
160
+ }
161
+
65
162
  export const skillExecuteTool = {
66
163
  name: "skill_execute",
67
164
  description:
@@ -39,6 +39,17 @@ const INLINE_COMMAND_ELIGIBLE_SOURCES = new Set([
39
39
  "workspace",
40
40
  ]);
41
41
 
42
+ /** Matches raw `` !`...` `` inline command tokens in a skill body. */
43
+ const INLINE_COMMAND_TOKEN_PATTERN = /!`[^`]*`/g;
44
+
45
+ /**
46
+ * Replacement for inline command tokens when they are not rendered during
47
+ * disk-pressure cleanup mode — keeps the raw tokens out of the prompt without
48
+ * executing any shell.
49
+ */
50
+ const INLINE_COMMAND_CLEANUP_STUB =
51
+ "[inline command skipped: storage cleanup mode]";
52
+
42
53
  const log = getLogger("skill-load");
43
54
 
44
55
  /**
@@ -159,11 +170,18 @@ export const skillLoadTool = {
159
170
  };
160
171
  }
161
172
 
173
+ // During disk-pressure cleanup mode, skill_load must not produce side
174
+ // effects: no auto-install (workspace writes / `bun install`) and no inline
175
+ // command execution. Instructions are still returned so the assistant can
176
+ // load the system-storage-cleanup skill (and any already-installed skill).
177
+ const cleanupMode = context.diskPressureCleanupModeActive === true;
178
+
162
179
  let loaded = loadSkillBySelector(selector);
163
180
 
164
181
  // Auto-install from catalog if the skill isn't found locally
165
182
  if (
166
183
  !loaded.skill &&
184
+ !cleanupMode &&
167
185
  (loaded.errorCode === "not_found" || loaded.errorCode === "empty_catalog")
168
186
  ) {
169
187
  try {
@@ -221,6 +239,10 @@ export const skillLoadTool = {
221
239
  const missing = collectAllMissing(skill.id, catalogIndex);
222
240
  if (missing.size === 0) break;
223
241
 
242
+ // Under the disk-pressure lock, never auto-install missing includes
243
+ // (that writes to the workspace). Leave them advisory ("not loaded").
244
+ if (cleanupMode) break;
245
+
224
246
  // Lazily resolve catalog on first round with missing includes
225
247
  if (!remoteCatalog) {
226
248
  try {
@@ -289,7 +311,19 @@ export const skillLoadTool = {
289
311
  const hasInlineCommands =
290
312
  skill.inlineCommandExpansions && skill.inlineCommandExpansions.length > 0;
291
313
 
292
- if (hasInlineCommands) {
314
+ if (hasInlineCommands && cleanupMode) {
315
+ // Under the disk-pressure lock, loading a skill must not execute shell.
316
+ // Strip inline command tokens instead of rendering them; the rest of the
317
+ // instructions are still returned.
318
+ body = body.replace(
319
+ INLINE_COMMAND_TOKEN_PATTERN,
320
+ INLINE_COMMAND_CLEANUP_STUB,
321
+ );
322
+ log.info(
323
+ { skillId: skill.id },
324
+ "Skipped inline command expansion during disk pressure cleanup mode",
325
+ );
326
+ } else if (hasInlineCommands) {
293
327
  if (skill.source === "extra" || skill.source === "plugin") {
294
328
  // Third-party skill roots — `extra` dirs and skills shipped inside
295
329
  // installed plugins — are out of scope for inline command expansion.
@@ -368,7 +402,14 @@ export const skillLoadTool = {
368
402
  childLoaded.skill.inlineCommandExpansions &&
369
403
  childLoaded.skill.inlineCommandExpansions.length > 0;
370
404
 
371
- if (childHasInlineCommands) {
405
+ if (childHasInlineCommands && cleanupMode) {
406
+ // No shell execution under the disk-pressure lock — strip the
407
+ // child's inline command tokens rather than rendering them.
408
+ childBody = childBody.replace(
409
+ INLINE_COMMAND_TOKEN_PATTERN,
410
+ INLINE_COMMAND_CLEANUP_STUB,
411
+ );
412
+ } else if (childHasInlineCommands) {
372
413
  if (
373
414
  childLoaded.skill.source === "extra" ||
374
415
  childLoaded.skill.source === "plugin"
@@ -1,3 +1,7 @@
1
+ import { validateInferenceProfileKey } from "../../config/inference-profile-validation.js";
2
+ import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
3
+ import { getConfig } from "../../config/loader.js";
4
+ import { AUTO_PROFILE_KEY } from "../../config/seed-inference-profiles.js";
1
5
  import { findConversation } from "../../daemon/conversation-registry.js";
2
6
  import { getConversationOverrideProfile } from "../../memory/conversation-crud.js";
3
7
  import type { Message } from "../../providers/types.js";
@@ -14,6 +18,7 @@ export async function executeSubagentSpawn(
14
18
  const extraContext = input.context as string | undefined;
15
19
  const fork = input.fork === true;
16
20
  const role = (input.role as string | undefined) ?? undefined;
21
+ const inferenceProfile = input.inference_profile;
17
22
 
18
23
  // For fork mode, sendResultToUser defaults to false unless explicitly set to true.
19
24
  // For regular mode, sendResultToUser defaults to true (existing behavior).
@@ -28,6 +33,26 @@ export async function executeSubagentSpawn(
28
33
  };
29
34
  }
30
35
 
36
+ let requestedOverrideProfile: string | undefined;
37
+ let forceOverrideProfile = false;
38
+ if (inferenceProfile !== undefined) {
39
+ if (typeof inferenceProfile !== "string") {
40
+ return {
41
+ content: "Error: inference_profile must be a string",
42
+ isError: true,
43
+ };
44
+ }
45
+ const profileError = validateInferenceProfileKey(inferenceProfile);
46
+ if (profileError) {
47
+ return {
48
+ content: `Error: ${profileError}`,
49
+ isError: true,
50
+ };
51
+ }
52
+ requestedOverrideProfile = inferenceProfile;
53
+ forceOverrideProfile = true;
54
+ }
55
+
31
56
  const manager = getSubagentManager();
32
57
  const sendToClient = context.sendToClient as
33
58
  | ((msg: { type: string; [key: string]: unknown }) => void)
@@ -71,22 +96,45 @@ export async function executeSubagentSpawn(
71
96
 
72
97
  // The subagent runs as its own background conversation, so the agent
73
98
  // loop's background-skip rule would zero out any inherited profile.
74
- // Pass the parent's profile explicitly via `SubagentConfig` so
75
- // `SubagentManager.spawn` forwards it back into the subagent's
76
- // `runAgentLoop` call as `options.overrideProfile`.
99
+ // Forward the invoker's profile explicitly via `SubagentConfig` so
100
+ // `SubagentManager.spawn` passes it into the subagent's `runAgentLoop` as
101
+ // `options.overrideProfile`.
77
102
  //
78
- // Prefer the per-turn `context.overrideProfile` (populated by
79
- // `runAgentLoopImpl` from its resolved `turnOverrideProfile`) over a
80
- // row read so nested spawns inherit correctly. The current subagent's
81
- // own conversation row never has `inferenceProfile` set — its override
82
- // arrived via in-memory `SubagentConfig.overrideProfile` — and even if it
83
- // were set, `getConversationOverrideProfile` short-circuits for
84
- // background conversations and returns `undefined`. Falling back to the
85
- // row read preserves behavior for tool calls that originate outside an
86
- // agent-loop turn.
87
- const inheritedOverrideProfile =
88
- context.overrideProfile ??
89
- getConversationOverrideProfile(context.conversationId);
103
+ // Resolution order: an explicit spawn-time profile, then the per-turn
104
+ // `context.overrideProfile` (populated by `runAgentLoopImpl` from its
105
+ // resolved `turnOverrideProfile`, covering per-conversation overrides and
106
+ // tool-routed switches), then a row read, and finally the resolved DEFAULT
107
+ // profile of the call site that invoked us. That last fallback is what makes
108
+ // a subagent match its invoker when the invoking turn ran purely on its
109
+ // call-site default — the workspace `activeProfile` for `mainAgent`, or the
110
+ // call site's own catalog default (e.g. `cost-optimized`) for a
111
+ // heartbeat/background invoker. `resolveDefaultProfileKey` does not reflect a
112
+ // static `llm.callSites.<callSite>` profile override (only the
113
+ // activeProfile-for-mainAgent path plus the catalog default), a narrow gap
114
+ // that only surfaces with no `activeProfile` set and a hand-tuned call-site
115
+ // profile.
116
+ //
117
+ // The fallback is forwarded NON-forced, so an explicit
118
+ // `llm.callSites.subagentSpawn` profile still wins; an explicit
119
+ // `inference_profile` argument keeps `forceOverrideProfile` and wins
120
+ // outright. (The row read short-circuits to `undefined` for the background
121
+ // subagent conversation and for tool calls outside an agent-loop turn.)
122
+ let inheritedOverrideProfile = requestedOverrideProfile;
123
+ if (inheritedOverrideProfile === undefined) {
124
+ const inheritedCandidate =
125
+ context.overrideProfile ??
126
+ getConversationOverrideProfile(context.conversationId) ??
127
+ resolveDefaultProfileKey(
128
+ context.invokingCallSite ?? "mainAgent",
129
+ getConfig().llm,
130
+ );
131
+ // Skip the metadata-only "auto" key: forwarding it collapses the child to
132
+ // `llm.default`, whereas the invoker's own auto base IS the `subagentSpawn`
133
+ // default — so leaving this undefined keeps the child on that default.
134
+ if (inheritedCandidate !== AUTO_PROFILE_KEY) {
135
+ inheritedOverrideProfile = inheritedCandidate;
136
+ }
137
+ }
90
138
 
91
139
  try {
92
140
  const subagentId = await manager.spawn(
@@ -102,6 +150,7 @@ export async function executeSubagentSpawn(
102
150
  ...(inheritedOverrideProfile
103
151
  ? { overrideProfile: inheritedOverrideProfile }
104
152
  : {}),
153
+ ...(forceOverrideProfile ? { forceOverrideProfile: true } : {}),
105
154
  ...(context.toolUseId ? { parentToolUseId: context.toolUseId } : {}),
106
155
  ...forkFields,
107
156
  },
@@ -4,8 +4,8 @@ import { spawn } from "node:child_process";
4
4
  import { getConfig } from "../../config/loader.js";
5
5
  import { isCesShellLockdownEnabled } from "../../credential-execution/feature-gates.js";
6
6
  import { RiskLevel } from "../../permissions/types.js";
7
- import { isUntrustedTrustClass } from "../../runtime/actor-trust-resolver.js";
8
7
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
8
+ import { isUntrustedShellLockdownActive } from "../../runtime/effective-capabilities.js";
9
9
  import { redactSecrets } from "../../security/secret-scanner.js";
10
10
  import { getLogger } from "../../util/logger.js";
11
11
  import { getDataDir } from "../../util/platform.js";
@@ -117,9 +117,10 @@ export const shellTool = {
117
117
  }
118
118
 
119
119
  const config = getConfig();
120
- const shellLockdownActive =
121
- isCesShellLockdownEnabled(config) &&
122
- isUntrustedTrustClass(context.trustClass);
120
+ const shellLockdownActive = isUntrustedShellLockdownActive({
121
+ trustClass: context.trustClass,
122
+ lockdownEnabled: isCesShellLockdownEnabled(config),
123
+ });
123
124
 
124
125
  const networkMode: "off" | "proxied" =
125
126
  input.network_mode === "proxied" ? "proxied" : "off";
@@ -307,6 +308,11 @@ export const shellTool = {
307
308
 
308
309
  const env = buildSanitizedEnv();
309
310
  env.__CONVERSATION_ID = context.conversationId;
311
+ // Surface the resolving model to assistant CLI commands so they can tailor
312
+ // remediation guidance for weak open models (see isWeakOpenModel).
313
+ if (context.attribution?.resolvedModel) {
314
+ env.__RESOLVED_MODEL = context.attribution.resolvedModel;
315
+ }
310
316
  if (proxyEnv) {
311
317
  Object.assign(env, proxyEnv);
312
318
  }
@@ -9,7 +9,7 @@ import {
9
9
  isUnparseableToolArgs,
10
10
  unparseableToolArgsMessage,
11
11
  } from "../providers/unparseable-tool-args.js";
12
- import { isUntrustedTrustClass } from "../runtime/actor-trust-resolver.js";
12
+ import { resolveCapabilities } from "../runtime/capabilities.js";
13
13
  import { createOrReuseToolGrantRequest } from "../runtime/tool-grant-request-helper.js";
14
14
  import { redactSecrets } from "../security/secret-scanner.js";
15
15
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
@@ -157,7 +157,6 @@ const UI_SURFACE_TOOLS = new Set(["ui_show", "ui_update", "ui_dismiss"]);
157
157
 
158
158
  function requiresGuardianApprovalForActor(
159
159
  toolName: string,
160
- input: Record<string, unknown>,
161
160
  executionTarget: ExecutionTarget,
162
161
  ): boolean {
163
162
  // UI surface tools are passive, user-visible operations (cards, forms,
@@ -170,14 +169,14 @@ function requiresGuardianApprovalForActor(
170
169
  // Side-effect tools always require guardian approval for untrusted actors.
171
170
  // Read-only host execution is also blocked because it can leak sensitive
172
171
  // local information (e.g. shell/file reads).
173
- return isSideEffectTool(toolName, input) || executionTarget === "host";
172
+ return isSideEffectTool(toolName) || executionTarget === "host";
174
173
  }
175
174
 
176
175
  function guardianApprovalDeniedMessage(
177
176
  trustClass: ToolContext["trustClass"],
178
177
  toolName: string,
179
178
  ): string {
180
- if (trustClass === "unknown") {
179
+ if (resolveCapabilities(trustClass).sensitiveToolApproval === "deny") {
181
180
  return `Permission denied for "${toolName}": this action requires guardian approval from a verified channel identity.`;
182
181
  }
183
182
  return `Permission denied for "${toolName}": this action requires guardian approval and the current actor is not the guardian.`;
@@ -320,11 +319,14 @@ export class ToolApprovalHandler {
320
319
 
321
320
  const guardianApprovalRequired = requiresGuardianApprovalForActor(
322
321
  name,
323
- input,
324
322
  executionTarget,
325
323
  );
326
324
 
327
- if (isUntrustedTrustClass(context.trustClass) && guardianApprovalRequired) {
325
+ if (
326
+ resolveCapabilities(context.trustClass).sensitiveToolApproval !==
327
+ "self" &&
328
+ guardianApprovalRequired
329
+ ) {
328
330
  const inputDigest = computeToolApprovalDigest(name, input);
329
331
  needsGrantConsumption = true;
330
332
  deferredConsumeParams = {
@@ -532,15 +534,18 @@ export class ToolApprovalHandler {
532
534
 
533
535
  // No matching grant or race condition - deny or wait inline.
534
536
  //
535
- // For verified non-guardian actors (trusted_contact) with sufficient
536
- // context, escalate to the guardian by creating a canonical
537
- // tool_grant_request. Then wait bounded for the grant to become
538
- // available - this lets the tool call succeed inline after guardian
539
- // approval without the requester having to retry manually.
537
+ // For non-guardian actors with established identity (trusted_contact
538
+ // or unverified_contact) and sufficient context, escalate to the
539
+ // guardian by creating a canonical tool_grant_request. Then wait
540
+ // bounded for the grant to become available - this lets the tool call
541
+ // succeed inline after guardian approval without the requester having
542
+ // to retry manually.
540
543
  //
541
- // Unverified actors remain fail-closed with no escalation or wait.
544
+ // Actors with no identity (unknown) remain fail-closed with no
545
+ // escalation or wait.
542
546
  if (
543
- context.trustClass === "trusted_contact" &&
547
+ resolveCapabilities(context.trustClass).sensitiveToolApproval ===
548
+ "escalate-and-wait" &&
544
549
  context.assistantId &&
545
550
  context.executionChannel &&
546
551
  context.requesterExternalUserId
@@ -15,7 +15,6 @@ import { askQuestionTool } from "./ask-question/ask-question-tool.js";
15
15
  import { makeAuthenticatedRequestTool } from "./credential-execution/make-authenticated-request.js";
16
16
  import { manageSecureCommandTool } from "./credential-execution/manage-secure-command-tool.js";
17
17
  import { runAuthenticatedCommandTool } from "./credential-execution/run-authenticated-command.js";
18
- import { credentialStoreTool } from "./credentials/vault.js";
19
18
  import { fileEditTool } from "./filesystem/edit.js";
20
19
  import { fileListTool } from "./filesystem/list.js";
21
20
  import { fileReadTool } from "./filesystem/read.js";
@@ -90,7 +89,6 @@ export const explicitTools: ToolDefinition[] = [
90
89
  // Always-explicit tools
91
90
  rememberTool,
92
91
  recallTool,
93
- credentialStoreTool,
94
92
  notifyParentTool,
95
93
  askQuestionTool,
96
94
  // NOTE: external skill tools (registered via registerExternalTools in
@@ -13,6 +13,7 @@ import { RiskLevel } from "@vellumai/skill-host-contracts";
13
13
  import { z } from "zod";
14
14
 
15
15
  import type { InterfaceId } from "../channels/types.js";
16
+ import type { LLMCallSite } from "../config/schemas/llm.js";
16
17
  import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
17
18
  import type { SecretPromptResult } from "../permissions/secret-prompter.js";
18
19
  import type { ContentBlock } from "../providers/types.js";
@@ -25,6 +26,7 @@ export const DISK_PRESSURE_CLEANUP_TOOL_NAMES: ReadonlySet<string> = new Set([
25
26
  "file_read",
26
27
  "file_list",
27
28
  "host_file_read",
29
+ "skill_load",
28
30
  "background_tool_list",
29
31
  "background_tool_cancel",
30
32
  ]);
@@ -363,6 +365,14 @@ export interface ToolContext {
363
365
  * `executeSubagentSpawn` in tools/subagent/spawn.ts.
364
366
  */
365
367
  overrideProfile?: string;
368
+ /**
369
+ * The LLM call site of the turn currently executing this tool (`mainAgent`,
370
+ * `heartbeatAgent`, scheduled work, etc.). `subagent_spawn` reads it to
371
+ * default a spawned subagent's inference profile to the profile the invoking
372
+ * turn resolved to, so subagents match whatever agent invoked them rather
373
+ * than always falling back to the static `subagentSpawn` call-site default.
374
+ */
375
+ invokingCallSite?: LLMCallSite;
366
376
  /**
367
377
  * Canonical principal ID of the actor on whose behalf this tool invocation
368
378
  * is running. Sourced from `conversation.trustContext.guardianPrincipalId`.
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { RiskLevel } from "../../permissions/types.js";
11
+ import { isWeakOpenModel } from "../../providers/weak-open-model.js";
11
12
  import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
12
13
  import type {
13
14
  ToolContext,
@@ -37,8 +38,9 @@ function proxyExecute(toolName: string) {
37
38
  ): Promise<ToolExecutionResult> => {
38
39
  if (toolName === "ui_show" && isEmptyDynamicPage(input)) {
39
40
  return {
40
- content:
41
- "Error: ui_show dynamic_page requires non-empty HTML in `data.html`. The surface was not displayed because no content was provided — the user would see a blank box. Resend ui_show with the full HTML markup in `data.html`.",
41
+ content: isWeakOpenModel(context.attribution?.resolvedModel)
42
+ ? EMPTY_DYNAMIC_PAGE_DECLARATIVE_REDIRECT
43
+ : EMPTY_DYNAMIC_PAGE_HTML_HINT,
42
44
  isError: true,
43
45
  };
44
46
  }
@@ -59,6 +61,14 @@ function proxyExecute(toolName: string) {
59
61
  };
60
62
  }
61
63
 
64
+ if (toolName === "ui_update" && isEmptyUpdate(input)) {
65
+ return {
66
+ content:
67
+ 'Error: ui_update received an empty `data` payload, so the surface was unchanged — the user still sees its previous state. The provided data is merged into the surface\'s current data, and merging nothing is a no-op. To advance a task_progress card, send the full step list: ui_update { surface_id: "<id>", data: { templateData: { steps: [{ label: "<step>", status: "completed" }, { label: "<step>", status: "in_progress" }] } } }. Resend ui_update with the fields you intend to change under `data`.',
68
+ isError: true,
69
+ };
70
+ }
71
+
62
72
  if (!context.proxyToolResolver) {
63
73
  return {
64
74
  content: `No proxy resolver configured for proxy tool "${toolName}". This tool requires an external resolver (e.g. a connected macOS client).`,
@@ -81,6 +91,27 @@ function proxyExecute(toolName: string) {
81
91
  };
82
92
  }
83
93
 
94
+ /**
95
+ * Rejection envelope for an empty `dynamic_page` ui_show from a capable model:
96
+ * the surface carried no `data.html`, so it would render as a blank box. These
97
+ * models reliably author HTML, so the fix is simply to resend it inline.
98
+ */
99
+ const EMPTY_DYNAMIC_PAGE_HTML_HINT =
100
+ "Error: ui_show dynamic_page requires non-empty HTML in `data.html`. The surface was not displayed because no content was provided — the user would see a blank box. Resend ui_show with the full HTML markup in `data.html`.";
101
+
102
+ /**
103
+ * Rejection envelope for an empty `dynamic_page` ui_show from a weak open model.
104
+ * Authoring full HTML inline is the generation task these models fail at (an
105
+ * empty `data: {}` here, or broken markup otherwise), so steering them to
106
+ * "resend the HTML" just repeats the failure. Most widget requests are really
107
+ * structured data — comparisons, results, metrics — which render reliably via a
108
+ * field-based surface the model populates without writing any HTML, and which
109
+ * cannot render blank. dynamic_page stays available for genuinely custom visual
110
+ * HTML.
111
+ */
112
+ const EMPTY_DYNAMIC_PAGE_DECLARATIVE_REDIRECT =
113
+ 'Error: ui_show dynamic_page was not displayed — `data.html` was empty, so the user would see a blank box. Authoring full HTML inline is error-prone; for data, comparisons, results, or metrics prefer a structured surface, which you fill with fields (no HTML) and which never renders blank. Re-show the content as one of: a `table` (ui_show { surface_type: "table", data: { columns: [{ id, label }], rows: [{ id, cells: { <columnId>: "<value>" } }] } }), a `card` ({ title, body, metadata: [{ label, value }] }), or `work_result` ({ summary, metrics: [{ label, value }] }). Only use dynamic_page when you genuinely need custom visual HTML, in which case include the complete markup in `data.html` now.';
114
+
84
115
  /**
85
116
  * Worked ui_update example, appended to a successful task_progress `ui_show`
86
117
  * result so the model learns the update pattern at the point of use (with the
@@ -98,6 +129,36 @@ function isTaskProgressCardShow(input: Record<string, unknown>): boolean {
98
129
  return data?.template === "task_progress";
99
130
  }
100
131
 
132
+ /**
133
+ * A `ui_update` whose `data` merge would change nothing: missing, not an
134
+ * object, or containing only (recursively) empty objects. Merging such a
135
+ * payload is a silent no-op — the surface keeps its prior state while the
136
+ * client still reports "Surface updated" — so the model never learns its
137
+ * update was hollow and a live card (e.g. task_progress) appears frozen.
138
+ * Arrays (e.g. `templateData.steps`) and any non-empty primitive leaf count
139
+ * as content.
140
+ */
141
+ function isEmptyUpdate(input: Record<string, unknown>): boolean {
142
+ const data = asRecord(input.data);
143
+ return data === null || !hasContent(data);
144
+ }
145
+
146
+ function hasContent(value: unknown): boolean {
147
+ if (value === null || value === undefined) {
148
+ return false;
149
+ }
150
+ if (Array.isArray(value)) {
151
+ return value.length > 0;
152
+ }
153
+ if (typeof value === "object") {
154
+ return Object.values(value).some(hasContent);
155
+ }
156
+ if (typeof value === "string") {
157
+ return value.trim().length > 0;
158
+ }
159
+ return true;
160
+ }
161
+
101
162
  function isEmptyDynamicPage(input: Record<string, unknown>): boolean {
102
163
  if (input.surface_type !== "dynamic_page") {
103
164
  return false;
@@ -311,7 +372,7 @@ export const uiShowTool = {
311
372
  // ui_update
312
373
  // ---------------------------------------------------------------------------
313
374
 
314
- const uiUpdateTool = {
375
+ export const uiUpdateTool = {
315
376
  name: "ui_update",
316
377
  description:
317
378
  "Update an existing surface's data. The provided data object is merged into the surface's current data.\n" +
@@ -10,6 +10,8 @@
10
10
  * /v1/channel-verification-sessions/revoke
11
11
  */
12
12
 
13
+ import { resolveCapabilities } from "../runtime/capabilities.js";
14
+
13
15
  const VERIFICATION_ENDPOINT_PATHS = [
14
16
  "/v1/channel-verification-sessions",
15
17
  "/v1/channel-verification-sessions/resend",
@@ -128,7 +130,7 @@ export function enforceVerificationControlPlanePolicy(
128
130
  return { denied: false };
129
131
  }
130
132
 
131
- if (trustClass === "guardian") {
133
+ if (resolveCapabilities(trustClass).canUseVerificationControlPlane) {
132
134
  return { denied: false };
133
135
  }
134
136