@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) 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 +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.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,3 +1,4 @@
1
+ import { optimizeImageForTransport } from "../../agent/image-optimize.js";
1
2
  import { getConfig } from "../../config/loader.js";
2
3
  import { HostBrowserProxy } from "../../daemon/host-browser-proxy.js";
3
4
  import type { ImageContent } from "../../providers/types.js";
@@ -1206,13 +1207,22 @@ export async function executeBrowserScreenshot(
1206
1207
  { quality: 80, fullPage },
1207
1208
  context.signal,
1208
1209
  );
1209
- const base64Data = buffer.toString("base64");
1210
+ const rawBase64 = buffer.toString("base64");
1211
+
1212
+ // Downscale before handing the screenshot to the model. A full-page
1213
+ // capture of a tall or high-DPI page can exceed Anthropic's 5 MB
1214
+ // per-image cap, which would otherwise persist into the conversation
1215
+ // history inside this tool_result and reject every subsequent turn.
1216
+ const { data: base64Data, mediaType } = optimizeImageForTransport(
1217
+ rawBase64,
1218
+ "image/jpeg",
1219
+ );
1210
1220
 
1211
1221
  const imageBlock: ImageContent = {
1212
1222
  type: "image" as const,
1213
1223
  source: {
1214
1224
  type: "base64" as const,
1215
- media_type: "image/jpeg",
1225
+ media_type: mediaType,
1216
1226
  data: base64Data,
1217
1227
  },
1218
1228
  };
@@ -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
  );
@@ -152,25 +152,56 @@ function isNullBodyStatus(status: number): boolean {
152
152
  return status === 204 || status === 205 || status === 304;
153
153
  }
154
154
 
155
+ // The WHATWG `Response` constructor only accepts 101 or the range 200–599 and
156
+ // throws a `RangeError` for anything else. Some servers — notably anti-bot
157
+ // gateways like LinkedIn and various job boards — reply with non-standard codes
158
+ // such as 999. Map those onto 502 so we can still build a Response; the real
159
+ // upstream status is preserved via `upstreamStatusByResponse` for reporting.
160
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
161
+ const RESPONSE_STATUS_SWITCHING_PROTOCOLS = 101;
162
+ const RESPONSE_STATUS_MIN = 200;
163
+ const RESPONSE_STATUS_MAX = 599;
164
+
165
+ function toConstructableStatus(status: number): number {
166
+ if (status === RESPONSE_STATUS_SWITCHING_PROTOCOLS) return status;
167
+ if (status >= RESPONSE_STATUS_MIN && status <= RESPONSE_STATUS_MAX) {
168
+ return status;
169
+ }
170
+ return 502;
171
+ }
172
+
173
+ const upstreamStatusByResponse = new WeakMap<Response, number>();
174
+
175
+ // Returns the real status the upstream server sent, which may fall outside the
176
+ // range the `Response` object can represent (e.g. 999). Falls back to
177
+ // `response.status` for responses built by native `fetch`, whose undici
178
+ // implementation already exposes non-standard codes directly.
179
+ export function getUpstreamStatus(response: Response): number {
180
+ return upstreamStatusByResponse.get(response) ?? response.status;
181
+ }
182
+
155
183
  export function buildFetchResponseFromNodeResponse(
156
184
  res: NodeHttpResponseLike,
157
185
  ): Response {
158
- const status = res.statusCode ?? 502;
186
+ const upstreamStatus = res.statusCode ?? 502;
187
+ const status = toConstructableStatus(upstreamStatus);
159
188
  const responseHeaders = buildResponseHeaders(res.headers);
160
189
  const statusText = res.statusMessage ?? "";
161
190
 
162
- if (isNullBodyStatus(status)) {
163
- // Drain any unexpected bytes and produce a valid null-body fetch Response.
164
- res.resume();
165
- return new Response(null, { status, statusText, headers: responseHeaders });
166
- }
191
+ const response = isNullBodyStatus(status)
192
+ ? // Drain any unexpected bytes and produce a valid null-body fetch Response.
193
+ (res.resume(),
194
+ new Response(null, { status, statusText, headers: responseHeaders }))
195
+ : new Response(Readable.toWeb(res) as unknown as BodyInit, {
196
+ status,
197
+ statusText,
198
+ headers: responseHeaders,
199
+ });
167
200
 
168
- const body = Readable.toWeb(res);
169
- return new Response(body as unknown as BodyInit, {
170
- status,
171
- statusText,
172
- headers: responseHeaders,
173
- });
201
+ if (status !== upstreamStatus) {
202
+ upstreamStatusByResponse.set(response, upstreamStatus);
203
+ }
204
+ return response;
174
205
  }
