@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
@@ -56,6 +56,12 @@ export interface AccessRequestParams {
56
56
  previousMemberStatus?: Exclude<ChannelStatus, "unverified">;
57
57
  /** Preview of the requester's original message, shown to the guardian. */
58
58
  messagePreview?: string;
59
+ /** Slack-specific: user is from an external workspace (Slack Connect). */
60
+ isStranger?: boolean;
61
+ /** Slack-specific: user is a guest / restricted account. */
62
+ isRestricted?: boolean;
63
+ /** Slack message timestamp for permalink construction. */
64
+ messageTs?: string;
59
65
  }
60
66
 
61
67
  export type AccessRequestResult =
@@ -92,6 +98,9 @@ export function notifyGuardianOfAccessRequest(
92
98
  actorUsername,
93
99
  previousMemberStatus,
94
100
  messagePreview,
101
+ isStranger,
102
+ isRestricted,
103
+ messageTs,
95
104
  } = params;
96
105
 
97
106
  if (!actorExternalId) {
@@ -193,15 +202,10 @@ export function notifyGuardianOfAccessRequest(
193
202
 
194
203
  let vellumDeliveryId: string | null = null;
195
204
  // When the access request originates from a text channel with
196
- // notification delivery support (Slack, Telegram), route the guardian
197
- // notification to that same channel only. Delivering on the macOS
198
- // client as well is noisy and approving from there doesn't work
199
- // because the desktop path lacks the channel delivery context needed
200
- // to deliver the verification code. Phone is excluded because it is
201
- // not a deliverable notification channel.
202
- // When the guardian was resolved via a verified same-channel contact,
203
- // route only to that channel — delivering on desktop as well is noisy
204
- // and the desktop path lacks the channel delivery context for approval.
205
+ // notification delivery support (Slack, Telegram) and the guardian was
206
+ // resolved via a verified same-channel contact, route the notification
207
+ // only to that channel to reduce noise. Phone is excluded because it
208
+ // is not a deliverable notification channel.
205
209
  // When the guardian was NOT verified on the source channel (e.g. resolved
206
210
  // via vellum anchor), route to all channels so the guardian can see
207
211
  // the request on desktop/other channels where they ARE verified.
@@ -238,6 +242,9 @@ export function notifyGuardianOfAccessRequest(
238
242
  guardianResolutionSource,
239
243
  previousMemberStatus: previousMemberStatus ?? null,
240
244
  messagePreview: messagePreview ?? null,
245
+ ...(isStranger !== undefined ? { isStranger } : {}),
246
+ ...(isRestricted !== undefined ? { isRestricted } : {}),
247
+ ...(messageTs ? { messageTs } : {}),
241
248
  },
242
249
  dedupeKey: `access-request:${canonicalRequest.id}`,
243
250
  onConversationCreated: (info) => {
@@ -7,6 +7,13 @@
7
7
  *
8
8
  * Semantics:
9
9
  * - Resolves the conversation context exactly as a normal user turn.
10
+ * - Runs the conversation's auto-threshold compaction gate
11
+ * (`conversation.maybeCompact()`) before snapshotting the history —
12
+ * the wake bypasses the orchestrator's in-loop compaction, so this is
13
+ * its turn-start equivalent. Callers can pass
14
+ * `suppressAutoCompaction: true` to skip it; a suppressed wake whose
15
+ * input exceeds the effective context window fails deterministically
16
+ * with `reason: "context_overflow"` instead of compacting.
10
17
  * - Appends `hint` as a non-persisted assistant message sandwiched
11
18
  * between two static user messages — never shows up in the transcript
12
19
  * or SSE feed. The assistant role prevents prompt injection (LLMs
@@ -56,6 +63,11 @@ import {
56
63
  classifyDiskPressureTurnPolicy,
57
64
  type DiskPressureTurnPolicyDecision,
58
65
  } from "../daemon/disk-pressure-policy.js";
66
+ import { looksLikeContextOverflowError } from "../daemon/parse-actual-tokens-from-error.js";
67
+ import type {
68
+ SubagentToolGateMode,
69
+ WakeToolContextPin,
70
+ } from "../daemon/tool-setup-types.js";
59
71
  import type { TrustContext } from "../daemon/trust-context.js";
60
72
  import {
61
73
  broadcastWakeSurface,
@@ -73,6 +85,7 @@ import {
73
85
  recordRequestLog,
74
86
  setAgentLoopExitReasonOnLatestLog,
75
87
  } from "../memory/llm-request-log-store.js";
88
+ import type { SystemPromptPersonaOverride } from "../prompts/system-prompt.js";
76
89
  import type { Message } from "../providers/types.js";
77
90
  import { getLogger } from "../util/logger.js";
78
91
 
@@ -86,6 +99,14 @@ const WAKE_PREAMBLE =
86
99
  const WAKE_POSTAMBLE =
87
100
  "[system] End of message from external system, continue the conversation.";
88
101
 
102
+ /**
103
+ * Warn line shared by the two reactive over-window failure sites (provider
104
+ * rejection escaped as a throw / swallowed into a no-output stop). The
105
+ * pre-flight estimate site logs its own distinct message.
106
+ */
107
+ const OVER_WINDOW_REJECTION_LOG_MESSAGE =
108
+ "agent-wake: provider rejected the input as over-window with auto-compaction suppressed; failing the wake";
109
+
89
110
  export interface WakeOptions {
90
111
  conversationId: string;
91
112
  hint: string;
@@ -113,6 +134,19 @@ export interface WakeOptions {
113
134
  * tune the model/profile and observability bucket independently.
114
135
  */
115
136
  callSite?: LLMCallSite;
137
+ /**
138
+ * Run the wake's LLM calls under this inference profile, floated ABOVE the
139
+ * call site's named profile and call-site overrides (the resolver's
140
+ * `forceOverrideProfile` escape hatch). When set, it replaces the
141
+ * conversation's own pinned-profile lookup. Used by fork-based memory
142
+ * retrospectives to resolve the SAME model/thinking/effort as the source
143
+ * conversation's turns so the provider prompt-cache prefix can be reused.
144
+ * A profile name that no longer exists in `llm.profiles` silently falls
145
+ * back to normal call-site resolution (the resolver's standard
146
+ * missing-reference semantics). Logging/attribution still bucket under
147
+ * `callSite`.
148
+ */
149
+ forceOverrideProfile?: string;
116
150
  /**
117
151
  * Role to use for the injected hint message. Defaults to `"assistant"` so
118
152
  * the hint is sandwiched between two static user bookends — the canonical
@@ -124,22 +158,25 @@ export interface WakeOptions {
124
158
  */
125
159
  hintRole?: "assistant" | "user";
126
160
  /**
127
- * Documented intent: this wake must not trigger auto-threshold compaction.
161
+ * Skip the wake's pre-run auto-threshold compaction gate and refuse to
162
+ * run over-window instead of compacting.
128
163
  *
129
- * Today this is automatically satisfied because the wake invokes
130
- * `conversation.agentLoop.run()` directly, bypassing the daemon orchestrator
131
- * (`conversation-agent-loop.ts`) where the compaction pipeline lives. The
132
- * flag is recorded in the wake's structured log line so operators can
133
- * verify the contract holds across refactors. If compaction is ever moved
134
- * into `AgentLoop.run` or invoked from the wake path, callers that pass
135
- * `true` here MUST be updated to suppress it; callers that pass `false`
136
- * (or omit it) MUST tolerate compaction firing.
164
+ * By default the wake runs `conversation.maybeCompact()` before
165
+ * snapshotting the history — the wake invokes `conversation.agentLoop.run()`
166
+ * directly with the loop's in-loop budget gate disabled, so this pre-run
167
+ * gate is the wake path's equivalent of the turn-start compaction the
168
+ * daemon orchestrator (`conversation-agent-loop.ts`) performs for user
169
+ * turns. Passing `true` skips the gate entirely; if the wake input would
170
+ * then exceed the effective context window, the wake fails fast with
171
+ * `reason: "context_overflow"` instead of compacting, and a provider
172
+ * context-overflow rejection mid-run is mapped to the same failure.
137
173
  *
138
174
  * Used by fork-based memory retrospectives: the wake operates on a
139
- * freshly-forked conversation that may already be near (or past) the
140
- * source's auto-threshold, but the goal is to operate on that exact
141
- * context — running a compaction LLM call before the wake's own first
142
- * call would waste tokens and defeat prompt-cache reuse.
175
+ * freshly-forked throwaway conversation that may already be near (or
176
+ * past) the source's auto-threshold, but the goal is to operate on that
177
+ * exact context — running a compaction LLM call before the wake's own
178
+ * first call would waste tokens, defeat prompt-cache reuse, and fire
179
+ * compaction side-effects on a fork that is deleted afterwards.
143
180
  */
144
181
  suppressAutoCompaction?: boolean;
145
182
  /**
@@ -161,9 +198,48 @@ export interface WakeOptions {
161
198
  /**
162
199
  * Optional exact tool allowlist for this wake. Used by internal maintenance
163
200
  * jobs that need the assistant's judgment but must not execute arbitrary
164
- * side-effect tools.
201
+ * side-effect tools. Enforcement depends on `toolGateMode`: in `"wire"`
202
+ * mode (default) the tool definitions sent to the provider are filtered to
203
+ * the allowlist; in `"execution"` mode the full tool surface stays on the
204
+ * wire and non-allowlisted calls are rejected with an error tool_result
205
+ * before their executor runs. Either way, only allowlisted tools can
206
+ * execute during the wake.
165
207
  */
166
208
  allowedTools?: readonly string[];
209
+ /**
210
+ * How `allowedTools` is enforced — see {@link SubagentToolGateMode} for the
211
+ * wire-vs-execution semantics and cache-parity rationale. Defaults to
212
+ * `"wire"` (the historical behavior, byte-identical when absent). Ignored
213
+ * when `allowedTools` is absent.
214
+ */
215
+ toolGateMode?: SubagentToolGateMode;
216
+ /**
217
+ * Client-context pin applied (and restored) alongside `allowedTools` for
218
+ * the duration of the wake — see {@link WakeToolContextPin}. Pass only
219
+ * with `toolGateMode: "execution"`: it exists purely so the wire tool
220
+ * definitions resolve under the SOURCE conversation's client context
221
+ * (provider prompt-cache parity) and is pointless when the wire is
222
+ * allowlist-filtered anyway. Definition resolution only — pinned-in tools
223
+ * remain execution-rejected by the gate. Ignored when `allowedTools` is
224
+ * absent.
225
+ */
226
+ toolContextPin?: WakeToolContextPin;
227
+ /**
228
+ * Explicit persona/channel slugs for the wake's system-prompt build,
229
+ * applied to the conversation for the duration of the run and restored
230
+ * afterwards. Wakes bypass the orchestrator's turn-start persona snapshot,
231
+ * so their prompt is otherwise built from whatever snapshot the
232
+ * conversation already holds — for a freshly hydrated conversation (the
233
+ * fork-retrospective case) that is the no-trust-context derivation
234
+ * (guardian persona + "vellum" channel) regardless of which actor/channel
235
+ * the conversation belongs to. Used by fork-based memory retrospectives to
236
+ * render the SOURCE conversation's persona sections — both for review
237
+ * quality and for byte-parity with the source's cached system-prompt
238
+ * prefix. May also pin `hasNoClient` for the prompt build (see
239
+ * {@link SystemPromptPersonaOverride}). Prompt-build selection only; trust
240
+ * class and approval semantics are governed solely by `trustContext`.
241
+ */
242
+ personaOverride?: SystemPromptPersonaOverride;
167
243
  }
168
244
 
169
245
  /**
@@ -177,7 +253,14 @@ export type WakeSkipReason =
177
253
  | "archived"
178
254
  | "timeout"
179
255
  | "no_resolver"
180
- | "disk_pressure";
256
+ | "disk_pressure"
257
+ /**
258
+ * The wake input exceeds the effective context window and the caller
259
+ * suppressed auto-compaction (`suppressAutoCompaction: true`), so the
260
+ * run cannot proceed without the compaction it was told not to perform.
261
+ * Only possible on suppressed wakes.
262
+ */
263
+ | "context_overflow";
181
264
 
182
265
  export interface WakeResult {
183
266
  invoked: boolean;
@@ -331,10 +414,12 @@ function classifyWakeDiskPressurePolicy(opts: WakeOptions): {
331
414
  }
332
415
 
333
416
  /**
334
- * Trust snapshot the wake hands to the agent loop. A wake has no compaction
335
- * path (it runs with overflow recovery disabled), so this snapshot is unread
336
- * except on the disk-pressure cleanup-mode path, whose guardian value scopes
337
- * the compactor's image manifest if cleanup ever compacts. Other wakes pass an
417
+ * Trust snapshot the wake hands to the agent loop. The wake's loop run has
418
+ * no in-loop compaction path (it runs with overflow recovery disabled; the
419
+ * wake compacts via the pre-run gate instead, which resolves trust from the
420
+ * conversation's own trust context), so this snapshot is unread except on
421
+ * the disk-pressure cleanup-mode path, whose guardian value scopes the
422
+ * compactor's image manifest if cleanup ever compacts. Other wakes pass an
338
423
  * `unknown`-class snapshot so a missing actor cannot grant elevated trust.
339
424
  */
340
425
  function buildWakeTrust(
@@ -420,6 +505,7 @@ export async function wakeAgentForOpportunity(
420
505
  deps?: WakeDeps,
421
506
  ): Promise<WakeResult> {
422
507
  const { conversationId, hint, source } = opts;
508
+ const suppressAutoCompaction = opts.suppressAutoCompaction === true;
423
509
  const resolveTarget = deps?.resolveTarget ?? defaultResolveTarget;
424
510
  const nowFn = deps?.now ?? Date.now;
425
511
  const startedAt = nowFn();
@@ -486,6 +572,85 @@ export async function wakeAgentForOpportunity(
486
572
  conversation.setTrustContext(opts.trustContext);
487
573
  }
488
574
 
575
+ // Honor the conversation's pinned inference-profile override (if any).
576
+ // Without this, scheduled-task wakes and other opportunity wakes bypass
577
+ // `runAgentLoopImpl` entirely and execute under workspace defaults,
578
+ // silently violating the user's pinned preference. A caller-supplied
579
+ // `forceOverrideProfile` replaces that lookup and additionally floats the
580
+ // profile above the call-site layers (see the option's doc). Resolve the
581
+ // effective context budget here as well because wakes bypass the normal
582
+ // user-turn path that computes it for tool-result truncation. Read before
583
+ // `setProcessing(true)` so a thrown DB/config read can't strand the
584
+ // processing flag.
585
+ const forceOverrideProfile = opts.forceOverrideProfile !== undefined;
586
+ const overrideProfile =
587
+ opts.forceOverrideProfile ??
588
+ getConversationOverrideProfile(conversationId);
589
+ const callSite = opts.callSite ?? "mainAgent";
590
+ const config = getConfig();
591
+ const effectiveContextWindow = resolveEffectiveContextWindow({
592
+ llm: config.llm,
593
+ callSite,
594
+ overrideProfile,
595
+ forceOverrideProfile,
596
+ });
597
+
598
+ // Apply the caller's persona override for the duration of the run. The
599
+ // wake's agent loop builds the system prompt through the conversation's
600
+ // resolveSystemPrompt callback, which reads this field; cleared (below,
601
+ // before drainQueue) so a queued user turn never builds its prompt under
602
+ // the wake's override. Assigned only AFTER the profile/config reads above
603
+ // — those can throw, and they run before the try/finally that clears the
604
+ // override, so an earlier assignment would strand the override on the
605
+ // cached Conversation and corrupt every later prompt build on it.
606
+ if (opts.personaOverride) {
607
+ conversation.wakePersonaOverride = opts.personaOverride;
608
+ }
609
+ const clearWakePersonaOverride = (): void => {
610
+ if (opts.personaOverride) {
611
+ conversation.wakePersonaOverride = undefined;
612
+ }
613
+ };
614
+
615
+ // Mark processing for the duration of the wake — including the pre-run
616
+ // compaction gate below, whose summary LLM call must not race a user
617
+ // send into a concurrent agent loop on the same conversation. A user
618
+ // message arriving while the flag is set is queued by `enqueueMessage()`
619
+ // and drained after the wake's tail is pushed + persisted. This happens
620
+ // before applying a wake-scoped tool allowlist so a concurrent user turn
621
+ // cannot start under the wake's restricted tool set.
622
+ conversation.setProcessing(true);
623
+
624
+ // ── Pre-run auto-compaction gate ──────────────────────────────────
625
+ // The wake invokes `conversation.agentLoop.run()` with the loop's
626
+ // in-loop budget gate disabled (see `resolveContextWindow` below), so
627
+ // the orchestrator's turn-start compaction never fires for wakes.
628
+ // Mirror it here: run the conversation's auto-threshold compaction
629
+ // before snapshotting the baseline — a successful compaction replaces
630
+ // `conversation.messages`, so it must precede the snapshot. Callers
631
+ // like fork-based memory retrospectives suppress this gate: the fork
632
+ // is throwaway and a summarization LLM call on it is wasted spend.
633
+ // Failure is non-fatal — the wake proceeds on the uncompacted history
634
+ // exactly as it would have before the gate existed. The gate's window
635
+ // sizing is threaded from the wake's own call-site resolution above —
636
+ // without it, `maybeCompact` sizes the threshold against `mainAgent`,
637
+ // which can pass un-compacted a wake whose call site resolves a smaller
638
+ // window (and then overflow at the provider).
639
+ if (!suppressAutoCompaction) {
640
+ try {
641
+ await conversation.maybeCompact({
642
+ callSite,
643
+ overrideProfile,
644
+ forceOverrideProfile,
645
+ });
646
+ } catch (err) {
647
+ log.warn(
648
+ { conversationId, source, err },
649
+ "agent-wake: pre-run auto-compaction failed; continuing with the uncompacted history",
650
+ );
651
+ }
652
+ }
653
+
489
654
  const baseline = conversation.getMessages();
490
655
  // Snapshot the baseline length BEFORE the run starts. Incremental
491
656
  // persistence pushes onto `conversation.messages` mid-run, which grows the
@@ -564,6 +729,14 @@ export async function wakeAgentForOpportunity(
564
729
  // so the latest-row lookup in `setAgentLoopExitReasonOnLatestLog`
565
730
  // can see the freshly-persisted final usage row.
566
731
  let pendingExitReason: string | null = null;
732
+ // Set when the provider rejects a call as context-too-large while
733
+ // auto-compaction is suppressed. The loop has no recovery ladder to
734
+ // drive (overflow recovery is disabled for wakes), so it swallows the
735
+ // rejection into a graceful no-output stop — which would read as a
736
+ // *successful* silent no-op to callers like the memory-retrospective
737
+ // job. Capture the signal here so the wake can fail deterministically
738
+ // instead (`reason: "context_overflow"`).
739
+ let suppressedContextOverflow = false;
567
740
  const persistLog = (record: PendingLog): void => {
568
741
  try {
569
742
  recordRequestLog(
@@ -572,7 +745,7 @@ export async function wakeAgentForOpportunity(
572
745
  JSON.stringify(record.rawResponse),
573
746
  undefined,
574
747
  record.provider,
575
- "mainAgent",
748
+ callSite,
576
749
  );
577
750
  } catch (err) {
578
751
  log.warn(
@@ -658,6 +831,19 @@ export async function wakeAgentForOpportunity(
658
831
  persistExitReason(event.reason);
659
832
  }
660
833
  }
834
+ // Detect an over-window rejection on a compaction-suppressed wake.
835
+ // `provider_error` fires at the provider-call site; `error` fires from
836
+ // the loop's generic catch — check both so a rewrapping retry layer
837
+ // can't hide the signal. The heuristic check also catches adapter
838
+ // paths (e.g. managed-proxy rewrappers) that surface the overflow as
839
+ // an untyped error the typed `instanceof` check would miss.
840
+ if (
841
+ suppressAutoCompaction &&
842
+ (event.type === "provider_error" || event.type === "error") &&
843
+ looksLikeContextOverflowError(event.error)
844
+ ) {
845
+ suppressedContextOverflow = true;
846
+ }
661
847
  if (mode === "buffering") {
662
848
  buffered.push(event);
663
849
  return;
@@ -758,23 +944,6 @@ export async function wakeAgentForOpportunity(
758
944
  persistedTailIndex += newMessages.length;
759
945
  };
760
946
 
761
- // Honor the conversation's pinned inference-profile override (if any).
762
- // Without this, scheduled-task wakes and other opportunity wakes bypass
763
- // `runAgentLoopImpl` entirely and execute under workspace defaults,
764
- // silently violating the user's pinned preference. Resolve the effective
765
- // context budget here as well because wakes bypass the normal user-turn
766
- // path that computes it for tool-result truncation. Read before
767
- // `setProcessing(true)` so a thrown DB/config read can't strand the
768
- // processing flag.
769
- const overrideProfile = getConversationOverrideProfile(conversationId);
770
- const callSite = opts.callSite ?? "mainAgent";
771
- const config = getConfig();
772
- const effectiveContextWindow = resolveEffectiveContextWindow({
773
- llm: config.llm,
774
- callSite,
775
- overrideProfile,
776
- });
777
-
778
947
  let wakeToolScopeRestored = false;
779
948
  let restoreWakeToolScope: (() => void) | null = null;
780
949
  const restoreWakeAllowedTools = (): void => {
@@ -796,6 +965,8 @@ export async function wakeAgentForOpportunity(
796
965
  restoreWakeToolScope = scopeWakeAllowedTools(
797
966
  conversation,
798
967
  new Set(opts.allowedTools),
968
+ opts.toolGateMode,
969
+ opts.toolContextPin,
799
970
  );
800
971
  return true;
801
972
  } catch (err) {
@@ -807,14 +978,6 @@ export async function wakeAgentForOpportunity(
807
978
  }
808
979
  };
809
980
 
810
- // Mark processing for the duration of the run so a concurrent user
811
- // send is queued by `enqueueMessage()` rather than spawning a second
812
- // concurrent agent loop on the same conversation (which would
813
- // interleave writes to `conversation.messages`). This happens before
814
- // applying a wake-scoped tool allowlist so a concurrent user turn cannot
815
- // start under the wake's restricted tool set.
816
- conversation.setProcessing(true);
817
-
818
981
  // Fires after each tool-execution turn finalizes (assistant message
819
982
  // + matching tool_result user message both in history). A single
820
983
  // tool turn is unambiguous evidence of output — promote to live
@@ -834,7 +997,64 @@ export async function wakeAgentForOpportunity(
834
997
  let toolUseNames: string[] = [];
835
998
  let tailMessageCount = 0;
836
999
  let drainedInTry = false;
1000
+ // Set when the wake fails with `reason: "context_overflow"`. The finally
1001
+ // block skips its generic outcome log for this path — the failure
1002
+ // already logged its own dedicated warn line.
1003
+ let failedContextOverflow = false;
1004
+ // Shared failure path for an over-window condition on a
1005
+ // compaction-suppressed wake (reached from the pre-flight estimate, from
1006
+ // the run's catch when the rejection escaped as a throw, or post-run when
1007
+ // the loop swallowed it into a graceful no-output stop and only the event
1008
+ // capture saw it). `extraLogFields` is spread into the warn line so each
1009
+ // site can attach its own context (err, token estimates).
1010
+ const failSuppressedContextOverflow = (
1011
+ logMessage: string,
1012
+ extraLogFields: Record<string, unknown> = {},
1013
+ ): WakeResult => {
1014
+ failedContextOverflow = true;
1015
+ log.warn({ conversationId, source, ...extraLogFields }, logMessage);
1016
+ return {
1017
+ invoked: false,
1018
+ producedToolCalls: false,
1019
+ reason: "context_overflow" as const,
1020
+ };
1021
+ };
837
1022
  try {
1023
+ // ── Over-window policy under suppressed auto-compaction ─────────
1024
+ // The pre-run gate above is the wake's only compaction path (the
1025
+ // loop's in-loop budget gate stays disabled), so when the caller
1026
+ // suppressed it an over-window input has no recovery. Fail fast and
1027
+ // deterministically — before spending any LLM call — instead of
1028
+ // letting the provider reject the run into a silent no-op. Estimated
1029
+ // with the same estimator the auto-compaction pre-check uses; an
1030
+ // estimator failure proceeds fail-open (the reactive
1031
+ // `suppressedContextOverflow` capture below still maps a provider
1032
+ // rejection to the same failure).
1033
+ if (suppressAutoCompaction) {
1034
+ let estimatedInputTokens: number | null = null;
1035
+ try {
1036
+ estimatedInputTokens =
1037
+ conversation.contextWindowManager.estimateInputTokens(runInput);
1038
+ } catch (err) {
1039
+ log.warn(
1040
+ { conversationId, source, err },
1041
+ "agent-wake: over-window pre-flight estimate failed; proceeding without it",
1042
+ );
1043
+ }
1044
+ if (
1045
+ estimatedInputTokens !== null &&
1046
+ estimatedInputTokens > effectiveContextWindow.maxInputTokens
1047
+ ) {
1048
+ return failSuppressedContextOverflow(
1049
+ "agent-wake: input exceeds the effective context window and auto-compaction is suppressed; failing fast",
1050
+ {
1051
+ estimatedInputTokens,
1052
+ maxInputTokens: effectiveContextWindow.maxInputTokens,
1053
+ },
1054
+ );
1055
+ }
1056
+ }
1057
+
838
1058
  if (!applyWakeAllowedTools()) {
839
1059
  return {
840
1060
  invoked: false,
@@ -859,16 +1079,33 @@ export async function wakeAgentForOpportunity(
859
1079
  callSite,
860
1080
  trust: wakeTrust,
861
1081
  overrideProfile,
862
- // Wake runs have no orchestrator-side mid-loop compaction path,
863
- // so the budget gate stays disabled (`overflowRecovery.enabled =
864
- // false`); `maxInputTokens` is still supplied for tool-result
865
- // truncation.
1082
+ forceOverrideProfile,
1083
+ // The wake's compaction lives in the pre-run gate above
1084
+ // (`conversation.maybeCompact()`), never in the loop: the in-loop
1085
+ // budget gate and overflow-recovery ladder stay disabled because
1086
+ // the wake's tail accounting holds external indexes into the
1087
+ // returned history (`baselineLength` + hint count), which an
1088
+ // in-loop compaction rebase would invalidate. `maxInputTokens` is
1089
+ // still supplied for tool-result truncation.
866
1090
  resolveContextWindow: () => ({
867
1091
  maxInputTokens: effectiveContextWindow.maxInputTokens,
868
1092
  overflowRecovery: { enabled: false, safetyMarginRatio: 0 },
869
1093
  }),
870
1094
  }));
871
1095
  } catch (err) {
1096
+ // An over-window throw on a compaction-suppressed wake is the
1097
+ // suppression contract's failure mode, not a generic loop error —
1098
+ // surface it as a deterministic failed result. The heuristic check
1099
+ // also catches rewrapped (untyped) provider overflow errors.
1100
+ if (
1101
+ suppressedContextOverflow ||
1102
+ (suppressAutoCompaction && looksLikeContextOverflowError(err))
1103
+ ) {
1104
+ return failSuppressedContextOverflow(
1105
+ OVER_WINDOW_REJECTION_LOG_MESSAGE,
1106
+ { err },
1107
+ );
1108
+ }
872
1109
  // Capture the error for post-finally logging, then short-circuit
873
1110
  // the rest of the try body — no tail to push/persist when the
874
1111
  // run threw mid-flight. The outer finally still runs to release
@@ -877,6 +1114,15 @@ export async function wakeAgentForOpportunity(
877
1114
  return { invoked: true, producedToolCalls: false };
878
1115
  }
879
1116
 
1117
+ // The loop swallows provider rejections into a graceful no-output
1118
+ // stop, so an over-window rejection on a compaction-suppressed wake
1119
+ // surfaces only through the event stream (captured into
1120
+ // `suppressedContextOverflow` by `onEvent`). Map it to a deterministic
1121
+ // failure instead of the silent no-op it would otherwise read as.
1122
+ if (suppressedContextOverflow) {
1123
+ return failSuppressedContextOverflow(OVER_WINDOW_REJECTION_LOG_MESSAGE);
1124
+ }
1125
+
880
1126
  // Run completed cleanly. The canonical user-turn pattern
881
1127
  // (conversation-agent-loop.ts:1860, 2106-2126) updates
882
1128
  // `ctx.messages` first, then clears the flag via `ctx.setProcessing(false)`, then
@@ -925,6 +1171,7 @@ export async function wakeAgentForOpportunity(
925
1171
  // processing to already be false). The finally block handles the
926
1172
  // error/early-return paths where no tail was produced.
927
1173
  restoreWakeAllowedTools();
1174
+ clearWakePersonaOverride();
928
1175
  try {
929
1176
  conversation.setProcessing(false);
930
1177
  } catch (err) {
@@ -952,6 +1199,7 @@ export async function wakeAgentForOpportunity(
952
1199
  // `drainedInTry` is still false.
953
1200
  if (!drainedInTry) {
954
1201
  restoreWakeAllowedTools();
1202
+ clearWakePersonaOverride();
955
1203
  try {
956
1204
  conversation.setProcessing(false);
957
1205
  } catch (err) {
@@ -971,9 +1219,12 @@ export async function wakeAgentForOpportunity(
971
1219
  }
972
1220
 
973
1221
  const durationMs = nowFn() - startedAt;
974
- const suppressAutoCompaction = opts.suppressAutoCompaction === true;
975
1222
  const suppressWakeSurface = opts.suppressWakeSurface === true;
976
- if (runError) {
1223
+ if (failedContextOverflow) {
1224
+ // Already logged its own dedicated warn line at the failure site;
1225
+ // a generic "silent no-op" line here would misclassify a failed
1226
+ // wake as a successful empty one.
1227
+ } else if (runError) {
977
1228
  log.error(
978
1229
  {
979
1230
  conversationId,