@vellumai/assistant 0.8.12 → 0.9.0-staging.2

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 (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -0,0 +1,547 @@
1
+ /**
2
+ * Tests for `subagentToolGateMode` — the execution-layer tool gating mode
3
+ * used by cache-parity wakes (fork-based memory retrospectives).
4
+ *
5
+ * Covers:
6
+ * - Resolver (`createResolveToolsCallback`): in `"execution"` mode the
7
+ * resolved tool definitions are NOT filtered by the subagent allowlist
8
+ * (the wire request keeps the conversation's full tool surface so the
9
+ * provider prompt-cache prefix stays byte-identical); in `"wire"` mode /
10
+ * absent, the definitions are filtered exactly as before (regression).
11
+ * - Executor (`createToolExecutor`): in `"execution"` mode a call to a
12
+ * non-allowlisted tool returns an error tool_result WITHOUT invoking the
13
+ * tool's executor (safety invariant), while allowlisted calls execute
14
+ * normally. The gate also covers the `skill_execute` indirection by
15
+ * gating the resolved inner tool name, and runs BEFORE every in-executor
16
+ * interception — including `switch_inference_profile`, which must not be
17
+ * able to switch `ctx.toolRoutedProfile` mid-wake (allowlist bypass +
18
+ * per-model prompt-cache parity break).
19
+ */
20
+
21
+ import { describe, expect, mock, test } from "bun:test";
22
+
23
+ import type { SkillProjectionCache } from "../daemon/conversation-skill-tools.js";
24
+ import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
25
+ import type { PermissionPrompter } from "../permissions/prompter.js";
26
+ import type { SecretPrompter } from "../permissions/secret-prompter.js";
27
+ import type { Message, ToolDefinition } from "../providers/types.js";
28
+ import type { ToolExecutor } from "../tools/executor.js";
29
+ import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Module mocks (must precede the import of the module under test)
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const baseConfig = {
36
+ tools: { exclude: [] as string[] },
37
+ timeouts: {
38
+ shellDefaultTimeoutSec: 120,
39
+ shellMaxTimeoutSec: 600,
40
+ permissionTimeoutSec: 300,
41
+ toolExecutionTimeoutSec: 600,
42
+ },
43
+ services: {},
44
+ llm: { profiles: { speedy: { label: "Speedy" } } },
45
+ };
46
+
47
+ mock.module("../config/loader.js", () => ({
48
+ getConfig: () => baseConfig,
49
+ loadConfig: () => baseConfig,
50
+ invalidateConfigCache: () => {},
51
+ loadRawConfig: () => ({}),
52
+ saveRawConfig: () => {},
53
+ getNestedValue: () => undefined,
54
+ setNestedValue: () => {},
55
+ }));
56
+
57
+ mock.module("../runtime/assistant-event-hub.js", () => ({
58
+ broadcastMessage: mock(() => {}),
59
+ assistantEventHub: { listClientsByCapability: () => [] },
60
+ }));
61
+
62
+ mock.module("../daemon/conversation-surfaces.js", () => ({
63
+ refreshSurfacesForApp: mock(() => {}),
64
+ surfaceProxyResolver: mock(() =>
65
+ Promise.resolve({ content: "", isError: false }),
66
+ ),
67
+ }));
68
+
69
+ mock.module("../services/published-app-updater.js", () => ({
70
+ updatePublishedAppDeployment: mock(() => Promise.resolve()),
71
+ }));
72
+
73
+ mock.module("../tools/browser/browser-screencast.js", () => ({
74
+ registerConversationSender: mock(() => {}),
75
+ }));
76
+
77
+ mock.module("../memory/app-store.js", () => ({
78
+ getApp: mock(() => null),
79
+ getAppDirPath: mock(() => "/tmp/test-apps/dummy"),
80
+ isMultifileApp: mock(() => false),
81
+ getAppsDir: mock(() => "/tmp/test-apps"),
82
+ resolveAppIdByDirName: mock(() => null),
83
+ resolveAppIdFromPath: mock(() => null),
84
+ }));
85
+
86
+ // Controls the skill tools the projection reports per resolver call.
87
+ let projectedSkillToolNames: string[] = [];
88
+
89
+ mock.module("../daemon/conversation-skill-tools.js", () => ({
90
+ projectSkillTools: mock(() => ({
91
+ allowedToolNames: new Set(projectedSkillToolNames),
92
+ toolDefinitions: [],
93
+ })),
94
+ }));
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Imports after mocks are in place
98
+ // ---------------------------------------------------------------------------
99
+
100
+ import {
101
+ createResolveToolsCallback,
102
+ createToolExecutor,
103
+ type SkillProjectionContext,
104
+ type ToolSetupContext,
105
+ } from "../daemon/conversation-tool-setup.js";
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Helpers
109
+ // ---------------------------------------------------------------------------
110
+
111
+ const EMPTY_HISTORY: Message[] = [];
112
+
113
+ function makeToolDef(name: string): ToolDefinition {
114
+ return { name, description: `${name} tool`, input_schema: {} };
115
+ }
116
+
117
+ function makeProjectionCtx(
118
+ overrides: Partial<SkillProjectionContext> = {},
119
+ ): SkillProjectionContext {
120
+ return {
121
+ skillProjectionState: new Map(),
122
+ skillProjectionCache: {} as SkillProjectionCache,
123
+ coreToolNames: new Set(["remember", "tool_b"]),
124
+ toolsDisabledDepth: 0,
125
+ ...overrides,
126
+ };
127
+ }
128
+
129
+ function makeSetupCtx(
130
+ overrides: Partial<ToolSetupContext> = {},
131
+ ): ToolSetupContext {
132
+ return {
133
+ conversationId: "conv-test",
134
+ currentRequestId: "req-1",
135
+ workingDir: "/tmp/test",
136
+ abortController: null,
137
+ traceEmitter: { emit: () => {} },
138
+ sendToClient: mock(() => {}),
139
+ pendingSurfaceActions: new Map(),
140
+ lastSurfaceAction: new Map(),
141
+ surfaceState: new Map<
142
+ string,
143
+ { surfaceType: SurfaceType; data: SurfaceData; title?: string }
144
+ >(),
145
+ surfaceUndoStacks: new Map(),
146
+ accumulatedSurfaceState: new Map(),
147
+ surfaceActionRequestIds: new Set<string>(),
148
+ currentTurnSurfaces: [],
149
+ isProcessing: () => false,
150
+ enqueueMessage: () => ({ queued: false, requestId: "r" }),
151
+ getQueueDepth: () => 0,
152
+ processMessage: async () => "",
153
+ withSurface: async <T>(_id: string, fn: () => T | Promise<T>) => fn(),
154
+ ...overrides,
155
+ };
156
+ }
157
+
158
+ /** Fake ToolExecutor that records every execute() invocation. */
159
+ function makeCapturingExecutor() {
160
+ const calls: Array<{ name: string; input: Record<string, unknown> }> = [];
161
+ const executor = {
162
+ execute: async (
163
+ name: string,
164
+ input: Record<string, unknown>,
165
+ _context: ToolContext,
166
+ ): Promise<ToolExecutionResult> => {
167
+ calls.push({ name, input });
168
+ return { content: "ok", isError: false };
169
+ },
170
+ };
171
+ return { executor: executor as unknown as ToolExecutor, calls };
172
+ }
173
+
174
+ const noopPrompter = {
175
+ prompt: mock(async () => ({ decision: "allow" as const })),
176
+ } as unknown as PermissionPrompter;
177
+ const noopSecretPrompter = {
178
+ prompt: mock(async () => ({ cancelled: true })),
179
+ } as unknown as SecretPrompter;
180
+
181
+ function makeToolFn(executor: ToolExecutor, ctx: ToolSetupContext) {
182
+ return createToolExecutor(
183
+ executor,
184
+ noopPrompter,
185
+ noopSecretPrompter,
186
+ ctx,
187
+ () => {},
188
+ );
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Resolver — wire vs execution gate mode
193
+ // ---------------------------------------------------------------------------
194
+
195
+ describe("createResolveToolsCallback — subagentToolGateMode", () => {
196
+ test("wire mode (default) filters resolved defs to the subagent allowlist (regression)", () => {
197
+ projectedSkillToolNames = ["skill_tool_x"];
198
+ const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
199
+ const ctx = makeProjectionCtx({
200
+ subagentAllowedTools: new Set(["remember"]),
201
+ });
202
+ const resolve = createResolveToolsCallback(toolDefs, ctx)!;
203
+
204
+ const tools = resolve(EMPTY_HISTORY);
205
+
206
+ expect(tools.map((t) => t.name)).toEqual(["remember"]);
207
+ expect(ctx.allowedToolNames).toEqual(new Set(["remember"]));
208
+ });
209
+
210
+ test("execution mode keeps the full tool surface on the wire", () => {
211
+ projectedSkillToolNames = ["skill_tool_x"];
212
+ const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
213
+ const ctx = makeProjectionCtx({
214
+ subagentAllowedTools: new Set(["remember"]),
215
+ subagentToolGateMode: "execution",
216
+ });
217
+ const resolve = createResolveToolsCallback(toolDefs, ctx)!;
218
+
219
+ const tools = resolve(EMPTY_HISTORY);
220
+
221
+ // Defs are NOT filtered by the allowlist — byte-identical to a turn
222
+ // without any subagent allowlist.
223
+ expect(tools.map((t) => t.name).sort()).toEqual(["remember", "tool_b"]);
224
+ // Skill-projection availability is also not narrowed: the execution-layer
225
+ // gate in the executor callback owns enforcement.
226
+ expect(ctx.allowedToolNames).toEqual(
227
+ new Set(["remember", "tool_b", "skill_tool_x"]),
228
+ );
229
+ });
230
+
231
+ test("execution mode resolves the same defs as having no allowlist at all", () => {
232
+ projectedSkillToolNames = [];
233
+ const toolDefs = [makeToolDef("remember"), makeToolDef("tool_b")];
234
+
235
+ const unscoped = createResolveToolsCallback(toolDefs, makeProjectionCtx())!(
236
+ EMPTY_HISTORY,
237
+ );
238
+ const executionScoped = createResolveToolsCallback(
239
+ toolDefs,
240
+ makeProjectionCtx({
241
+ subagentAllowedTools: new Set(["remember"]),
242
+ subagentToolGateMode: "execution",
243
+ }),
244
+ )!(EMPTY_HISTORY);
245
+
246
+ expect(executionScoped).toEqual(unscoped);
247
+ });
248
+ });
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Resolver — toolContextPin (wire tool-surface parity for execution wakes)
252
+ // ---------------------------------------------------------------------------
253
+
254
+ describe("createResolveToolsCallback — toolContextPin", () => {
255
+ // Core defs spanning each client-gated family: host proxy (host_bash),
256
+ // dynamic UI (ui_show), connected-client (app_open), client-platform
257
+ // (request_system_permission), plus the always-on remember.
258
+ const CLIENT_GATED_DEFS = [
259
+ makeToolDef("remember"),
260
+ makeToolDef("host_bash"),
261
+ makeToolDef("ui_show"),
262
+ makeToolDef("app_open"),
263
+ makeToolDef("request_system_permission"),
264
+ ];
265
+
266
+ /** Execution-gate ctx shaped like a clientless fork-retrospective wake. */
267
+ function clientlessExecutionCtx(
268
+ overrides: Partial<SkillProjectionContext> = {},
269
+ ): SkillProjectionContext {
270
+ return makeProjectionCtx({
271
+ coreToolNames: new Set(CLIENT_GATED_DEFS.map((d) => d.name)),
272
+ hasNoClient: true,
273
+ subagentAllowedTools: new Set(["remember"]),
274
+ subagentToolGateMode: "execution",
275
+ ...overrides,
276
+ });
277
+ }
278
+
279
+ test("control: without a pin, a clientless fork drops every client-gated tool from the wire", () => {
280
+ projectedSkillToolNames = [];
281
+ const resolve = createResolveToolsCallback(
282
+ CLIENT_GATED_DEFS,
283
+ clientlessExecutionCtx(),
284
+ )!;
285
+
286
+ expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
287
+ });
288
+
289
+ test("a desktop-source pin restores the host/UI/client tool defs on the wire", () => {
290
+ projectedSkillToolNames = [];
291
+ const resolve = createResolveToolsCallback(
292
+ CLIENT_GATED_DEFS,
293
+ clientlessExecutionCtx({
294
+ toolContextPin: { hasNoClient: false, transportInterface: "macos" },
295
+ }),
296
+ )!;
297
+
298
+ const names = resolve(EMPTY_HISTORY)
299
+ .map((t) => t.name)
300
+ .sort();
301
+ // request_system_permission stays out: it keys on
302
+ // channelCapabilities.clientOS, which desktop HTTP live turns never set
303
+ // either — exclusion IS parity there.
304
+ expect(names).toEqual(["app_open", "host_bash", "remember", "ui_show"]);
305
+ });
306
+
307
+ test("the pin REPLACES the live context — absent pin fields do not fall through", () => {
308
+ projectedSkillToolNames = [];
309
+ // Live ctx claims an interactive macOS client; the pin says clientless.
310
+ // Client-gated tools must drop, proving pinned-undefined beats live.
311
+ const resolve = createResolveToolsCallback(
312
+ CLIENT_GATED_DEFS,
313
+ clientlessExecutionCtx({
314
+ hasNoClient: false,
315
+ transportInterface: "macos",
316
+ toolContextPin: { hasNoClient: true },
317
+ }),
318
+ )!;
319
+
320
+ expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
321
+ });
322
+
323
+ test("invariant: a pinned-in tool is on the wire but can never execute", async () => {
324
+ projectedSkillToolNames = [];
325
+ const pin = { hasNoClient: false, transportInterface: "macos" as const };
326
+ const resolve = createResolveToolsCallback(
327
+ CLIENT_GATED_DEFS,
328
+ clientlessExecutionCtx({ toolContextPin: pin }),
329
+ )!;
330
+ expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toContain("host_bash");
331
+
332
+ // The pin affects tool-DEFINITION resolution only: the executor-level
333
+ // gate never reads it, so the pinned-in host tool is rejected before
334
+ // any executor dispatch.
335
+ const { executor, calls } = makeCapturingExecutor();
336
+ const toolFn = makeToolFn(
337
+ executor,
338
+ makeSetupCtx({
339
+ hasNoClient: true,
340
+ subagentAllowedTools: new Set(["remember"]),
341
+ subagentToolGateMode: "execution",
342
+ }),
343
+ );
344
+
345
+ const result = await toolFn("host_bash", { command: "echo hi" });
346
+
347
+ expect(result).toEqual({
348
+ content: "This background pass may only use: remember.",
349
+ isError: true,
350
+ });
351
+ expect(calls).toHaveLength(0);
352
+ });
353
+ });
354
+
355
+ // ---------------------------------------------------------------------------
356
+ // Executor — execution-layer allowlist gate
357
+ // ---------------------------------------------------------------------------
358
+
359
+ describe("createToolExecutor — execution-layer allowlist gate", () => {
360
+ test("execution mode: non-allowlisted call returns an error tool_result and never invokes the executor", async () => {
361
+ const { executor, calls } = makeCapturingExecutor();
362
+ const toolFn = makeToolFn(
363
+ executor,
364
+ makeSetupCtx({
365
+ subagentAllowedTools: new Set(["remember"]),
366
+ subagentToolGateMode: "execution",
367
+ }),
368
+ );
369
+
370
+ const result = await toolFn("bash", { command: "echo hi" });
371
+
372
+ expect(result).toEqual({
373
+ content: "This background pass may only use: remember.",
374
+ isError: true,
375
+ });
376
+ // Safety invariant: the non-allowlisted tool's executor must never run.
377
+ expect(calls).toHaveLength(0);
378
+ });
379
+
380
+ test("execution mode: allowlisted call executes normally", async () => {
381
+ const { executor, calls } = makeCapturingExecutor();
382
+ const toolFn = makeToolFn(
383
+ executor,
384
+ makeSetupCtx({
385
+ subagentAllowedTools: new Set(["remember"]),
386
+ subagentToolGateMode: "execution",
387
+ }),
388
+ );
389
+
390
+ const result = await toolFn("remember", { content: "a fact" });
391
+
392
+ expect(result).toEqual({ content: "ok", isError: false });
393
+ expect(calls).toHaveLength(1);
394
+ expect(calls[0]!.name).toBe("remember");
395
+ });
396
+
397
+ test("execution mode: skill_execute gates the resolved inner tool, executor never invoked", async () => {
398
+ const { executor, calls } = makeCapturingExecutor();
399
+ const toolFn = makeToolFn(
400
+ executor,
401
+ makeSetupCtx({
402
+ subagentAllowedTools: new Set(["remember"]),
403
+ subagentToolGateMode: "execution",
404
+ }),
405
+ );
406
+
407
+ const rejected = await toolFn("skill_execute", {
408
+ tool: "bash",
409
+ input: { command: "echo hi" },
410
+ });
411
+ expect(rejected).toEqual({
412
+ content: "This background pass may only use: remember.",
413
+ isError: true,
414
+ });
415
+ expect(calls).toHaveLength(0);
416
+
417
+ const allowed = await toolFn("skill_execute", {
418
+ tool: "remember",
419
+ input: { content: "a fact" },
420
+ });
421
+ expect(allowed).toEqual({ content: "ok", isError: false });
422
+ expect(calls).toHaveLength(1);
423
+ expect(calls[0]!.name).toBe("remember");
424
+ });
425
+
426
+ test("execution mode: multi-entry allowlist renders sorted in the rejection message", async () => {
427
+ const { executor, calls } = makeCapturingExecutor();
428
+ const toolFn = makeToolFn(
429
+ executor,
430
+ makeSetupCtx({
431
+ subagentAllowedTools: new Set(["remember", "file_read"]),
432
+ subagentToolGateMode: "execution",
433
+ }),
434
+ );
435
+
436
+ const result = await toolFn("bash", {});
437
+
438
+ expect(result).toEqual({
439
+ content: "This background pass may only use: file_read, remember.",
440
+ isError: true,
441
+ });
442
+ expect(calls).toHaveLength(0);
443
+ });
444
+
445
+ test("execution mode: switch_inference_profile is rejected BEFORE its interception runs", async () => {
446
+ const { executor, calls } = makeCapturingExecutor();
447
+ const ctx = makeSetupCtx({
448
+ subagentAllowedTools: new Set(["remember"]),
449
+ subagentToolGateMode: "execution",
450
+ });
451
+ const toolFn = makeToolFn(executor, ctx);
452
+
453
+ const result = await toolFn("switch_inference_profile", {
454
+ profile: "speedy",
455
+ });
456
+
457
+ // The gate must fire before the interception: the rejection tool_result
458
+ // comes back (NOT the interception's "Switched to ..." / "not found"
459
+ // responses) and the routed profile is untouched — switching it mid-wake
460
+ // would bypass the allowlist and break per-model prompt-cache parity.
461
+ expect(result).toEqual({
462
+ content: "This background pass may only use: remember.",
463
+ isError: true,
464
+ });
465
+ expect(ctx.toolRoutedProfile).toBeUndefined();
466
+ expect(calls).toHaveLength(0);
467
+ });
468
+
469
+ test("switch_inference_profile interception still works when the gate is inert (regression)", async () => {
470
+ // No execution-mode allowlist (wire mode) — the interception must keep
471
+ // its historical behavior: switch the routed profile without ever
472
+ // touching the tool executor pipeline.
473
+ const { executor, calls } = makeCapturingExecutor();
474
+ const ctx = makeSetupCtx({
475
+ subagentAllowedTools: new Set(["remember"]),
476
+ subagentToolGateMode: "wire",
477
+ });
478
+ const toolFn = makeToolFn(executor, ctx);
479
+
480
+ const result = await toolFn("switch_inference_profile", {
481
+ profile: "speedy",
482
+ });
483
+
484
+ expect(result).toEqual({
485
+ content: "Switched to Speedy profile. Continue with your response.",
486
+ isError: false,
487
+ });
488
+ expect(ctx.toolRoutedProfile).toBe("speedy");
489
+ expect(calls).toHaveLength(0);
490
+ });
491
+
492
+ test("execution mode: allowlisted switch_inference_profile still reaches the interception", async () => {
493
+ // When the orchestrator explicitly allowlists the routing tool, the gate
494
+ // passes and the interception behaves normally.
495
+ const { executor, calls } = makeCapturingExecutor();
496
+ const ctx = makeSetupCtx({
497
+ subagentAllowedTools: new Set(["remember", "switch_inference_profile"]),
498
+ subagentToolGateMode: "execution",
499
+ });
500
+ const toolFn = makeToolFn(executor, ctx);
501
+
502
+ const result = await toolFn("switch_inference_profile", {
503
+ profile: "speedy",
504
+ });
505
+
506
+ expect(result).toEqual({
507
+ content: "Switched to Speedy profile. Continue with your response.",
508
+ isError: false,
509
+ });
510
+ expect(ctx.toolRoutedProfile).toBe("speedy");
511
+ expect(calls).toHaveLength(0);
512
+ });
513
+
514
+ test("wire mode (and absent mode) leaves the executor path unchanged (regression)", async () => {
515
+ // In wire mode the allowlist is enforced by filtering the wire defs (and
516
+ // by the executor pipeline's own allowedToolNames gate) — the new
517
+ // execution-layer rejection must stay inert.
518
+ for (const subagentToolGateMode of ["wire", undefined] as const) {
519
+ const { executor, calls } = makeCapturingExecutor();
520
+ const toolFn = makeToolFn(
521
+ executor,
522
+ makeSetupCtx({
523
+ subagentAllowedTools: new Set(["remember"]),
524
+ subagentToolGateMode,
525
+ }),
526
+ );
527
+
528
+ const result = await toolFn("bash", { command: "echo hi" });
529
+
530
+ expect(result).toEqual({ content: "ok", isError: false });
531
+ expect(calls).toHaveLength(1);
532
+ }
533
+ });
534
+
535
+ test("execution mode without an allowlist gates nothing", async () => {
536
+ const { executor, calls } = makeCapturingExecutor();
537
+ const toolFn = makeToolFn(
538
+ executor,
539
+ makeSetupCtx({ subagentToolGateMode: "execution" }),
540
+ );
541
+
542
+ const result = await toolFn("bash", { command: "echo hi" });
543
+
544
+ expect(result).toEqual({ content: "ok", isError: false });
545
+ expect(calls).toHaveLength(1);
546
+ });
547
+ });
@@ -1326,7 +1326,7 @@ describe("buildSystemPrompt", () => {
1326
1326
  const result = buildSystemPrompt();
1327
1327
  expect(result).toContain("## Credential Security");
1328
1328
  expect(result).toContain("Never ask users to share secrets");
1329
- expect(result).toContain("`credential_store` tool");
1329
+ expect(result).toContain("`assistant credentials prompt`");
1330
1330
  });
1331
1331
 
1332
1332
  test("renders after the access-preference section to preserve original order", () => {