@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -51,7 +51,6 @@ const BACKGROUND_SOURCES = new Set([
51
51
  "heartbeat",
52
52
  "memory",
53
53
  "notification",
54
- "proactive-artifact",
55
54
  "reminder",
56
55
  "schedule",
57
56
  "task",
@@ -2,21 +2,22 @@
2
2
  * Plugin bootstrap — runs every registered plugin's `init()` hook once during
3
3
  * daemon startup.
4
4
  *
5
- * Plugins register themselves via side-effect imports elsewhere in the boot
6
- * sequence (first-party) or at runtime (hot-reload). By the time
7
- * {@link bootstrapPlugins} runs, the registry has been fully populated for
8
- * this boot cycle. This function:
5
+ * The registry is populated before this runs: first-party defaults are
6
+ * registered explicitly (step 1 below), user plugins are registered by the
7
+ * user-plugin loader from the workspace `plugins/` directory, and hot-reload
8
+ * registers at runtime. By the time {@link bootstrapPlugins} runs, the
9
+ * registry has been fully populated for this boot cycle. This function:
9
10
  *
10
11
  * 1. Registers the canonical first-party default plugins via
11
- * {@link registerDefaultPlugins} (one per pipeline). Registration is
12
- * idempotent so repeat calls (e.g. during integration tests) do not throw.
12
+ * {@link registerDefaultPlugins}. Registration is idempotent so repeat
13
+ * calls (e.g. during integration tests) do not throw.
13
14
  * 2. Walks {@link getRegisteredPlugins} in registration order.
14
15
  * 3. For each plugin, consults `manifest.requiresFlag` against
15
16
  * {@link isAssistantFeatureFlagEnabled}. If any listed flag is disabled,
16
17
  * the plugin is skipped wholesale — no `init()`, no tool/route/skill
17
18
  * contributions, no entry in the shutdown hook, and the plugin is also
18
- * dropped from the registry via {@link unregisterPlugin} so its middleware
19
- * stops participating in pipeline runs. This is the primary mechanism for
19
+ * dropped from the registry via {@link unregisterPlugin} so none of its
20
+ * hooks participate in the turn lifecycle. This is the primary mechanism for
20
21
  * shipping experimental plugins behind a feature flag.
21
22
  * 4. Resolves the plugin's `manifest.requiresCredential` entries via the
22
23
  * credential store helper ({@link getSecureKeyAsync}). In Docker mode
@@ -36,14 +37,15 @@
36
37
  * surface is already wired — useful for plugins that want to attach
37
38
  * metadata, warm caches, or otherwise interact with their own
38
39
  * contributions during initialization.
39
- * 8. Awaits `plugin.init(ctx)` sequentially. An init failure surfaces as a
40
- * {@link PluginExecutionError} naming the offending plugin and aborts
41
- * bootstrap — later plugins' `init()` never runs and the daemon fails
42
- * startup cleanly rather than coming up in a half-wired state. The
43
- * failing plugin's already-registered tools, routes, and skills are
44
- * rolled back before the error propagates so the registry never
45
- * carries state contributed by a plugin that never finished
46
- * initializing.
40
+ * 8. Awaits `plugin.init(ctx)` sequentially. An init failure is contained to
41
+ * the offending plugin: its already-registered tools, routes, and skills
42
+ * are rolled back, it is dropped from the registry, the failure is logged,
43
+ * and bootstrap continues with the remaining plugins. A single plugin's
44
+ * failure never deregisters plugins that already initialized — in
45
+ * particular the first-party defaults, which carry core turn behavior
46
+ * (memory retrieval, history repair, title generation) — so the daemon
47
+ * comes up with the failing plugin absent rather than blacking out the
48
+ * whole plugin layer.
47
49
  *
48
50
  * A single shutdown hook is registered via
49
51
  * {@link registerShutdownHook} that walks the plugin list in **reverse
@@ -217,9 +219,10 @@ export async function initializePlugins(): Promise<void> {
217
219
  * Run every registered plugin's `init()` hook sequentially and install a
218
220
  * reverse-order shutdown hook. See the module docstring for full semantics.
219
221
  *
220
- * Returns once every plugin has finished initialising successfully. Throws a
221
- * {@link PluginExecutionError} on the first failure — the error message names
222
- * the offending plugin so operators see which `init()` bailed.
222
+ * Returns once every plugin has been processed. A plugin whose `init()` throws
223
+ * is rolled back, dropped from the registry, and logged; bootstrap continues
224
+ * with the rest, so one failing plugin never blocks the others — or the
225
+ * first-party defaults — from coming up.
223
226
  *
224
227
  * Must be called after any custom/third-party plugin registrations have run
225
228
  * and before the first conversation is served. First-party defaults are
@@ -258,36 +261,14 @@ export async function bootstrapPlugins(): Promise<void> {
258
261
  // onShutdown" invariant.
259
262
  const activePlugins: ActivePlugin[] = [];
260
263
 
261
- // Tear down a plugin's contributions AND remove it from the registry. The
262
- // two steps always move together on the bootstrap failure path: the former
263
- // clears tools/routes/skills (so they stop appearing to the model/HTTP
264
- // server), the latter drops the plugin's entry from the Map (so
265
- // `getMiddlewaresFor` doesn't re-enter an uninitialized plugin on the next
266
- // pipeline invocation).
267
264
  // Shutdown context is identical for every plugin in this boot — construct
268
- // once and reuse across the bootstrap-failure rollback and the normal
269
- // shutdown hook below. Only `assistantVersion` is exposed today; future
270
- // additions live on {@link PluginShutdownContext}.
265
+ // once and reuse across the per-plugin teardown and the normal shutdown
266
+ // hook below. Only `assistantVersion` is exposed today; future additions
267
+ // live on {@link PluginShutdownContext}.
271
268
  const shutdownContext: PluginShutdownContext = {
272
269
  assistantVersion: APP_VERSION,
273
270
  };
274
271
 
275
- async function rollbackPlugin(active: ActivePlugin): Promise<void> {
276
- await teardownPlugin(active, "bootstrap-failed", shutdownContext);
277
- unregisterPlugin(active.plugin.manifest.name);
278
- }
279
-
280
- // If one plugin's init or contribution phase throws, tear down any plugins
281
- // that already fully initialized (in reverse registration order) before
282
- // re-throwing. Without this, a mid-loop failure would leave earlier plugins
283
- // with live tools/routes/skills and no `onShutdown()` call — the shutdown
284
- // hook is only registered once the loop completes successfully.
285
- async function teardownPartialInit(): Promise<void> {
286
- for (let i = activePlugins.length - 1; i >= 0; i--) {
287
- await rollbackPlugin(activePlugins[i]!);
288
- }
289
- }
290
-
291
272
  for (const plugin of plugins) {
292
273
  const name = plugin.manifest.name;
293
274
  const disabledFlag = getDisabledPluginFlag(plugin, assistantConfig);
@@ -303,9 +284,18 @@ export async function bootstrapPlugins(): Promise<void> {
303
284
  try {
304
285
  activePlugins.push(await initializePlugin(plugin, assistantConfig));
305
286
  } catch (err) {
287
+ // Contain the failure to this plugin. `initializePlugin` already rolled
288
+ // back its own partial tool/route/skill contributions, so we just drop
289
+ // it from the registry and move on. A single plugin's init failure must
290
+ // never deregister the plugins that already came up — above all the
291
+ // first-party defaults, which carry core turn behavior. The daemon stays
292
+ // reachable with the failing plugin absent rather than losing the whole
293
+ // plugin layer.
306
294
  unregisterPlugin(name);
307
- await teardownPartialInit();
308
- throw err;
295
+ log.warn(
296
+ { err, plugin: name },
297
+ `plugin ${name} failed to initialize — skipping (continuing with remaining plugins)`,
298
+ );
309
299
  }
310
300
  }
311
301
 
@@ -31,6 +31,7 @@ import {
31
31
  } from "../../runtime/channel-verification-service.js";
32
32
  import {
33
33
  cancelOutbound,
34
+ deliverVerificationEmail,
34
35
  deliverVerificationSlack,
35
36
  deliverVerificationTelegram,
36
37
  DESTINATION_RATE_WINDOW_MS,
@@ -555,7 +556,11 @@ export async function handleChannelVerificationSession(
555
556
  const { userId, text, assistantId: aid } = result._pendingSlackDm;
556
557
  deliverVerificationSlack(userId, text, aid);
557
558
  }
558
- const { _pendingSlackDm: _, ...publicResult } = result;
559
+ if (result._pendingEmail) {
560
+ const { to, text, subject, assistantId: aid } = result._pendingEmail;
561
+ deliverVerificationEmail(to, text, subject, aid);
562
+ }
563
+ const { _pendingSlackDm: _, _pendingEmail: __, ...publicResult } = result;
559
564
  broadcastMessage({
560
565
  type: "channel_verification_session_response",
561
566
  ...publicResult,
@@ -600,7 +605,11 @@ export async function handleChannelVerificationSession(
600
605
  const { userId, text, assistantId: aid } = result._pendingSlackDm;
601
606
  deliverVerificationSlack(userId, text, aid);
602
607
  }
603
- const { _pendingSlackDm: _, ...publicResult } = result;
608
+ if (result._pendingEmail) {
609
+ const { to, text, subject, assistantId: aid } = result._pendingEmail;
610
+ deliverVerificationEmail(to, text, subject, aid);
611
+ }
612
+ const { _pendingSlackDm: _, _pendingEmail: __, ...publicResult } = result;
604
613
  broadcastMessage({
605
614
  type: "channel_verification_session_response",
606
615
  ...publicResult,
@@ -8,9 +8,11 @@ import { createAbortReason } from "../../util/abort-reasons.js";
8
8
  import { truncate } from "../../util/truncate.js";
9
9
  import { regenerate } from "../conversation-history.js";
10
10
  import {
11
- clearAllActiveConversations,
12
11
  conversationEntries,
13
12
  findConversation,
13
+ } from "../conversation-registry.js";
14
+ import {
15
+ clearAllActiveConversations,
14
16
  getOrCreateConversation,
15
17
  touchConversation,
16
18
  } from "../conversation-store.js";
@@ -428,7 +428,15 @@ export function renderHistoryContent(
428
428
  finalizeSegment();
429
429
  thinkingSegments.push(block.thinking);
430
430
  contentOrder.push(`thinking:${thinkingSegments.length - 1}`);
431
- contentBlocks.push({ type: "thinking", thinking: block.thinking });
431
+ const thinkingBlock: Extract<
432
+ ConversationContentBlock,
433
+ { type: "thinking" }
434
+ > = { type: "thinking", thinking: block.thinking };
435
+ if (typeof block._startedAt === "number")
436
+ thinkingBlock.startedAt = block._startedAt;
437
+ if (typeof block._completedAt === "number")
438
+ thinkingBlock.completedAt = block._completedAt;
439
+ contentBlocks.push(thinkingBlock);
432
440
  continue;
433
441
  }
434
442
 
@@ -31,7 +31,10 @@ import {
31
31
  isTextMimeType as isTextMime,
32
32
  MAX_INLINE_TEXT_SIZE,
33
33
  } from "../../runtime/routes/workspace-utils.js";
34
- import { getCachedCatalogSync, getCatalog } from "../../skills/catalog-cache.js";
34
+ import {
35
+ getCachedCatalogSync,
36
+ getCatalog,
37
+ } from "../../skills/catalog-cache.js";
35
38
  import type { SkillFileEntry } from "../../skills/catalog-files.js";
36
39
  import {
37
40
  catalogSkillToSlim,
@@ -12,11 +12,12 @@
12
12
  * translation on top.
13
13
  *
14
14
  * **Session lock.** Only one conversation may hold an active app-control
15
- * session at a time, and that session is bound to a specific target app.
15
+ * session at a time, and that session is bound to a specific target app
16
+ * on a specific client.
16
17
  * The lock is module-level (`activeAppControlSession`) because the session
17
18
  * targets the user's actual desktop application, which is a host-wide
18
19
  * resource. It is acquired optimistically when `app_control_start` is
19
- * dispatched (storing `(conversationId, app)`) so that the synchronous
20
+ * dispatched (storing `(conversationId, app, targetClientId)`) so that the synchronous
20
21
  * guard and the asynchronous host round-trip cannot race. A separate
21
22
  * `confirmedAppControlSession` tracks the last session the host reported
22
23
  * `running`; the rollback path on a failed `start` restores from the
@@ -32,7 +33,7 @@
32
33
  * user's medium-risk approval at start time is the consent boundary. All
33
34
  * other tools (observe / press / combo / sequence / type / click / drag)
34
35
  * require the calling conversation to own an active session targeting the
35
- * same `app`; otherwise the call is rejected before any host dispatch.
36
+ * same `app` and client; otherwise the call is rejected before any host dispatch.
36
37
  * This prevents prompt-injected tool calls from sending raw input to
37
38
  * arbitrary apps without the user having approved control of that
38
39
  * specific app.
@@ -78,9 +79,9 @@ const STUCK_REPEAT_THRESHOLD = 4;
78
79
  // ---------------------------------------------------------------------------
79
80
 
80
81
  /**
81
- * Active app-control session: the conversation that owns the lock and the
82
- * `app` it was approved against. Set on a successful `app_control_start`;
83
- * cleared by the owning proxy's `dispose()`.
82
+ * Active app-control session: the conversation that owns the lock, the
83
+ * `app` and client it was approved against. Set on a successful
84
+ * `app_control_start`; cleared by the owning proxy's `dispose()`.
84
85
  */
85
86
  export interface ActiveAppControlSession {
86
87
  conversationId: string;
@@ -90,6 +91,11 @@ export interface ActiveAppControlSession {
90
91
  * the `app` of subsequent non-start tool calls.
91
92
  */
92
93
  app: string;
94
+ /**
95
+ * The client selected for `app_control_start`. Undefined when the start
96
+ * was untargeted; subsequent calls must match the same value.
97
+ */
98
+ targetClientId?: string;
93
99
  /**
94
100
  * Strictly monotonic counter assigned when the session is created (in
95
101
  * `request()` for a `start`). Used by {@link promoteStartIfCurrent} to
@@ -153,11 +159,13 @@ export function _resetActiveAppControlSession(): void {
153
159
  export function _setActiveAppControlSession(session: {
154
160
  conversationId: string;
155
161
  app: string;
162
+ targetClientId?: string;
156
163
  dispatchedAt?: number;
157
164
  }): void {
158
165
  const full: ActiveAppControlSession = {
159
166
  conversationId: session.conversationId,
160
167
  app: session.app,
168
+ targetClientId: session.targetClientId,
161
169
  dispatchedAt: session.dispatchedAt ?? nextDispatchedAt++,
162
170
  };
163
171
  activeAppControlSession = full;
@@ -177,6 +185,7 @@ export function _setActiveAppControlSession(session: {
177
185
  function checkNonStartAuthorization(
178
186
  input: HostAppControlInput,
179
187
  conversationId: string,
188
+ targetClientId: string | undefined,
180
189
  ): ToolExecutionResult | null {
181
190
  if (activeAppControlSession == null) {
182
191
  return {
@@ -219,6 +228,16 @@ function checkNonStartAuthorization(
219
228
  isError: true,
220
229
  };
221
230
  }
231
+ if (activeAppControlSession.targetClientId !== targetClientId) {
232
+ return {
233
+ content:
234
+ `Active app-control session targets client ` +
235
+ `${activeAppControlSession.targetClientId ?? "<default>"}; cannot ` +
236
+ `send actions to client ${targetClientId ?? "<default>"}. Call ` +
237
+ `app_control_stop and app_control_start to switch clients.`,
238
+ isError: true,
239
+ };
240
+ }
222
241
  return null;
223
242
  }
224
243
 
@@ -284,11 +303,53 @@ export class HostAppControlProxy extends HostProxyBase<
284
303
  return { content: "Aborted", isError: true };
285
304
  }
286
305
 
306
+ // Resolve target client and enforce same-actor BEFORE acquiring the
307
+ // session lock. This way early-exit errors don't need rollback, and
308
+ // the resolved client id can be stored in the session.
309
+ let resolvedTargetClientId = targetClientId;
310
+ if (resolvedTargetClientId == null) {
311
+ const resolved = pickSameUserAutoResolve({
312
+ hub: assistantEventHub,
313
+ capability: "host_app_control",
314
+ sourceActorPrincipalId,
315
+ });
316
+ if (resolved.kind === "ambiguous") {
317
+ return {
318
+ content:
319
+ "Multiple host_app_control clients are connected for this user. Specify target_client_id to disambiguate.",
320
+ isError: true,
321
+ };
322
+ }
323
+ if (resolved.kind === "match") {
324
+ resolvedTargetClientId = resolved.clientId;
325
+ } else if (
326
+ assistantEventHub.listClientsByCapability("host_app_control").length > 0
327
+ ) {
328
+ return {
329
+ content:
330
+ "App control is not available for the current actor. Connect a host_app_control-capable client as the same user.",
331
+ isError: true,
332
+ };
333
+ }
334
+ }
335
+
336
+ if (resolvedTargetClientId != null) {
337
+ const rejection = enforceSameActorOrErrorResult({
338
+ hub: assistantEventHub,
339
+ sourceActorPrincipalId,
340
+ targetClientId: resolvedTargetClientId,
341
+ op: "host_app_control",
342
+ });
343
+ if (rejection) {
344
+ return rejection;
345
+ }
346
+ }
347
+
287
348
  // Authorization gate. `start` acquires the session lock (the user's
288
349
  // medium-risk approval is the consent boundary); all other tools must
289
- // belong to the active session and target the same `app`. Without this
290
- // gate, prompt-injected calls would bypass the start-time approval and
291
- // send raw input to arbitrary apps.
350
+ // belong to the active session and target the same `app` and client.
351
+ // Without this gate, prompt-injected calls would bypass the start-time
352
+ // approval and send raw input to arbitrary apps or clients.
292
353
  let attemptedSession: ActiveAppControlSession | undefined;
293
354
  if (input.tool === "start") {
294
355
  if (
@@ -314,6 +375,7 @@ export class HostAppControlProxy extends HostProxyBase<
314
375
  attemptedSession = {
315
376
  conversationId: this.conversationId,
316
377
  app: input.app,
378
+ targetClientId: resolvedTargetClientId,
317
379
  dispatchedAt: nextDispatchedAt++,
318
380
  };
319
381
  activeAppControlSession = attemptedSession;
@@ -321,60 +383,13 @@ export class HostAppControlProxy extends HostProxyBase<
321
383
  const sessionError = checkNonStartAuthorization(
322
384
  input,
323
385
  this.conversationId,
386
+ resolvedTargetClientId,
324
387
  );
325
388
  if (sessionError != null) {
326
389
  return sessionError;
327
390
  }
328
391
  }
329
392
 
330
- let resolvedTargetClientId = targetClientId;
331
- if (resolvedTargetClientId == null) {
332
- const resolved = pickSameUserAutoResolve({
333
- hub: assistantEventHub,
334
- capability: "host_app_control",
335
- sourceActorPrincipalId,
336
- });
337
- if (resolved.kind === "ambiguous") {
338
- if (input.tool === "start") {
339
- this.rollbackStartIfCurrent(attemptedSession);
340
- }
341
- return {
342
- content:
343
- "Multiple host_app_control clients are connected for this user. Specify target_client_id to disambiguate.",
344
- isError: true,
345
- };
346
- }
347
- if (resolved.kind === "match") {
348
- resolvedTargetClientId = resolved.clientId;
349
- } else if (
350
- assistantEventHub.listClientsByCapability("host_app_control").length > 0
351
- ) {
352
- if (input.tool === "start") {
353
- this.rollbackStartIfCurrent(attemptedSession);
354
- }
355
- return {
356
- content:
357
- "App control is not available for the current actor. Connect a host_app_control-capable client as the same user.",
358
- isError: true,
359
- };
360
- }
361
- }
362
-
363
- if (resolvedTargetClientId != null) {
364
- const rejection = enforceSameActorOrErrorResult({
365
- hub: assistantEventHub,
366
- sourceActorPrincipalId,
367
- targetClientId: resolvedTargetClientId,
368
- op: "host_app_control",
369
- });
370
- if (rejection) {
371
- if (input.tool === "start") {
372
- this.rollbackStartIfCurrent(attemptedSession);
373
- }
374
- return rejection;
375
- }
376
- }
377
-
378
393
  try {
379
394
  const payload = await this.dispatchRequest(
380
395
  toolName,
@@ -34,9 +34,7 @@ import { getLogger } from "../util/logger.js";
34
34
  const log = getLogger("host-proxy-preactivation");
35
35
 
36
36
  /**
37
- * Subset of Conversation/ProcessConversationContext that
38
- * `preactivateHostProxySkills` needs. Both `Conversation` and
39
- * `ProcessConversationContext` satisfy this structurally.
37
+ * Subset of `Conversation` that `preactivateHostProxySkills` needs.
40
38
  */
41
39
  export interface HostProxyPreactivationTarget {
42
40
  readonly conversationId: string;
@@ -60,7 +60,6 @@ import { sweepConceptPageFrontmatter } from "../memory/v2/frontmatter-sweep.js";
60
60
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
61
61
  import { backfillManualTokenConnections } from "../oauth/manual-token-connection.js";
62
62
  import { seedOAuthProviders } from "../oauth/seed-providers.js";
63
- import { backfillGuardIfNeeded } from "../proactive-artifact/index.js";
64
63
  import { ensurePromptFiles } from "../prompts/system-prompt.js";
65
64
  import { runProviderConnectionsBackfill } from "../providers/inference/backfill.js";
66
65
  import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
@@ -98,6 +97,7 @@ import {
98
97
  updateWorkItem,
99
98
  } from "../work-items/work-item-store.js";
100
99
  import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
100
+ import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/migrations/097-enable-adaptive-thinking-managed-profiles.js";
101
101
  import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
102
102
  import { runWorkspaceMigrations } from "../workspace/migrations/runner.js";
103
103
  import {
@@ -589,6 +589,26 @@ export async function runDaemon(): Promise<void> {
589
589
  );
590
590
  }
591
591
 
592
+ // Re-run the adaptive thinking repair after overlay merge + profile seeding.
593
+ // Workspace migration 097 enables adaptive thinking on managed profiles, but
594
+ // it runs before mergeDefaultWorkspaceConfig() which can overwrite the fix
595
+ // with overlay profiles that have thinking disabled or absent. On-platform
596
+ // instances where the overlay supplies "balanced" / "quality-optimized"
597
+ // profiles without thinking enabled would be stuck permanently because the
598
+ // migration is already checkpointed as completed. This idempotent repair
599
+ // ensures thinking is enabled regardless of overlay ordering.
600
+ if (defaultConfigMerge.hadOverlay) {
601
+ try {
602
+ repairAdaptiveThinkingOnManagedProfiles(getWorkspaceDir());
603
+ log.info("Post-overlay adaptive thinking repair complete");
604
+ } catch (err) {
605
+ log.warn(
606
+ { err },
607
+ "Post-overlay adaptive thinking repair failed — continuing startup",
608
+ );
609
+ }
610
+ }
611
+
592
612
  log.info("Daemon startup: loading config");
593
613
  const config = loadConfig();
594
614
 
@@ -1276,12 +1296,6 @@ export async function runDaemon(): Promise<void> {
1276
1296
  "Heartbeat service configured",
1277
1297
  );
1278
1298
 
1279
- try {
1280
- backfillGuardIfNeeded();
1281
- } catch (err) {
1282
- log.warn({ err }, "Proactive artifact backfill failed");
1283
- }
1284
-
1285
1299
  // Filing yields to the memory v2 consolidation job when v2 is enabled —
1286
1300
  // both serve the same role (periodic background memory processing) and
1287
1301
  // running both is redundant. The consolidation job runs through the
@@ -6,8 +6,9 @@
6
6
  * retries. That transformation is transient — the stored message row keeps the
7
7
  * full-size image block, so the rejected image is rehydrated on every later
8
8
  * turn and keeps re-entering the model's context. This module makes the
9
- * downgrade durable for images that can *never* be transmitted, so a rejected
10
- * upload cannot resurface after the user re-uploads a smaller version.
9
+ * downgrade durable: the oversized block is rewritten to its downscaled form,
10
+ * or to a text note when it cannot be shrunk on this host, so a rejected image
11
+ * cannot resurface and re-reject on every later turn.
11
12
  */
12
13
 
13
14
  import { optimizeImageForTransport } from "../agent/image-optimize.js";
@@ -40,19 +41,25 @@ export const UNSENDABLE_IMAGE_NOTE =
40
41
  "(An image was attached but could not be sent — its dimensions exceed the provider limit and automatic resize was not available. Please resize the image and try again.)";
41
42
 
42
43
  /**
43
- * True when a stored image block can never be transmitted to the provider: it
44
- * violates a provider hard limit (per-side pixel cap or payload size) and
45
- * cannot be shrunk to fit (re-optimization is a no-op).
44
+ * Replacement for an image that violates a provider hard limit (per-side pixel
45
+ * cap or payload size), or null when the image is within limits and should be
46
+ * left untouched. Gating on the provider hard caps is what keeps still-sendable
47
+ * images intact: a normally sized image is left alone rather than being noted or
48
+ * needlessly rewritten.
46
49
  *
47
- * Stored blocks are already post-optimization, so a block that is still
48
- * oversized here only stays oversized because resizing is unavailable on this
49
- * host (e.g. `sips` is absent off macOS, or the format is unsupported). A
50
- * downscalable image would have been reduced at upload time and would not reach
51
- * this predicate, so it is left untouched.
50
+ * An oversized image that can be shrunk is rewritten to its downscaled form; one
51
+ * that cannot be shrunk on this host (resize is a no-op, e.g. `sips` is absent
52
+ * off macOS or the format is unsupported) is replaced with a text note.
53
+ *
54
+ * Shared by the in-memory recovery transform and this durable persist pass so
55
+ * both apply the identical rule. Persisting the downscaled form is what lets a
56
+ * poisoned conversation durably self-heal — the latest tool-result media is kept
57
+ * in context, so without it the original oversized block rehydrates and
58
+ * re-rejects on every later turn.
52
59
  */
53
- function isImagePermanentlyUnsendable(
60
+ export function oversizedImageReplacement(
54
61
  block: Extract<ContentBlock, { type: "image" }>,
55
- ): boolean {
62
+ ): ContentBlock | null {
56
63
  const payloadBytes = block.source.data.length;
57
64
  const dims = parseImageDimensions(block.source.data, block.source.media_type);
58
65
  const exceedsDimensionCap =
@@ -60,24 +67,35 @@ function isImagePermanentlyUnsendable(
60
67
  (dims.width > PROVIDER_MAX_IMAGE_DIMENSION ||
61
68
  dims.height > PROVIDER_MAX_IMAGE_DIMENSION);
62
69
  const exceedsPayloadCap = payloadBytes > PROVIDER_MAX_IMAGE_PAYLOAD_BYTES;
63
- if (!exceedsDimensionCap && !exceedsPayloadCap) return false;
70
+ if (!exceedsDimensionCap && !exceedsPayloadCap) return null;
64
71
 
65
72
  const optimized = optimizeImageForTransport(
66
73
  block.source.data,
67
74
  block.source.media_type,
68
75
  );
69
- return optimized.data === block.source.data;
76
+ if (optimized.data !== block.source.data) {
77
+ return {
78
+ type: "image",
79
+ source: {
80
+ type: "base64",
81
+ media_type: optimized.mediaType,
82
+ data: optimized.data,
83
+ },
84
+ };
85
+ }
86
+ return { type: "text", text: UNSENDABLE_IMAGE_NOTE };
70
87
  }
71
88
 
72
89
  /**
73
- * Rewrite every stored message in a conversation that holds a permanently
74
- * unsendable image, replacing those image blocks with {@link
75
- * UNSENDABLE_IMAGE_NOTE}. Reads stored content directly (not the in-memory,
76
- * injection-enriched copy) so injected prefixes and hydrated source paths are
77
- * never written back.
90
+ * Rewrite every stored message in a conversation that holds an oversized image
91
+ * the provider rejects — whether a top-level block or one nested in a
92
+ * tool_result's contentBlocks — replacing it with its downscaled form, or with
93
+ * {@link UNSENDABLE_IMAGE_NOTE} when it cannot be shrunk on this host. Reads
94
+ * stored content directly (not the in-memory, injection-enriched copy) so
95
+ * injected prefixes and hydrated source paths are never written back.
78
96
  *
79
- * Idempotent: once an image is replaced by the note there is no image block
80
- * left to match, so re-running is a no-op. Returns the number of rewritten
97
+ * Idempotent: a downscaled image is within limits and a note is no longer an
98
+ * image, so neither matches on a second run. Returns the number of rewritten
81
99
  * messages.
82
100
  */
83
101
  export function persistUnsendableImageDowngrades(
@@ -99,10 +117,29 @@ export function persistUnsendableImageDowngrades(
99
117
 
100
118
  let changed = false;
101
119
  const next = (parsed as ContentBlock[]).map((block): ContentBlock => {
102
- if (block.type !== "image") return block;
103
- if (!isImagePermanentlyUnsendable(block)) return block;
104
- changed = true;
105
- return { type: "text", text: UNSENDABLE_IMAGE_NOTE };
120
+ if (block.type === "image") {
121
+ const replacement = oversizedImageReplacement(block);
122
+ if (!replacement) return block;
123
+ changed = true;
124
+ return replacement;
125
+ }
126
+ // Images returned by a tool (e.g. browser_screenshot) live inside the
127
+ // tool_result's contentBlocks, not as top-level blocks. Downgrade them
128
+ // in place so the tool_use/tool_result pairing stays intact.
129
+ if (block.type === "tool_result" && block.contentBlocks?.length) {
130
+ let nestedChanged = false;
131
+ const contentBlocks = block.contentBlocks.map((cb): ContentBlock => {
132
+ if (cb.type !== "image") return cb;
133
+ const replacement = oversizedImageReplacement(cb);
134
+ if (!replacement) return cb;
135
+ nestedChanged = true;
136
+ return replacement;
137
+ });
138
+ if (!nestedChanged) return block;
139
+ changed = true;
140
+ return { ...block, contentBlocks };
141
+ }
142
+ return block;
106
143
  });
107
144
  if (!changed) continue;
108
145
 
@@ -195,7 +195,7 @@ async function prepareConversationForMessage(
195
195
  "wiring in conversation-routes.ts into a shared helper.",
196
196
  );
197
197
  }
198
- const sourceActorPrincipalId = conversation.trustContext?.guardianPrincipalId;
198
+ const sourceActorPrincipalId = conversation.authContext?.actorPrincipalId;
199
199
  // CU is per-conversation (owns step count, AX tree history, loop detection).
200
200
  if (
201
201
  shouldAttachHostProxyForCapability(
@@ -39,6 +39,8 @@ import {
39
39
  conversationEntries,
40
40
  deleteConversation,
41
41
  getConversationMap,
42
+ } from "./conversation-registry.js";
43
+ import {
42
44
  getOrCreateConversation as getOrCreateActiveConversation,
43
45
  initConversationLifecycle,
44
46
  setCesClientPromise,