@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

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 (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  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__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Tests for the default `empty-response` plugin's `stop` hook.
3
+ *
4
+ * Covers:
5
+ * - The default hook's binary decision for the canonical cases: empty turn
6
+ * after a prior tool-use turn → continue (with the canonical nudge text);
7
+ * visible text → stop; prior turn already delivered visible text → stop;
8
+ * first model call with no prior turn → stop; provider refusal → continue
9
+ * (with the refusal-specific nudge text); refusal-but-recovered → stop.
10
+ * - The hook scopes its prior-turn signals to the current response cycle (the
11
+ * messages after the last genuine user prompt), so visible text from the
12
+ * inbound conversation does not suppress the nudge.
13
+ * - When the hook continues, it appends the nudge as a `user` message to
14
+ * `messages`.
15
+ * - End-to-end through `runHook` + the registry: registering the default
16
+ * plugin makes the hook fire, and a later-registered user hook chains after
17
+ * it and can read/override the decision.
18
+ *
19
+ * The loop's actual side-effects (retry-budget cap, history splice, log
20
+ * emission) live in `agent/loop.ts` and are covered by integration tests in
21
+ * `conversation-agent-loop.test.ts` / `agent-loop.test.ts`. This file isolates
22
+ * the hook.
23
+ */
24
+
25
+ import { beforeEach, describe, expect, test } from "bun:test";
26
+
27
+ import { HOOKS } from "../plugin-api/constants.js";
28
+ import type { PluginLogger, StopContext } from "../plugin-api/types.js";
29
+ import {
30
+ NUDGE_TEXT,
31
+ REFUSAL_NUDGE_TEXT,
32
+ } from "../plugins/defaults/empty-response/hooks/stop.js";
33
+ import stop from "../plugins/defaults/empty-response/hooks/stop.js";
34
+ import { defaultEmptyResponsePlugin } from "../plugins/defaults/empty-response/register.js";
35
+ import { runHook } from "../plugins/pipeline.js";
36
+ import {
37
+ registerPlugin,
38
+ resetPluginRegistryForTests,
39
+ } from "../plugins/registry.js";
40
+ import type { ContentBlock, Message } from "../providers/types.js";
41
+
42
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
43
+
44
+ const noopLogger: PluginLogger = {
45
+ info: () => {},
46
+ warn: () => {},
47
+ error: () => {},
48
+ debug: () => {},
49
+ };
50
+
51
+ const emptyTextBlock: ContentBlock = { type: "text", text: " " };
52
+
53
+ /** A prior assistant turn that issued a tool call but no visible text. */
54
+ const priorToolUseTurn: Message = {
55
+ role: "assistant",
56
+ content: [{ type: "tool_use", id: "tu_1", name: "read_file", input: {} }],
57
+ };
58
+
59
+ /** A prior assistant turn that delivered visible text to the user. */
60
+ const priorVisibleTextTurn: Message = {
61
+ role: "assistant",
62
+ content: [{ type: "text", text: "here is what I found earlier" }],
63
+ };
64
+
65
+ /** A genuine user prompt — the boundary that opens a new response cycle. */
66
+ function userPrompt(text: string): Message {
67
+ return { role: "user", content: [{ type: "text", text }] };
68
+ }
69
+
70
+ function makeCtx(overrides: Partial<StopContext> = {}): StopContext {
71
+ return {
72
+ conversationId: "conv-stop",
73
+ messages: [],
74
+ responseContent: [],
75
+ stopReason: null,
76
+ decision: "stop",
77
+ logger: noopLogger,
78
+ ...overrides,
79
+ };
80
+ }
81
+
82
+ // ─── Default decisions ───────────────────────────────────────────────────────
83
+
84
+ describe("empty-response stop hook — default decisions", () => {
85
+ test("empty turn after a prior tool-use turn → continue with canonical nudge", async () => {
86
+ // GIVEN a run that already issued a tool call, then returned an empty
87
+ // (whitespace-only) assistant turn with no visible text.
88
+ const ctx = makeCtx({
89
+ messages: [priorToolUseTurn],
90
+ responseContent: [emptyTextBlock],
91
+ });
92
+
93
+ // WHEN the default stop hook runs.
94
+ await stop(ctx);
95
+
96
+ // THEN it asks the loop to continue and appends the canonical nudge.
97
+ expect(ctx.decision).toBe("continue");
98
+ expect(ctx.messages.at(-1)).toEqual({
99
+ role: "user",
100
+ content: [{ type: "text", text: NUDGE_TEXT }],
101
+ });
102
+ });
103
+
104
+ test("turn contains visible text → stop", async () => {
105
+ // GIVEN a turn that delivered visible text.
106
+ const ctx = makeCtx({
107
+ messages: [priorToolUseTurn],
108
+ responseContent: [{ type: "text", text: "here is a summary" }],
109
+ });
110
+
111
+ // WHEN the hook runs.
112
+ await stop(ctx);
113
+
114
+ // THEN the decision stays at stop and nothing is appended.
115
+ expect(ctx.decision).toBe("stop");
116
+ expect(ctx.messages).toEqual([priorToolUseTurn]);
117
+ });
118
+
119
+ test("prior turn already delivered visible text → stop", async () => {
120
+ // GIVEN the model said its piece in an earlier turn this run, then ended
121
+ // with a side-effect tool and returned empty. Nudging would force a
122
+ // verbatim re-send of text the user already saw.
123
+ const ctx = makeCtx({
124
+ messages: [userPrompt("do X"), priorVisibleTextTurn, priorToolUseTurn],
125
+ responseContent: [],
126
+ });
127
+
128
+ // WHEN the hook runs.
129
+ await stop(ctx);
130
+
131
+ // THEN the decision stays at stop.
132
+ expect(ctx.decision).toBe("stop");
133
+ });
134
+
135
+ test("visible text before the last user prompt is ignored → continue", async () => {
136
+ // GIVEN the inbound conversation already contains an assistant turn with
137
+ // visible text, but it precedes this run's user prompt. The current cycle
138
+ // (after that prompt) holds only a tool-use turn, so the earlier text
139
+ // belongs to the prior conversation and must not suppress the nudge.
140
+ const ctx = makeCtx({
141
+ messages: [
142
+ priorVisibleTextTurn,
143
+ userPrompt("do the next thing"),
144
+ priorToolUseTurn,
145
+ ],
146
+ responseContent: [],
147
+ });
148
+
149
+ // WHEN the hook runs.
150
+ await stop(ctx);
151
+
152
+ // THEN it continues — the cycle scope sees only the tool-use turn, not the
153
+ // inbound conversation's visible text.
154
+ expect(ctx.decision).toBe("continue");
155
+ expect(ctx.messages.at(-1)).toEqual({
156
+ role: "user",
157
+ content: [{ type: "text", text: NUDGE_TEXT }],
158
+ });
159
+ });
160
+
161
+ test("first model call with no prior turn → stop", async () => {
162
+ // GIVEN an empty first assistant response with no prior turn this run and
163
+ // no refusal — not the pattern the organic-empty-turn nudge guards against.
164
+ const ctx = makeCtx({ messages: [], responseContent: [] });
165
+
166
+ // WHEN the hook runs.
167
+ await stop(ctx);
168
+
169
+ // THEN the decision stays at stop.
170
+ expect(ctx.decision).toBe("stop");
171
+ });
172
+
173
+ // ─── Refusal stop ──────────────────────────────────────────────────────────
174
+
175
+ test("refusal on the first call with no content → continue with refusal nudge", async () => {
176
+ // GIVEN the canonical failure mode: the provider's safety classifier zeros
177
+ // the response on the very first model call, returning `stopReason:
178
+ // "refusal"` and no visible text.
179
+ const ctx = makeCtx({
180
+ messages: [],
181
+ responseContent: [],
182
+ stopReason: "refusal",
183
+ });
184
+
185
+ // WHEN the hook runs.
186
+ await stop(ctx);
187
+
188
+ // THEN it continues with the refusal-specific nudge, even with no prior turn.
189
+ expect(ctx.decision).toBe("continue");
190
+ expect(ctx.messages.at(-1)).toEqual({
191
+ role: "user",
192
+ content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
193
+ });
194
+ });
195
+
196
+ test("refusal with a thinking-only block still continues", async () => {
197
+ // GIVEN a refusal whose only content is a thinking block — the user sees
198
+ // nothing.
199
+ const ctx = makeCtx({
200
+ messages: [],
201
+ responseContent: [
202
+ { type: "thinking", thinking: "...", signature: "sig" },
203
+ ],
204
+ stopReason: "refusal",
205
+ });
206
+
207
+ // WHEN the hook runs.
208
+ await stop(ctx);
209
+
210
+ // THEN it continues with the refusal-specific nudge.
211
+ expect(ctx.decision).toBe("continue");
212
+ expect(ctx.messages.at(-1)).toEqual({
213
+ role: "user",
214
+ content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
215
+ });
216
+ });
217
+
218
+ test("refusal but visible text present → stop (model recovered)", async () => {
219
+ // GIVEN a refusal that still delivered some visible text before refusing —
220
+ // the user has something to see.
221
+ const ctx = makeCtx({
222
+ responseContent: [{ type: "text", text: "partial answer" }],
223
+ stopReason: "refusal",
224
+ });
225
+
226
+ // WHEN the hook runs.
227
+ await stop(ctx);
228
+
229
+ // THEN the decision stays at stop.
230
+ expect(ctx.decision).toBe("stop");
231
+ });
232
+
233
+ test("refusal beats the post-tool-empty nudge wording", async () => {
234
+ // GIVEN conditions that would trip both the refusal branch and the
235
+ // post-tool-empty branch.
236
+ const ctx = makeCtx({
237
+ messages: [priorToolUseTurn],
238
+ responseContent: [],
239
+ stopReason: "refusal",
240
+ });
241
+
242
+ // WHEN the hook runs.
243
+ await stop(ctx);
244
+
245
+ // THEN refusal wins because its wording is more accurate.
246
+ expect(ctx.decision).toBe("continue");
247
+ expect(ctx.messages.at(-1)).toEqual({
248
+ role: "user",
249
+ content: [{ type: "text", text: REFUSAL_NUDGE_TEXT }],
250
+ });
251
+ });
252
+ });
253
+
254
+ // ─── Via runHook + registry ──────────────────────────────────────────────────
255
+
256
+ describe("empty-response stop hook — via runHook", () => {
257
+ beforeEach(() => {
258
+ resetPluginRegistryForTests();
259
+ });
260
+
261
+ test("registering the default plugin makes the hook continue on empty-after-tools", async () => {
262
+ // GIVEN the default empty-response plugin is registered.
263
+ registerPlugin(defaultEmptyResponsePlugin);
264
+
265
+ // WHEN the stop chain runs over an empty-after-tools context.
266
+ const result = await runHook<StopContext>(
267
+ HOOKS.STOP,
268
+ makeCtx({ messages: [priorToolUseTurn], responseContent: [] }),
269
+ );
270
+
271
+ // THEN the chain settles on continue with the canonical nudge appended.
272
+ expect(result.decision).toBe("continue");
273
+ expect(result.messages.at(-1)).toEqual({
274
+ role: "user",
275
+ content: [{ type: "text", text: NUDGE_TEXT }],
276
+ });
277
+ });
278
+
279
+ test("a later-registered user hook can override the default decision", async () => {
280
+ // GIVEN the default plugin registers first, then a user plugin whose hook
281
+ // observes the default's decision and forces a stop.
282
+ let observedDecision: string | null = null;
283
+ registerPlugin(defaultEmptyResponsePlugin);
284
+ registerPlugin({
285
+ manifest: { name: "force-stop", version: "0.0.1" },
286
+ hooks: {
287
+ stop: async (ctx: StopContext) => {
288
+ observedDecision = ctx.decision;
289
+ ctx.decision = "stop";
290
+ },
291
+ },
292
+ });
293
+
294
+ // WHEN the chain runs over an empty-after-tools context.
295
+ const result = await runHook<StopContext>(
296
+ HOOKS.STOP,
297
+ makeCtx({ messages: [priorToolUseTurn], responseContent: [] }),
298
+ );
299
+
300
+ // THEN the user hook saw the default's continue, and its override wins.
301
+ expect(observedDecision as string | null).toBe("continue");
302
+ expect(result.decision).toBe("stop");
303
+ });
304
+ });
@@ -19,7 +19,7 @@
19
19
  // Mirrors `src/config/feature-flag-cache.ts`. Duplicated by design — see