175
206
 
176
207
  function createAbortError(): Error {
@@ -252,7 +283,16 @@ const defaultRequestExecutor: WebFetchRequestExecutor = async (
252
283
 
253
284
  return await new Promise<Response>((resolve, reject) => {
254
285
  const req = requestFn(requestOptions, (res) => {
255
- resolve(buildFetchResponseFromNodeResponse(res));
286
+ // Building the fetch Response runs inside this HTTP `response` event
287
+ // callback. Any synchronous throw here would escape as an uncaught
288
+ // exception and crash the daemon, so funnel failures into `reject` to
289
+ // keep them local to this request.
290
+ try {
291
+ resolve(buildFetchResponseFromNodeResponse(res));
292
+ } catch (err) {
293
+ res.resume();
294
+ reject(err);
295
+ }
256
296
  });
257
297
  req.once("error", reject);
258
298
  req.end();
@@ -730,9 +770,10 @@ export async function executeWebFetch(
730
770
  );
731
771
  }
732
772
 
773
+ const upstreamStatus = getUpstreamStatus(response);
733
774
  const location = response.headers.get("location");
734
775
  const isRedirect =
735
- response.status >= 300 && response.status < 400 && !!location;
776
+ upstreamStatus >= 300 && upstreamStatus < 400 && !!location;
736
777
  if (!isRedirect) break;
737
778
 
738
779
  if (redirectCount >= MAX_REDIRECTS) {
@@ -741,7 +782,7 @@ export async function executeWebFetch(
741
782
  {
742
783
  url: requestedUrl,
743
784
  finalUrl: currentUrl.href,
744
- status: response.status,
785
+ status: getUpstreamStatus(response),
745
786
  redirectCount,
746
787
  },
747
788
  );
@@ -761,7 +802,7 @@ export async function executeWebFetch(
761
802
  {
762
803
  url: requestedUrl,
763
804
  finalUrl: currentUrl.href,
764
- status: response.status,
805
+ status: getUpstreamStatus(response),
765
806
  redirectCount,
766
807
  },
767
808
  );
@@ -773,7 +814,7 @@ export async function executeWebFetch(
773
814
  {
774
815
  url: requestedUrl,
775
816
  finalUrl: currentUrl.href,
776
- status: response.status,
817
+ status: getUpstreamStatus(response),
777
818
  redirectCount,
778
819
  },
779
820
  );
@@ -785,7 +826,7 @@ export async function executeWebFetch(
785
826
  {
786
827
  url: requestedUrl,
787
828
  finalUrl: currentUrl.href,
788
- status: response.status,
829
+ status: getUpstreamStatus(response),
789
830
  redirectCount,
790
831
  },
791
832
  );
@@ -805,7 +846,7 @@ export async function executeWebFetch(
805
846
  {
806
847
  url: requestedUrl,
807
848
  finalUrl: currentUrl.href,
808
- status: response.status,
849
+ status: getUpstreamStatus(response),
809
850
  redirectCount,
810
851
  },
811
852
  );
@@ -817,7 +858,7 @@ export async function executeWebFetch(
817
858
  {
818
859
  url: requestedUrl,
819
860
  finalUrl: currentUrl.href,
820
- status: response.status,
861
+ status: getUpstreamStatus(response),
821
862
  redirectCount,
822
863
  },
823
864
  );
@@ -844,7 +885,7 @@ export async function executeWebFetch(
844
885
  {
845
886
  url: requestedUrl,
846
887
  finalUrl: currentUrl.href,
847
- status: response.status,
888
+ status: getUpstreamStatus(response),
848
889
  contentType,
849
890
  redirectCount,
850
891
  },
@@ -909,7 +950,7 @@ export async function executeWebFetch(
909
950
  const content = formatWebFetchOutput({
910
951
  requestedUrl: safeRequestedUrl,
911
952
  finalUrl: sanitizeUrlForOutput(currentUrl),
912
- status: response.status,
953
+ status: getUpstreamStatus(response),
913
954
  statusText: response.statusText,
914
955
  contentType,
915
956
  bytesRead: body.bytesRead,
@@ -931,7 +972,7 @@ export async function executeWebFetch(
931
972
  const meta: WebFetchMetadata = {
932
973
  url: safeRequestedUrl,
933
974
  finalUrl: sanitizeUrlForOutput(currentUrl),
934
- status: response.status,
975
+ status: getUpstreamStatus(response),
935
976
  contentType: contentType || undefined,
936
977
  byteCount: body.bytesRead,
937
978
  charCount: sliced.length,
@@ -945,7 +986,7 @@ export async function executeWebFetch(
945
986
  };
946
987
 
947
988
  if (!response.ok) {
948
- const errorMessage = `Error: HTTP ${response.status}`;
989
+ const errorMessage = `Error: HTTP ${getUpstreamStatus(response)}`;
949
990
  return {
950
991
  content: `${errorMessage}\n\n${content}`,
951
992
  isError: true,
@@ -128,7 +128,7 @@ export const skillLoadTool = {
128
128
  name: "skill_load",
129
129
 
130
130
  description:
131
- 'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`.',
131
+ 'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. Bundled/first-party skills (like `app-builder`) are already installed — loading only activates their instructions for the current conversation, since skills unload between turns; treat "load" as activation, not installation, and don\'t tell users a bundled skill needs installing. Loading can still fail (e.g. a feature-gated skill returns "currently unavailable", or the load errors) — if it does, relay that specific error rather than claiming the skill is not installed or offering to install it. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`.',
132
132
 
133
133
  category: "skills",
134
134
 
@@ -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,
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { RiskLevel } from "../../permissions/types.js";
11
+ import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
11
12
  import type {
12
13
  ToolContext,
13
14
  ToolDefinition,
@@ -19,7 +20,7 @@ import type {
19
20
  // ---------------------------------------------------------------------------
20
21
 
21
22
  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;
23
+ /\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
24
  const APP_BUILDER_BUILD_RE =
24
25
  /\b(build|building|built|create|creating|created|make|making|made|generate|generating|generated)\b/i;
25
26
 
@@ -58,11 +59,31 @@ function isDynamicPageAppSubstitute(input: Record<string, unknown>): boolean {
58
59
  }
59
60
 
60
61
  const text = collectRoutingText(input).join(" ");
61
- if (!APP_BUILDER_ARTIFACT_RE.test(text)) {
62
- return false;
62
+ if (
63
+ APP_BUILDER_ARTIFACT_RE.test(text) &&
64
+ (APP_BUILDER_BUILD_RE.test(text) || /\b(app|application)\b/i.test(text))
65
+ ) {
66
+ return true;
63
67
  }
64
68
 
65
- return APP_BUILDER_BUILD_RE.test(text) || /\b(app|application)\b/i.test(text);
69
+ // Second signal: even when the model gives the surface a clean,
70
+ // non-app-sounding title (dodging the text regex above), substantial
71
+ // interactive HTML is an app being smuggled in as a transient surface.
72
+ // A genuinely transient page is small and static; an app has real
73
+ // scripted markup. Keep the bar high so simple snippets still pass.
74
+ return isSubstantialInteractiveHtml(input);
75
+ }
76
+
77
+ const INTERACTIVE_HTML_RE =
78
+ /<script\b|on[a-z]+\s*=|addEventListener|new Chart\b|window\.vellum\b/i;
79
+
80
+ function isSubstantialInteractiveHtml(input: Record<string, unknown>): boolean {
81
+ const data = asRecord(input.data);
82
+ const html = data?.html;
83
+ if (typeof html !== "string") {
84
+ return false;
85
+ }
86
+ return html.length > 2000 && INTERACTIVE_HTML_RE.test(html);
66
87
  }
67
88
 
68
89
  function collectRoutingText(input: Record<string, unknown>): string[] {
@@ -116,7 +137,7 @@ export const uiShowTool = {
116
137
  "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
117
138
  "- task_preferences: {} (no data needed — categories are rendered client-side)\n" +
118
139
  '- 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.",
140
+ "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
141
  category: "ui-surface",
121
142
  defaultRiskLevel: RiskLevel.Low,
122
143
  executionTarget: "host",
@@ -184,6 +205,12 @@ export const uiShowTool = {
184
205
  description:
185
206
  "When true, clicking an action does not dismiss the surface — the card stays visible and only the clicked action is marked as spent. Use for launcher or menu-style cards where multiple buttons may be clicked. Defaults to false.",
186
207
  },
208
+ activation_moment: {
209
+ type: "string",
210
+ enum: ACTIVATION_MOMENT_PARAMS,
211
+ description:
212
+ "Activation-rail telemetry tag (cohort only). Set this when this surface IS one of the activation funnel moments; the milestone is recorded automatically when the user commits the surface. Omit for all non-activation surfaces.",
213
+ },
187
214
  },
188
215
  required: ["surface_type", "data"],
189
216
  },
@@ -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