@vellumai/assistant 0.8.8 → 0.8.9-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 (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -68,6 +68,73 @@ export type ProxyResolver = (
68
68
  input: Record<string, unknown>,
69
69
  ) => Promise<ExecutorResult>;
70
70
 
71
+ /**
72
+ * Validate a `source_files` map (LLM input is not type-checked at runtime).
73
+ * Returns an error ExecutorResult when invalid, or null when absent/valid.
74
+ */
75
+ function validateSourceFiles(sourceFiles: unknown): ExecutorResult | null {
76
+ if (sourceFiles == null) {
77
+ return null;
78
+ }
79
+ if (typeof sourceFiles !== "object" || Array.isArray(sourceFiles)) {
80
+ return {
81
+ content: JSON.stringify({
82
+ error:
83
+ "source_files must be an object mapping relative file paths to string contents",
84
+ }),
85
+ isError: true,
86
+ };
87
+ }
88
+ for (const [key, val] of Object.entries(sourceFiles)) {
89
+ if (typeof val !== "string") {
90
+ return {
91
+ content: JSON.stringify({
92
+ error: `source_files["${key}"] must be a string, got ${typeof val}`,
93
+ }),
94
+ isError: true,
95
+ };
96
+ }
97
+ }
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * Resolve an app name when the model omits it. The preview card's title is
103
+ * "always include" guidance and almost always present, so it's the best
104
+ * fallback — a real, meaningful name — before a generic placeholder.
105
+ */
106
+ function resolveAppName(input: AppCreateInput): string {
107
+ const previewTitle =
108
+ input.preview && typeof input.preview.title === "string"
109
+ ? input.preview.title
110
+ : undefined;
111
+ for (const candidate of [input.name, previewTitle]) {
112
+ if (typeof candidate === "string" && candidate.trim() !== "") {
113
+ return candidate.trim();
114
+ }
115
+ }
116
+ return "New App";
117
+ }
118
+
119
+ /**
120
+ * Compile-result fields shared by the app_refresh and app_update responses.
121
+ * On failure the errors/warnings are surfaced so the agent can fix them.
122
+ */
123
+ function compileResultPayload(
124
+ compileResult: Awaited<ReturnType<typeof compileApp>>,
125
+ ): Record<string, unknown> {
126
+ return {
127
+ compiled: compileResult.ok,
128
+ ...(compileResult.ok
129
+ ? { compile_duration_ms: compileResult.durationMs }
130
+ : {
131
+ compile_errors: compileResult.errors,
132
+ compile_warnings: compileResult.warnings,
133
+ compile_duration_ms: compileResult.durationMs,
134
+ }),
135
+ };
136
+ }
137
+
71
138
  // ---------------------------------------------------------------------------
72
139
  // app_create
73
140
  // ---------------------------------------------------------------------------
@@ -76,14 +143,12 @@ export interface AppCreateInput {
76
143
  name: string;
77
144
  description?: string;
78
145
  schema_json?: string;
79
- /**
80
- * Retired single-file shortcut. Kept in the type so legacy or stale callers
81
- * get a clear tool error instead of silently creating a v2 app with stale
82
- * scaffold content.
83
- */
146
+ /** Retired single-file shortcut. Returns a guidance error. */
84
147
  html?: unknown;
85
- /** Retired single-file multi-page shortcut. */
148
+ /** Retired single-file multi-page shortcut. Returns a guidance error. */
86
149
  pages?: unknown;
150
+ /** Lenient alias. Folded into preview.icon when preview.icon is absent. */
151
+ icon?: unknown;
87
152
  auto_open?: boolean;
88
153
  preview?: Record<string, unknown>;
89
154
  source_files?: Record<string, string>;
@@ -94,15 +159,20 @@ export async function executeAppCreate(
94
159
  store: AppStore,
95
160
  proxyToolResolver?: ProxyResolver,
96
161
  ): Promise<ExecutorResult> {
97
- const name = input.name;
162
+ // The model sometimes omits a name; resolve a sensible one rather than
163
+ // erroring out so the build still succeeds. Users can rename via app_update.
164
+ const name = resolveAppName(input);
98
165
  const description = input.description;
99
166
  const schemaJson = input.schema_json ?? "{}";
100
167
 
168
+ // Retired shortcut: a top-level `html` is no longer accepted. Reject with a
169
+ // helpful message (rather than a cryptic schema error) so the model writes a
170
+ // multi-file TSX app under src/ instead.
101
171
  if (Object.prototype.hasOwnProperty.call(input, "html")) {
102
172
  return {
103
173
  content: JSON.stringify({
104
174
  error:
105
- "app_create no longer accepts html. Create the app scaffold, write src/index.html and src/main.tsx with file_write, then call app_refresh.",
175
+ "app_create no longer accepts html. Build a multi-file TSX app under src/ (src/index.html + src/main.tsx + src/App.tsx) instead.",
106
176
  }),
107
177
  isError: true,
108
178
  };
@@ -120,44 +190,18 @@ export async function executeAppCreate(
120
190
  const autoOpen = input.auto_open !== false; // default true
121
191
  const preview = input.preview;
122
192
 
123
- // Validate required fields - LLM input is not type-checked at runtime
124
- if (typeof name !== "string" || name.trim() === "") {
125
- return {
126
- content: JSON.stringify({
127
- error: "name is required and must be a non-empty string",
128
- }),
129
- isError: true,
130
- };
131
- }
132
-
133
- if (input.source_files != null) {
134
- if (
135
- typeof input.source_files !== "object" ||
136
- Array.isArray(input.source_files)
137
- ) {
138
- return {
139
- content: JSON.stringify({
140
- error:
141
- "source_files must be an object mapping relative file paths to string contents",
142
- }),
143
- isError: true,
144
- };
145
- }
146
- for (const [key, val] of Object.entries(input.source_files)) {
147
- if (typeof val !== "string") {
148
- return {
149
- content: JSON.stringify({
150
- error: `source_files["${key}"] must be a string, got ${typeof val}`,
151
- }),
152
- isError: true,
153
- };
154
- }
155
- }
193
+ const sourceFilesError = validateSourceFiles(input.source_files);
194
+ if (sourceFilesError) {
195
+ return sourceFilesError;
156
196
  }
157
197
 
158
198
  // Extract icon from preview if provided - only persist emoji-like values,
159
199
  // not URLs which would render as raw strings in UI and bundle manifests.
160
- const rawIcon = preview?.icon as string | undefined;
200
+ // Lenient alias: a top-level `icon` is folded in when preview.icon is absent.
201
+ const rawIcon = (preview?.icon ??
202
+ (typeof input.icon === "string" ? input.icon : undefined)) as
203
+ | string
204
+ | undefined;
161
205
  const icon = rawIcon && !rawIcon.startsWith("http") ? rawIcon : undefined;
162
206
 
163
207
  const app = store.createApp({
@@ -351,14 +395,7 @@ export async function executeAppRefresh(
351
395
  refreshed: true,
352
396
  appId: updated.id,
353
397
  name: updated.name,
354
- compiled: compileResult.ok,
355
- ...(compileResult.ok
356
- ? { compile_duration_ms: compileResult.durationMs }
357
- : {
358
- compile_errors: compileResult.errors,
359
- compile_warnings: compileResult.warnings,
360
- compile_duration_ms: compileResult.durationMs,
361
- }),
398
+ ...compileResultPayload(compileResult),
362
399
  }),
363
400
  isError: false,
364
401
  };
@@ -374,6 +411,85 @@ export async function executeAppRefresh(
374
411
  };
375
412
  }
376
413
 
414
+ // ---------------------------------------------------------------------------
415
+ // app_update
416
+ // ---------------------------------------------------------------------------
417
+
418
+ export interface AppUpdateInput {
419
+ app_id: string;
420
+ name?: string;
421
+ description?: string;
422
+ schema_json?: string;
423
+ source_files?: Record<string, string>;
424
+ }
425
+
426
+ export async function executeAppUpdate(
427
+ input: AppUpdateInput,
428
+ store: AppStore,
429
+ ): Promise<ExecutorResult> {
430
+ const app = store.getApp(input.app_id);
431
+ if (!app) {
432
+ return {
433
+ content: JSON.stringify({ error: `App '${input.app_id}' not found` }),
434
+ isError: true,
435
+ };
436
+ }
437
+
438
+ const sourceFilesError = validateSourceFiles(input.source_files);
439
+ if (sourceFilesError) {
440
+ return sourceFilesError;
441
+ }
442
+
443
+ if (input.source_files) {
444
+ for (const [filePath, content] of Object.entries(input.source_files)) {
445
+ store.writeAppFile(input.app_id, filePath, content);
446
+ }
447
+ }
448
+
449
+ const updates: Partial<
450
+ Pick<AppDefinition, "name" | "description" | "schemaJson">
451
+ > = {};
452
+ if (typeof input.name === "string" && input.name.trim() !== "") {
453
+ updates.name = input.name.trim();
454
+ }
455
+ if (typeof input.description === "string") {
456
+ updates.description = input.description;
457
+ }
458
+ if (typeof input.schema_json === "string") {
459
+ updates.schemaJson = input.schema_json;
460
+ }
461
+
462
+ // An empty update still bumps updatedAt, triggering a client surface refresh.
463
+ const updated = store.updateApp(input.app_id, updates);
464
+
465
+ // Multifile apps recompile so the agent sees any errors from the edited
466
+ // source instead of serving a stale dist (mirrors app_refresh).
467
+ if (app.formatVersion === 2) {
468
+ const appDir = getAppDirPath(input.app_id);
469
+ const compileResult = await compileApp(appDir);
470
+ return {
471
+ content: JSON.stringify({
472
+ updated: true,
473
+ appId: updated.id,
474
+ name: updated.name,
475
+ description: updated.description,
476
+ ...compileResultPayload(compileResult),
477
+ }),
478
+ isError: false,
479
+ };
480
+ }
481
+
482
+ return {
483
+ content: JSON.stringify({
484
+ updated: true,
485
+ appId: updated.id,
486
+ name: updated.name,
487
+ description: updated.description,
488
+ }),
489
+ isError: false,
490
+ };
491
+ }
492
+
377
493
  // ---------------------------------------------------------------------------
378
494
  // app_generate_icon
379
495
  // ---------------------------------------------------------------------------
@@ -1,5 +1,6 @@
1
1
  import { join, resolve, sep } from "node:path";
2
2
 
3
+ import { getAppsDir } from "../../memory/app-store.js";
3
4
  import { enqueuePkbIndexJob } from "../../memory/jobs/embed-pkb-file.js";
4
5
  import { PKB_WORKSPACE_SCOPE } from "../../memory/pkb/types.js";
5
6
  import { RiskLevel } from "../../permissions/types.js";
@@ -17,6 +18,29 @@ import type {
17
18
 
18
19
  const logger = getLogger("file-write");
19
20
 
21
+ /**
22
+ * Detects an attempt to write a self-contained interactive HTML document —
23
+ * the signature of a "visualization" / "artifact" the model should be
24
+ * building as a real app via the app-builder skill, not dumping as a loose
25
+ * file. We deliberately trip ONLY on standalone docs with a substantial
26
+ * INLINE script (the data + rendering live in the file). app-builder's own
27
+ * scaffold writes a thin shell whose only script is an external module
28
+ * (`<script type="module" src="/src/main.tsx">`) with an empty body, so this
29
+ * never blocks the app-builder workflow it redirects to.
30
+ */
31
+ const STANDALONE_HTML_RE = /<!doctype\s+html|<html[\s>]/i;
32
+ const INLINE_SCRIPT_RE = /<script\b(?![^>]*\bsrc=)[^>]*>[\s\S]{400,}?<\/script>/i;
33
+
34
+ function isSelfContainedArtifactHtml(path: string, content: string): boolean {
35
+ if (!/\.html?$/i.test(path)) return false;
36
+ if (content.length < 3000) return false;
37
+ if (!STANDALONE_HTML_RE.test(content)) return false;
38
+ return INLINE_SCRIPT_RE.test(content);
39
+ }
40
+
41
+ const ARTIFACT_REDIRECT_MESSAGE =
42
+ 'Error: This looks like a self-contained interactive visualization/artifact written as a loose HTML file. Do not build these as standalone .html files. Load the app-builder skill first with `skill_load` using `skill: "app-builder"`, then build it as a real persistent app with `app_create` (the skill loads frontend-design for the visual pass and provides chart widgets). If this genuinely needs to be a raw HTML file inside an existing code project, write it under that project folder.';
43
+
20
44
  /**
21
45
  * Returns `true` iff `absPath` is an absolute path that resolves strictly
22
46
  * inside `pkbRoot`. Matches the containment semantics used elsewhere in the
@@ -82,6 +106,16 @@ export const fileWriteTool = {
82
106
  };
83
107
  }
84
108
 
109
+ // Redirect self-contained interactive HTML artifacts to app-builder.
110
+ // Exempt writes that land inside the apps directory (app-builder's own
111
+ // scaffold/iteration writes) so we never block the workflow we point to.
112
+ if (isSelfContainedArtifactHtml(rawPath, fileContent)) {
113
+ const candidate = resolve(context.workingDir, rawPath);
114
+ if (!isInsidePkbRoot(candidate, getAppsDir())) {
115
+ return { content: ARTIFACT_REDIRECT_MESSAGE, isError: true };
116
+ }
117
+ }
118
+
85
119
  const ops = new FileSystemOps((path, opts) =>
86
120
  sandboxPolicy(path, context.workingDir, opts),
87
121
  );
@@ -1,4 +1,4 @@
1
- import { findConversation } from "../../daemon/conversation-store.js";
1
+ import { findConversation } from "../../daemon/conversation-registry.js";
2
2
  import { getConversationOverrideProfile } from "../../memory/conversation-crud.js";
3
3
  import type { Message } from "../../providers/types.js";
4
4
  import { getSubagentManager } from "../../subagent/index.js";
@@ -102,9 +102,7 @@ export async function executeSubagentSpawn(
102
102
  ...(inheritedOverrideProfile
103
103
  ? { overrideProfile: inheritedOverrideProfile }
104
104
  : {}),
105
- ...(context.toolUseId
106
- ? { parentToolUseId: context.toolUseId }
107
- : {}),
105
+ ...(context.toolUseId ? { parentToolUseId: context.toolUseId } : {}),
108
106
  ...forkFields,
109
107
  },
110
108
  sendToClient as (msg: unknown) => void,
@@ -19,7 +19,7 @@ import type {
19
19
  // ---------------------------------------------------------------------------
20
20
 
21
21
  const APP_BUILDER_ARTIFACT_RE =
22
- /\b(app|apps|application|applications|website|websites|site|sites|dashboard|dashboards|game|games|calculator|calculators|tracker|trackers|visualization|visualizations|visualisation|visualisations|tool|tools|utility|utilities|counter|counters)\b/i;
22
+ /\b(app|apps|application|applications|website|websites|site|sites|dashboard|dashboards|game|games|calculator|calculators|tracker|trackers|visualization|visualizations|visualisation|visualisations|visualize|visualise|artifact|artifacts|chart|charts|graph|graphs|tool|tools|utility|utilities|counter|counters)\b/i;
23
23
  const APP_BUILDER_BUILD_RE =
24
24
  /\b(build|building|built|create|creating|created|make|making|made|generate|generating|generated)\b/i;
25
25
 
@@ -58,11 +58,31 @@ function isDynamicPageAppSubstitute(input: Record<string, unknown>): boolean {
58
58
  }
59
59
 
60
60
  const text = collectRoutingText(input).join(" ");
61
- if (!APP_BUILDER_ARTIFACT_RE.test(text)) {
62
- return false;
61
+ if (
62
+ APP_BUILDER_ARTIFACT_RE.test(text) &&
63
+ (APP_BUILDER_BUILD_RE.test(text) || /\b(app|application)\b/i.test(text))
64
+ ) {
65
+ return true;
63
66
  }
64
67
 
65
- return APP_BUILDER_BUILD_RE.test(text) || /\b(app|application)\b/i.test(text);
68
+ // Second signal: even when the model gives the surface a clean,
69
+ // non-app-sounding title (dodging the text regex above), substantial
70
+ // interactive HTML is an app being smuggled in as a transient surface.
71
+ // A genuinely transient page is small and static; an app has real
72
+ // scripted markup. Keep the bar high so simple snippets still pass.
73
+ return isSubstantialInteractiveHtml(input);
74
+ }
75
+
76
+ const INTERACTIVE_HTML_RE =
77
+ /<script\b|on[a-z]+\s*=|addEventListener|new Chart\b|window\.vellum\b/i;
78
+
79
+ function isSubstantialInteractiveHtml(input: Record<string, unknown>): boolean {
80
+ const data = asRecord(input.data);
81
+ const html = data?.html;
82
+ if (typeof html !== "string") {
83
+ return false;
84
+ }
85
+ return html.length > 2000 && INTERACTIVE_HTML_RE.test(html);
66
86
  }
67
87
 
68
88
  function collectRoutingText(input: Record<string, unknown>): string[] {
@@ -116,7 +136,7 @@ export const uiShowTool = {
116
136
  "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
117
137
  "- task_preferences: {} (no data needed — categories are rendered client-side)\n" +
118
138
  '- work_result: { eyebrow?, status?: "completed"|"partial"|"failed"|"in_progress", summary?, metrics?: [{ label, value, detail?, tone?: "neutral"|"positive"|"warning"|"negative" }], sections?: [{ id?, title, description?, type?: "items"|"timeline"|"diff"|"artifacts"|"warnings", items?: [{ id?, title, description?, status?, tone?, metadata?: [{ label, value }], href? }], diffs?: [{ label?, before?, after? }] }] }. Shows a structured receipt after real work: what changed, what was skipped, proof points, and next actions. Keep display-only unless explicit follow-up buttons are needed.\n\n' +
119
- "Proactively show a task_progress card before multi-step or long-running work (web searches, file operations, research). Show it before your first tool call, then update steps as work progresses.",
139
+ "Emit a task_progress card BEFORE your first tool call on any multi-step, long-running, or post-form turn (web searches, file operations, research), and keep its steps updated as work progresses. Skills that estimate >30s of work should declare progress upfront with concrete step labels (and counts when known).",
120
140
  category: "ui-surface",
121
141
  defaultRiskLevel: RiskLevel.Low,
122
142
  executionTarget: "host",
@@ -9,10 +9,9 @@ import type { WorkspaceMigration } from "./types.js";
9
9
  * `conversationSummarization` is invoked from `ContextWindowManager.
10
10
  * updateSummary()` during mid-loop compaction. Without a call-site entry it
11
11
  * falls through to `llm.default` (opus + `effort: "max"` + `thinking:
12
- * { enabled: true }` + `maxTokens: 64000`), which is far too expensive for
13
- * summarizing a ~150k-token transcript inside the agent-loop plugin
14
- * pipeline's 30s budget we were hitting `PluginTimeoutError` and hard-
15
- * failing the turn.
12
+ * { enabled: true }` + `maxTokens: 64000`), which is far too expensive and
13
+ * slow for summarizing a ~150k-token transcript inside the agent loop
14
+ * the summary call would stall the turn.
16
15
  *
17
16
  * This migration seeds `effort: "low"` and `thinking: { enabled: false }`
18
17
  * (and opus-4.7 as the model when absent) so the summary call runs cheaply
@@ -99,7 +98,7 @@ export const seedConversationSummarizationCallsiteMigration: WorkspaceMigration
99
98
  },
100
99
  down(_workspaceDir: string): void {
101
100
  // Forward-only: removing the seeded defaults would reintroduce the
102
- // 30s pipeline-budget timeout this migration fixes.
101
+ // expensive, slow summary call this migration fixes.
103
102
  },
104
103
  };
105
104
 
@@ -20,65 +20,89 @@ import type { WorkspaceMigration } from "./types.js";
20
20
  // explicitly disabled. It skips profiles that:
21
21
  // - Don't exist (no profile to patch)
22
22
  // - Already have thinking enabled (idempotent)
23
- // - Are not source: "managed" (user-created profiles are untouched)
23
+ // - Are source: "user" (user-created profiles are untouched)
24
+ // - Have a non-managed, non-absent source (unknown origin)
24
25
  // - Use a non-Anthropic provider (adaptive thinking is Anthropic-specific)
25
26
 
26
27
  const ADAPTIVE_THINKING = { enabled: true, streamThinking: true } as const;
27
28
  const TARGET_PROFILES = ["balanced", "quality-optimized"] as const;
28
29
 
29
- export const enableAdaptiveThinkingManagedProfilesMigration: WorkspaceMigration =
30
- {
31
- id: "097-enable-adaptive-thinking-managed-profiles",
32
- description:
33
- "Enable adaptive thinking on managed balanced and quality-optimized profiles",
34
- run(workspaceDir: string): void {
35
- const configPath = join(workspaceDir, "config.json");
36
- if (!existsSync(configPath)) return;
30
+ /**
31
+ * Patch managed Anthropic profiles that are missing adaptive thinking.
32
+ *
33
+ * Exported so lifecycle.ts can re-run the repair after mergeDefaultWorkspaceConfig()
34
+ * and seedInferenceProfiles(). On-platform instances with a config overlay have their
35
+ * profiles overwritten by the overlay merge (which runs after workspace migrations),
36
+ * so the migration alone is insufficient — the post-overlay call ensures the repair
37
+ * sticks even when the overlay supplies profiles without thinking enabled.
38
+ *
39
+ * The function is idempotent: profiles that already have thinking enabled are skipped.
40
+ */
41
+ export function repairAdaptiveThinkingOnManagedProfiles(
42
+ workspaceDir: string,
43
+ ): void {
44
+ const configPath = join(workspaceDir, "config.json");
45
+ if (!existsSync(configPath)) return;
37
46
 
38
- let config: Record<string, unknown>;
39
- try {
40
- const raw = JSON.parse(readFileSync(configPath, "utf-8"));
41
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
42
- config = raw as Record<string, unknown>;
43
- } catch {
44
- return;
45
- }
47
+ let config: Record<string, unknown>;
48
+ try {
49
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
50
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
51
+ config = raw as Record<string, unknown>;
52
+ } catch {
53
+ return;
54
+ }
46
55
 
47
- const llm = readObject(config.llm);
48
- if (llm === null) return;
56
+ const llm = readObject(config.llm);
57
+ if (llm === null) return;
49
58
 
50
- const profiles = readObject(llm.profiles);
51
- if (profiles === null) return;
59
+ const profiles = readObject(llm.profiles);
60
+ if (profiles === null) return;
52
61
 
53
- let changed = false;
62
+ let changed = false;
54
63
 
55
- for (const name of TARGET_PROFILES) {
56
- const profile = readObject(profiles[name]);
57
- if (profile === null) continue;
64
+ for (const name of TARGET_PROFILES) {
65
+ const profile = readObject(profiles[name]);
66
+ if (profile === null) continue;
58
67
 
59
- // Only patch managed Anthropic profiles.
60
- if (profile.source !== "managed") continue;
61
- if (
62
- typeof profile.provider === "string" &&
63
- profile.provider !== "anthropic"
64
- ) {
65
- continue;
66
- }
68
+ // Only patch managed Anthropic profiles.
69
+ // Legacy profiles created before the `source` metadata field was introduced
70
+ // have source=undefined. Treat these as managed when the profile name is one
71
+ // of the canonical managed names (which TARGET_PROFILES already guarantees)
72
+ // and the provider is Anthropic (or absent). Explicit `source: "user"`
73
+ // profiles are always skipped.
74
+ if (profile.source === "user") continue;
75
+ if (profile.source !== undefined && profile.source !== "managed") continue;
76
+ if (
77
+ typeof profile.provider === "string" &&
78
+ profile.provider !== "anthropic"
79
+ ) {
80
+ continue;
81
+ }
67
82
 
68
- // Skip if thinking is already enabled.
69
- const thinking = readObject(profile.thinking);
70
- if (thinking !== null && thinking.enabled === true) continue;
83
+ // Skip if thinking is already enabled.
84
+ const thinking = readObject(profile.thinking);
85
+ if (thinking !== null && thinking.enabled === true) continue;
71
86
 
72
- profile.thinking = { ...ADAPTIVE_THINKING };
73
- profiles[name] = profile;
74
- changed = true;
75
- }
87
+ profile.thinking = { ...ADAPTIVE_THINKING };
88
+ profiles[name] = profile;
89
+ changed = true;
90
+ }
76
91
 
77
- if (changed) {
78
- llm.profiles = profiles;
79
- config.llm = llm;
80
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
81
- }
92
+ if (changed) {
93
+ llm.profiles = profiles;
94
+ config.llm = llm;
95
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
96
+ }
97
+ }
98
+
99
+ export const enableAdaptiveThinkingManagedProfilesMigration: WorkspaceMigration =
100
+ {
101
+ id: "097-enable-adaptive-thinking-managed-profiles",
102
+ description:
103
+ "Enable adaptive thinking on managed balanced and quality-optimized profiles",
104
+ run(workspaceDir: string): void {
105
+ repairAdaptiveThinkingOnManagedProfiles(workspaceDir);
82
106
  },
83
107
  down(_workspaceDir: string): void {
84
108
  // Forward-only.