20
20
  // the "No source-module imports" section above.
21
21
  type FlagSlot = {
22
- overrides: Record<string, boolean> | null;
22
+ overrides: Record<string, boolean | string> | null;
23
23
  fromGateway: boolean;
24
24
  };
25
25
 
@@ -45,7 +45,7 @@ function flagSlot(): FlagSlot {
45
45
  * `clearFeatureFlagOverridesCache()` from `assistant-feature-flags.ts`.
46
46
  */
47
47
  export function setOverridesForTesting(
48
- overrides: Record<string, boolean>,
48
+ overrides: Record<string, boolean | string>,
49
49
  ): void {
50
50
  const s = flagSlot();
51
51
  s.overrides = { ...overrides };
@@ -260,3 +260,66 @@ describe("file_write tool PKB re-index hook", () => {
260
260
  expect(existsSync(join(workingDir, "pkb", "oops.md"))).toBe(true);
261
261
  });
262
262
  });
263
+
264
+ describe("file_write artifact-HTML guard", () => {
265
+ test("rejects a self-contained interactive HTML visualization", async () => {
266
+ const dir = makeTempDir();
267
+ const html =
268
+ "<!doctype html><html><head><title>Food Market</title></head>" +
269
+ "<body><canvas id='c'></canvas><script>" +
270
+ ("const data=[{x:1,y:2}];").padEnd(4000, "/") +
271
+ "new Chart(document.getElementById('c'), {data});</script></body></html>";
272
+
273
+ const result = await fileWriteTool.execute(
274
+ { path: "food-market-stats.html", content: html },
275
+ makeContext(dir),
276
+ );
277
+
278
+ expect(result.isError).toBe(true);
279
+ expect(result.content).toContain('skill: "app-builder"');
280
+ expect(existsSync(join(dir, "food-market-stats.html"))).toBe(false);
281
+ });
282
+
283
+ test("allows app-builder's thin shell index.html (external module script)", async () => {
284
+ const dir = makeTempDir();
285
+ const shell =
286
+ "<!doctype html><html><head>" +
287
+ "<link rel='stylesheet' href='/src/styles.css'>".padEnd(3200, " ") +
288
+ "</head><body><div id='app'></div>" +
289
+ "<script type='module' src='/src/main.tsx'></script></body></html>";
290
+
291
+ const result = await fileWriteTool.execute(
292
+ { path: "src/index.html", content: shell },
293
+ makeContext(dir),
294
+ );
295
+
296
+ expect(result.isError).toBe(false);
297
+ expect(existsSync(join(dir, "src", "index.html"))).toBe(true);
298
+ });
299
+
300
+ test("allows a small/static HTML snippet", async () => {
301
+ const dir = makeTempDir();
302
+ const result = await fileWriteTool.execute(
303
+ { path: "note.html", content: "<html><body><h1>Hi</h1></body></html>" },
304
+ makeContext(dir),
305
+ );
306
+
307
+ expect(result.isError).toBe(false);
308
+ expect(existsSync(join(dir, "note.html"))).toBe(true);
309
+ });
310
+
311
+ test("allows a non-HTML file even with inline script-like content", async () => {
312
+ const dir = makeTempDir();
313
+ const result = await fileWriteTool.execute(
314
+ {
315
+ path: "notes.md",
316
+ content:
317
+ "<script>" + "x".padEnd(2000, "x") + "</script>" + "\n# notes\n",
318
+ },
319
+ makeContext(dir),
320
+ );
321
+
322
+ expect(result.isError).toBe(false);
323
+ expect(existsSync(join(dir, "notes.md"))).toBe(true);
324
+ });
325
+ });
@@ -27,6 +27,7 @@ const ALLOWLIST = new Set([
27
27
  "clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
28
28
  "clients/macos/vellum-assistant/App/AppDelegate.swift",
29
29
  "clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
30
+ "apps/macos/src/main/bundle-flow.ts", // Electron main calls the local gateway (gatewayPort) with a Guardian token to scan bundles
30
31
  ".claude/skills/update/SKILL.md", // daemon health check script
31
32
 
32
33
  // --- Test fixtures that poll the daemon directly (gateway may require auth) ---
@@ -84,12 +85,21 @@ function isGatewayInternal(filePath: string): boolean {
84
85
  return filePath.startsWith("gateway/");
85
86
  }
86
87
 
88
+ /** Additional files allowed for the interpolated-port check only (use gateway port, not runtime). */
89
+ const INTERPOLATED_PORT_ALLOWLIST = new Set([
90
+ "apps/macos/src/main/bundle-flow.ts",
91
+ ]);
92
+
87
93
  /** Shared violation filter: exempt test files, gateway internals, and allowlisted paths. */
88
- function filterViolations(files: string[]): string[] {
94
+ function filterViolations(
95
+ files: string[],
96
+ extraAllowlist?: Set<string>,
97
+ ): string[] {
89
98
  return files.filter((f) => {
90
99
  if (isTestFile(f)) return false;
91
100
  if (isGatewayInternal(f)) return false;
92
101
  if (ALLOWLIST.has(f)) return false;
102
+ if (extraAllowlist?.has(f)) return false;
93
103
  return true;
94
104
  });
95
105
  }
@@ -181,7 +191,7 @@ describe("gateway-only API consumption guard", () => {
181
191
  }
182
192
 
183
193
  const files = grepOutput.split("\n").filter((f) => f.length > 0);
184
- const violations = filterViolations(files);
194
+ const violations = filterViolations(files, INTERPOLATED_PORT_ALLOWLIST);
185
195
 
186
196
  if (violations.length > 0) {
187
197
  const message = [
@@ -287,6 +287,19 @@ describe("mapGeminiError", () => {
287
287
  expect(msg).toContain("Rate limit");
288
288
  });
289
289
 
290
+ test("maps 402 status to a non-retryable out-of-credits message", () => {
291
+ const msg = mapGeminiError(new FakeApiError(402, "payment required"));
292
+ expect(msg).toContain("out of credits");
293
+ expect(msg).not.toContain("Please try again");
294
+ });
295
+
296
+ test("maps a managed-proxy 402 failure (plain Error) to out-of-credits message", () => {
297
+ const msg = mapGeminiError(
298
+ new Error("Managed proxy request failed (402): insufficient credits"),
299
+ );
300
+ expect(msg).toContain("out of credits");
301
+ });
302
+
290
303
  test("maps 500 status to server error message", () => {
291
304
  const msg = mapGeminiError(new FakeApiError(500, "internal"));
292
305
  expect(msg).toContain("temporarily unavailable");
@@ -20,7 +20,7 @@ mock.module("../util/logger.js", () => ({
20
20
  }));
21
21
 
22
22
  const _conversationMocks = new Map<string, unknown>();
23
- mock.module("../daemon/conversation-store.js", () => ({
23
+ mock.module("../daemon/conversation-registry.js", () => ({
24
24
  findConversation: (id: string) => _conversationMocks.get(id),
25
25
  }));
26
26
 
@@ -21,7 +21,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
21
21
  mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
22
22
 
23
23
  const _conversationMocks = new Map<string, unknown>();
24
- mock.module("../daemon/conversation-store.js", () => ({
24
+ mock.module("../daemon/conversation-registry.js", () => ({
25
25
  findConversation: (id: string) => _conversationMocks.get(id),
26
26
  }));
27
27
 
@@ -51,9 +51,7 @@ import {
51
51
  routeGuardianReply,
52
52
  } from "../runtime/guardian-reply-router.js";
53
53
  import * as pendingInteractions from "../runtime/pending-interactions.js";
54
- import {
55
- listGuardianDecisionPrompts,
56
- } from "../runtime/routes/guardian-action-routes.js";
54
+ import { listGuardianDecisionPrompts } from "../runtime/routes/guardian-action-routes.js";
57
55
 
58
56
  initializeDb();
59
57
 
@@ -143,7 +143,7 @@ mock.module("../util/logger.js", () => ({
143
143
  import {
144
144
  clearConversations,
145
145
  setConversation,
146
- } from "../daemon/conversation-store.js";
146
+ } from "../daemon/conversation-registry.js";
147
147
  import { handleConfirmationResponse } from "../daemon/handlers/conversations.js";
148
148
 
149
149
  describe("handleConfirmationResponse canonical status sync", () => {
@@ -56,6 +56,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
56
56
  markStaleRunsAsMissed: mockMarkStaleRunsAsMissed,
57
57
  markStaleRunningAsError: mockMarkStaleRunningAsError,
58
58
  countCompletedHeartbeatRuns: mock(() => 10),
59
+ countCompletedRunsToday: mock(() => 0),
59
60
  countRecentConsecutiveRuns: mock(() => 0),
60
61
  }));
61
62
 
@@ -32,6 +32,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
32
32
  markStaleRunningAsError: mockMarkStaleRunningAsError,
33
33
  listHeartbeatRuns: mockListHeartbeatRuns,
34
34
  countCompletedHeartbeatRuns: mockCountCompletedHeartbeatRuns,
35
+ countCompletedRunsToday: mock(() => 0),
35
36
  countRecentConsecutiveRuns: mock(() => 0),
36
37
  }));
37
38
 
@@ -0,0 +1,110 @@
1
+ // Type-only import: this binds the mock to the real `Provider` contract so it
2
+ // can be passed to a live `AgentLoop` without casts. It must stay `import type`
3
+ // — a value import would pull `providers/types.ts`'s runtime exports into this
4
+ // shared helper, which the test-machinery isolation rule forbids.
5
+ import type {
6
+ Message,
7
+ Provider,
8
+ ProviderResponse,
9
+ SendMessageOptions,
10
+ ToolDefinition,
11
+ } from "../../providers/types.js";
12
+
13
+ /**
14
+ * Records the arguments of a single `provider.sendMessage` invocation so tests
15
+ * can assert on what the agent loop sent (messages, tools, system prompt, the
16
+ * resolved options bag).
17
+ */
18
+ export interface RecordedProviderCall {
19
+ messages: Message[];
20
+ tools?: ToolDefinition[];
21
+ systemPrompt?: string;
22
+ options?: SendMessageOptions;
23
+ }
24
+
25
+ /**
26
+ * One scripted provider turn. A `ProviderResponse` is returned normally; an
27
+ * `Error` is thrown to simulate a provider HTTP rejection (e.g. a
28
+ * context-too-large error the orchestrator must recover from).
29
+ */
30
+ export type ScriptedResponse = ProviderResponse | Error;
31
+
32
+ /**
33
+ * A mock provider that returns pre-configured responses in sequence.
34
+ *
35
+ * Drives the real {@link import("../../agent/loop.js").AgentLoop} by mocking
36
+ * only the provider HTTP boundary: each call returns the next scripted
37
+ * `ProviderResponse` (the last response repeats once the script is exhausted)
38
+ * and replays its text blocks as `text_delta` events so the loop streams
39
+ * exactly as it would against a live provider. A scripted `Error` entry is
40
+ * thrown instead of returned, so a rejection can be sequenced before a
41
+ * recovery response.
42
+ */
43
+ export function createMockProvider(
44
+ responses: ScriptedResponse[],
45
+ name = "mock",
46
+ ): {
47
+ provider: Provider;
48
+ calls: RecordedProviderCall[];
49
+ } {
50
+ const calls: RecordedProviderCall[] = [];
51
+ let callIndex = 0;
52
+
53
+ const provider: Provider = {
54
+ name,
55
+ async sendMessage(
56
+ messages: Message[],
57
+ options?: SendMessageOptions,
58
+ ): Promise<ProviderResponse> {
59
+ calls.push({
60
+ messages: [...messages],
61
+ tools: options?.tools,
62
+ systemPrompt: options?.systemPrompt,
63
+ options,
64
+ });
65
+ const scripted = responses[callIndex] ?? responses[responses.length - 1];
66
+ callIndex++;
67
+
68
+ if (scripted instanceof Error) {
69
+ throw scripted;
70
+ }
71
+
72
+ // Replay streaming deltas for text blocks, mirroring a live provider.
73
+ if (options?.onEvent) {
74
+ for (const block of scripted.content) {
75
+ if (block.type === "text") {
76
+ options.onEvent({ type: "text_delta", text: block.text });
77
+ }
78
+ }
79
+ }
80
+
81
+ return scripted;
82
+ },
83
+ };
84
+
85
+ return { provider, calls };
86
+ }
87
+
88
+ /** A scripted assistant turn that ends with a plain text response. */
89
+ export function textResponse(text: string): ProviderResponse {
90
+ return {
91
+ content: [{ type: "text", text }],
92
+ model: "mock-model",
93
+ usage: { inputTokens: 10, outputTokens: 5 },
94
+ stopReason: "end_turn",
95
+ };
96
+ }
97
+
98
+ /** A scripted assistant turn that invokes a single tool. */
99
+ export function toolUseResponse(
100
+ id: string,
101
+ name: string,
102
+ input: Record<string, unknown>,
103
+ ): ProviderResponse {
104
+ return {
105
+ content: [{ type: "tool_use", id, name, input }],
106
+ model: "mock-model",
107
+ usage: { inputTokens: 10, outputTokens: 5 },
108
+ stopReason: "tool_use",
109
+ };
110
+ }