@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
@@ -1,11 +1,12 @@
1
- import { beforeEach, describe, expect, test } from "bun:test";
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
2
 
3
- import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
4
- import { defaultInjectorsPlugin } from "../plugins/defaults/injectors/register.js";
5
3
  import {
6
- registerPlugin,
7
- resetPluginRegistryForTests,
8
- } from "../plugins/registry.js";
4
+ clearConversations,
5
+ setConversation,
6
+ } from "../daemon/conversation-registry.js";
7
+ import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
8
+ import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
9
+ import { createApp } from "../memory/app-store.js";
9
10
  import type { Message } from "../providers/types.js";
10
11
 
11
12
  // ---------------------------------------------------------------------------
@@ -16,6 +17,46 @@ function userMsg(text: string): Message {
16
17
  return { role: "user", content: [{ type: "text", text }] };
17
18
  }
18
19
 
20
+ // `applyRuntimeInjections` synthesizes this conversation id when no
21
+ // `turnContext` is supplied, so the `workspace-context` injector resolves the
22
+ // live workspace block from the registry under this key.
23
+ const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
24
+
25
+ // Register the fallback conversation in the live registry so the runtime
26
+ // injectors resolve their blocks from it (the orchestrator no longer threads
27
+ // workspace or active-surface content as options).
28
+ function registerFallbackConversation(fields: Record<string, unknown>): void {
29
+ setConversation(FALLBACK_CONVERSATION_ID, {
30
+ conversationId: FALLBACK_CONVERSATION_ID,
31
+ workingDir: "/sandbox",
32
+ // Non-dirty empty workspace by default so the workspace-context injector
33
+ // skips both the filesystem rescan and the DB refresh unless a test
34
+ // explicitly seeds a block via `workspaceTopLevelContext`.
35
+ workspaceTopLevelContext: "",
36
+ workspaceTopLevelDirty: false,
37
+ ...fields,
38
+ } as never);
39
+ }
40
+
41
+ // Seed the live conversation registry with a pre-rendered top-level block. The
42
+ // cache is non-dirty with non-null content, so `resolveWorkspaceTopLevelContext`
43
+ // returns it verbatim without rescanning the filesystem.
44
+ function seedWorkspaceContext(text: string): void {
45
+ registerFallbackConversation({
46
+ workspaceTopLevelContext: text,
47
+ workspaceTopLevelDirty: false,
48
+ });
49
+ }
50
+
51
+ // Build the conversation surface-state map that `buildActiveSurfaceContext`
52
+ // reads to render the `<active_workspace>` block.
53
+ function makeSurfaceState(
54
+ surfaceId: string,
55
+ data: SurfaceData,
56
+ ): Map<string, { surfaceType: SurfaceType; data: SurfaceData }> {
57
+ return new Map([[surfaceId, { surfaceType: "dynamic_page", data }]]);
58
+ }
59
+
19
60
  // ---------------------------------------------------------------------------
20
61
  // Tests
21
62
  // ---------------------------------------------------------------------------
@@ -23,26 +64,21 @@ function userMsg(text: string): Message {
23
64
  const sampleContext =
24
65
  "<workspace>\nRoot: /sandbox\nDirectories: src, lib, tests\n</workspace>";
25
66
 
26
- // The standalone `injectWorkspaceTopLevelContext` helper was removed in
27
- // G2.1. The workspace-context default injector (registered by
28
- // `defaultInjectorsPlugin`) now emits the workspace block as a
29
- // `prepend-user-tail` placement during `applyRuntimeInjections`. The suite
30
- // below exercises that end-to-end path instead.
67
+ // The workspace-context default injector emits the workspace block as a
68
+ // `prepend-user-tail` placement during `applyRuntimeInjections`. It sources
69
+ // the rendered block from the per-conversation workspace registry and
70
+ // (re)injects only when the block is absent from the working messages, so the
71
+ // suite seeds the registry and exercises that end-to-end path.
31
72
 
32
73
  describe("applyRuntimeInjections — workspace top-level context", () => {
33
- beforeEach(() => {
34
- // Post-G2.1: workspace injection is driven by the `workspace-context`
35
- // default injector, so the plugin must be registered for the chain to
36
- // produce a block. Each test gets a clean registry.
37
- resetPluginRegistryForTests();
38
- registerPlugin(defaultInjectorsPlugin);
74
+ afterEach(() => {
75
+ clearConversations();
39
76
  });
40
77
 
41
- test("injects workspace context when provided", async () => {
78
+ test("injects workspace context when registered", async () => {
79
+ seedWorkspaceContext(sampleContext);
42
80
  const messages: Message[] = [userMsg("Hello")];
43
- const { messages: result } = await applyRuntimeInjections(messages, {
44
- workspaceTopLevelContext: sampleContext,
45
- });
81
+ const { messages: result } = await applyRuntimeInjections(messages, {});
46
82
 
47
83
  expect(result).toHaveLength(1);
48
84
  expect(result[0].content).toHaveLength(2);
@@ -50,22 +86,38 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
50
86
  expect((result[0].content[1] as { text: string }).text).toBe("Hello");
51
87
  });
52
88
 
53
- test("does not inject when workspace context is null", async () => {
89
+ test("does not inject when no workspace context is registered", async () => {
54
90
  const messages: Message[] = [userMsg("Hello")];
55
- const { messages: result } = await applyRuntimeInjections(messages, {
56
- workspaceTopLevelContext: null,
57
- });
91
+ const { messages: result } = await applyRuntimeInjections(messages, {});
58
92
 
59
93
  expect(result).toHaveLength(1);
60
94
  expect(result[0].content).toHaveLength(1);
61
95
  });
62
96
 
97
+ test("does not re-inject when the workspace block is already present", async () => {
98
+ // GIVEN the registry holds a workspace block AND the working messages
99
+ // already carry that block (a normal cached turn, post-injection).
100
+ seedWorkspaceContext(sampleContext);
101
+ const messages: Message[] = [userMsg(sampleContext), userMsg("Hello")];
102
+
103
+ // WHEN injections are applied
104
+ const { messages: result } = await applyRuntimeInjections(messages, {});
105
+
106
+ // THEN presence detection skips the block to keep the prefix stable.
107
+ expect(result).toHaveLength(2);
108
+ expect(result[1].content).toHaveLength(1);
109
+ expect((result[1].content[0] as { text: string }).text).toBe("Hello");
110
+ });
111
+
63
112
  test("workspace context appears before active surface context in content", async () => {
64
- const messages: Message[] = [userMsg("Hello")];
65
- const { messages: result } = await applyRuntimeInjections(messages, {
66
- activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
113
+ registerFallbackConversation({
67
114
  workspaceTopLevelContext: sampleContext,
115
+ workspaceTopLevelDirty: false,
116
+ currentActiveSurfaceId: "sf_1",
117
+ surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
68
118
  });
119
+ const messages: Message[] = [userMsg("Hello")];
120
+ const { messages: result } = await applyRuntimeInjections(messages, {});
69
121
 
70
122
  // Workspace is injected last (in applyRuntimeInjections order) so it
71
123
  // prepends to whatever was already prepended by activeSurface.
@@ -79,17 +131,20 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
79
131
  });
80
132
 
81
133
  test("app-backed active surface tells the model to load app-builder with the right argument", async () => {
82
- const messages: Message[] = [userMsg("Edit this app")];
83
- const { messages: result } = await applyRuntimeInjections(messages, {
84
- activeSurface: {
85
- surfaceId: "sf_1",
134
+ const app = createApp({
135
+ name: "Example App",
136
+ schemaJson: "{}",
137
+ htmlDefinition: "<div>test</div>",
138
+ });
139
+ registerFallbackConversation({
140
+ currentActiveSurfaceId: "sf_1",
141
+ surfaceState: makeSurfaceState("sf_1", {
86
142
  html: "<div>test</div>",
87
- appId: "app-1",
88
- appName: "Example App",
89
- appFiles: [],
90
- },
91
- workspaceTopLevelContext: null,
143
+ appId: app.id,
144
+ }),
92
145
  });
146
+ const messages: Message[] = [userMsg("Edit this app")];
147
+ const { messages: result } = await applyRuntimeInjections(messages, {});
93
148
 
94
149
  const activeWorkspaceText = (result[0].content[0] as { text: string }).text;
95
150
  expect(activeWorkspaceText).toContain('skill: "app-builder"');
@@ -98,15 +153,14 @@ describe("applyRuntimeInjections — workspace top-level context", () => {
98
153
  });
99
154
 
100
155
  describe("applyRuntimeInjections — minimal mode skips workspace blocks", () => {
101
- beforeEach(() => {
102
- resetPluginRegistryForTests();
103
- registerPlugin(defaultInjectorsPlugin);
156
+ afterEach(() => {
157
+ clearConversations();
104
158
  });
105
159
 
106
160
  test("minimal mode skips workspace top-level context", async () => {
161
+ seedWorkspaceContext(sampleContext);
107
162
  const messages: Message[] = [userMsg("Hello")];
108
163
  const { messages: result } = await applyRuntimeInjections(messages, {
109
- workspaceTopLevelContext: sampleContext,
110
164
  mode: "minimal",
111
165
  });
112
166
 
@@ -116,9 +170,12 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
116
170
  });
117
171
 
118
172
  test("minimal mode skips active surface context", async () => {
173
+ registerFallbackConversation({
174
+ currentActiveSurfaceId: "sf_1",
175
+ surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
176
+ });
119
177
  const messages: Message[] = [userMsg("Hello")];
120
178
  const { messages: result } = await applyRuntimeInjections(messages, {
121
- activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
122
179
  mode: "minimal",
123
180
  });
124
181
 
@@ -128,11 +185,14 @@ describe("applyRuntimeInjections — minimal mode skips workspace blocks", () =>
128
185
  });
129
186
 
130
187
  test("full mode (default) still includes workspace blocks", async () => {
131
- const messages: Message[] = [userMsg("Hello")];
132
- const { messages: result } = await applyRuntimeInjections(messages, {
188
+ registerFallbackConversation({
133
189
  workspaceTopLevelContext: sampleContext,
134
- activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
190
+ workspaceTopLevelDirty: false,
191
+ currentActiveSurfaceId: "sf_1",
192
+ surfaceState: makeSurfaceState("sf_1", { html: "<div>test</div>" }),
135
193
  });
194
+ const messages: Message[] = [userMsg("Hello")];
195
+ const { messages: result } = await applyRuntimeInjections(messages, {});
136
196
 
137
197
  expect(result[0].content).toHaveLength(3);
138
198
  expect((result[0].content[0] as { text: string }).text).toBe(sampleContext);
@@ -140,53 +140,19 @@ describe("resolveSlash command contract", () => {
140
140
  });
141
141
  });
142
142
 
143
- describe("resolveSlash /compact target override", () => {
144
- test("plain /compact returns no override", async () => {
143
+ describe("resolveSlash /compact", () => {
144
+ test("plain /compact resolves to kind=compact", async () => {
145
145
  const result = await resolveSlash("/compact");
146
146
  expect(result).toEqual({ kind: "compact" });
147
147
  });
148
148
 
149
- test("/compact <integer> sets explicit token target", async () => {
149
+ test("/compact rejects arguments with usage hint", async () => {
150
150
  const result = await resolveSlash("/compact 30000");
151
- expect(result).toEqual({
152
- kind: "compact",
153
- targetInputTokensOverride: 30000,
154
- });
155
- });
156
-
157
- test("/compact <n>k expands to thousands", async () => {
158
- const result = await resolveSlash("/compact 30k");
159
- expect(result).toEqual({
160
- kind: "compact",
161
- targetInputTokensOverride: 30_000,
162
- });
163
- });
164
-
165
- test("/compact <n>m expands to millions", async () => {
166
- const result = await resolveSlash("/compact 1.5M");
167
- expect(result).toEqual({
168
- kind: "compact",
169
- targetInputTokensOverride: 1_500_000,
170
- });
171
- });
172
-
173
- test("/compact rejects malformed args with usage hint", async () => {
174
- const result = await resolveSlash("/compact bogus");
175
151
  expect(result.kind).toBe("unknown");
176
152
  if (result.kind !== "unknown") throw new Error("expected unknown");
177
- expect(result.message).toContain("`bogus`");
153
+ expect(result.message).toContain("does not take arguments");
178
154
  expect(result.message).toContain("/compact");
179
155
  });
180
-
181
- test("/compact rejects zero", async () => {
182
- const result = await resolveSlash("/compact 0");
183
- expect(result.kind).toBe("unknown");
184
- });
185
-
186
- test("/compact rejects negative numbers", async () => {
187
- const result = await resolveSlash("/compact -50");
188
- expect(result.kind).toBe("unknown");
189
- });
190
156
  });
191
157
 
192
158
  describe("resolveSlash /clean", () => {
@@ -227,9 +193,9 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
227
193
  { input: "/status", kind: "unknown" },
228
194
  { input: "/commands", kind: "unknown" },
229
195
  { input: "/compact", kind: "compact" },
230
- { input: "/compact 30000", kind: "compact" },
231
- { input: "/compact 30k", kind: "compact" },
232
- { input: "/compact 1.5M", kind: "compact" },
196
+ { input: "/compact 30000", kind: "unknown" },
197
+ { input: "/compact 30k", kind: "unknown" },
198
+ { input: "/compact 1.5M", kind: "unknown" },
233
199
  { input: "/compact bogus", kind: "unknown" },
234
200
  { input: "/clean", kind: "clean" },
235
201
  { input: " /clean ", kind: "clean" },
@@ -239,7 +205,7 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
239
205
  { input: "/opus", kind: "unknown" },
240
206
  { input: "hello", kind: "passthrough" },
241
207
  { input: " /compact ", kind: "compact" },
242
- { input: " /compact 50k ", kind: "compact" },
208
+ { input: " /compact 50k ", kind: "unknown" },
243
209
  { input: "/models foo", kind: "passthrough" },
244
210
  ];
245
211
 
@@ -228,7 +228,12 @@ mock.module("../agent/loop.js", () => ({
228
228
  const history = await new Promise<Message[]>((resolve) => {
229
229
  pendingRuns.push({ resolve, messages, onEvent });
230
230
  });
231
- return { history, exitReason: null };
231
+ return {
232
+ history,
233
+ exitReason: null,
234
+ appendedNewMessages: history.length > messages.length,
235
+ newMessages: history.slice(messages.length),
236
+ };
232
237
  }
233
238
  },
234
239
  }));
@@ -339,8 +339,8 @@ describe("GET /v1/conversation-starters", () => {
339
339
 
340
340
  test("does not re-enqueue for invalid items when within cooldown period", async () => {
341
341
  const now = Date.now();
342
- // Generation happened 30 seconds ago (within the 60s cooldown)
343
- const recentGenAt = now - 30_000;
342
+ // Last attempt 30 seconds ago (within the 5-minute cooldown)
343
+ const recentAttemptAt = now - 30_000;
344
344
  insertStarter({
345
345
  label: "Let me check calendar",
346
346
  prompt: "Let me check what Alice has today.",
@@ -353,9 +353,13 @@ describe("GET /v1/conversation-starters", () => {
353
353
  category: "productivity",
354
354
  createdAt: now - 1,
355
355
  });
356
+ setCheckpoint(
357
+ "conversation_starters:last_attempt_at:default",
358
+ String(recentAttemptAt),
359
+ );
356
360
  setCheckpoint(
357
361
  "conversation_starters:last_gen_at:default",
358
- String(recentGenAt),
362
+ String(recentAttemptAt),
359
363
  );
360
364
  setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
361
365
  insertMemoryItem();
@@ -375,8 +379,8 @@ describe("GET /v1/conversation-starters", () => {
375
379
 
376
380
  test("re-enqueues for invalid items after cooldown expires", async () => {
377
381
  const now = Date.now();
378
- // Generation happened 90 seconds ago (past the 60s cooldown)
379
- const oldGenAt = now - 90_000;
382
+ // Last attempt 6 minutes ago (past the 5-minute cooldown)
383
+ const oldAttemptAt = now - 6 * 60_000;
380
384
  insertStarter({
381
385
  label: "Let me check calendar",
382
386
  prompt: "Let me check what Alice has today.",
@@ -389,9 +393,13 @@ describe("GET /v1/conversation-starters", () => {
389
393
  category: "productivity",
390
394
  createdAt: now - 1,
391
395
  });
396
+ setCheckpoint(
397
+ "conversation_starters:last_attempt_at:default",
398
+ String(oldAttemptAt),
399
+ );
392
400
  setCheckpoint(
393
401
  "conversation_starters:last_gen_at:default",
394
- String(oldGenAt),
402
+ String(oldAttemptAt),
395
403
  );
396
404
  setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
397
405
  insertMemoryItem();
@@ -366,6 +366,90 @@ describe("surface action delivery to assistant", () => {
366
366
  expect(JSON.stringify(completeMsg)).not.toContain(largeBase64);
367
367
  });
368
368
 
369
+ test("choice surface broadcasts ui_surface_complete on action", async () => {
370
+ const sent: ServerMessage[] = [];
371
+ const ctx = makeContext(sent);
372
+
373
+ const showResult = await surfaceProxyResolver(ctx, "ui_show", {
374
+ surface_type: "choice",
375
+ title: "Pick an outcome",
376
+ data: {
377
+ options: [
378
+ { id: "inbox", title: "Clean up my inbox" },
379
+ { id: "calendar", title: "Plan my week" },
380
+ ],
381
+ },
382
+ });
383
+
384
+ expect(showResult.isError).toBe(false);
385
+ expect(showResult.yieldToUser).toBe(true);
386
+
387
+ const showMessage = sent.find(
388
+ (msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
389
+ ) as UiSurfaceShow;
390
+ const surfaceId = showMessage.surfaceId;
391
+ expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(true);
392
+
393
+ await handleSurfaceAction(ctx, surfaceId, "inbox", {
394
+ choiceId: "inbox",
395
+ choiceTitle: "Clean up my inbox",
396
+ selectedIds: ["inbox"],
397
+ selectedTitles: ["Clean up my inbox"],
398
+ });
399
+
400
+ const completeMsg = broadcastedMessages.find(
401
+ (m) =>
402
+ (m as unknown as Record<string, unknown>).type ===
403
+ "ui_surface_complete" &&
404
+ (m as unknown as Record<string, unknown>).surfaceId === surfaceId,
405
+ ) as unknown as Record<string, unknown> | undefined;
406
+ expect(completeMsg).toBeDefined();
407
+ expect(completeMsg?.conversationId).toBe("conv-1");
408
+ expect(completeMsg?.summary).toBe('User chose: "Clean up my inbox"');
409
+ expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(false);
410
+ });
411
+
412
+ test("oauth_connect surface broadcasts ui_surface_complete on action", async () => {
413
+ const sent: ServerMessage[] = [];
414
+ const ctx = makeContext(sent);
415
+
416
+ const showResult = await surfaceProxyResolver(ctx, "ui_show", {
417
+ surface_type: "oauth_connect",
418
+ title: "Connect Google",
419
+ data: {
420
+ providerKey: "google",
421
+ displayName: "Google",
422
+ },
423
+ });
424
+
425
+ expect(showResult.isError).toBe(false);
426
+ expect(showResult.yieldToUser).toBe(true);
427
+
428
+ const showMessage = sent.find(
429
+ (msg): msg is UiSurfaceShow => msg.type === "ui_surface_show",
430
+ ) as UiSurfaceShow;
431
+ const surfaceId = showMessage.surfaceId;
432
+ expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(true);
433
+
434
+ await handleSurfaceAction(ctx, surfaceId, "connect", {
435
+ status: "connected",
436
+ providerKey: "google",
437
+ providerLabel: "Google",
438
+ accountLabel: "user@example.com",
439
+ });
440
+
441
+ const completeMsg = broadcastedMessages.find(
442
+ (m) =>
443
+ (m as unknown as Record<string, unknown>).type ===
444
+ "ui_surface_complete" &&
445
+ (m as unknown as Record<string, unknown>).surfaceId === surfaceId,
446
+ ) as unknown as Record<string, unknown> | undefined;
447
+ expect(completeMsg).toBeDefined();
448
+ expect(completeMsg?.conversationId).toBe("conv-1");
449
+ expect(completeMsg?.summary).toBe("Connected Google: user@example.com");
450
+ expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(false);
451
+ });
452
+
369
453
  test("table surface does NOT broadcast ui_surface_complete (not one-shot)", async () => {
370
454
  const sent: ServerMessage[] = [];
371
455
  const ctx = makeContext(sent);
@@ -1,5 +1,3 @@
1
- import { readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
1
  import { afterAll, beforeEach, describe, expect, test } from "bun:test";
4
2
 
5
3
  import {
@@ -18,6 +16,7 @@ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
18
16
  import { ROUTES as CONVERSATION_LIST_ROUTES } from "../runtime/routes/conversation-list-routes.js";
19
17
  import { ROUTES as CONVERSATION_MANAGEMENT_ROUTES } from "../runtime/routes/conversation-management-routes.js";
20
18
  import type { RouteDefinition } from "../runtime/routes/types.js";
19
+ import { publishConversationTitleChanged } from "../runtime/sync/resource-sync-events.js";
21
20
  import { resetDbForTesting } from "./db-test-helpers.js";
22
21
  import { waitFor } from "./helpers/wait-for.js";
23
22
 
@@ -122,20 +121,33 @@ describe("conversation sync tags", () => {
122
121
  ).toBe(false);
123
122
  });
124
123
 
125
- test("agent-loop title updates emit metadata-only sync tags (no list umbrella)", () => {
126
- const source = readFileSync(
127
- join(import.meta.dir, "..", "daemon", "conversation-agent-loop.ts"),
128
- "utf-8",
124
+ test("auto-title generation emits the typed title event and a metadata-only sync tag (no list umbrella)", async () => {
125
+ // Auto-title generation (first-pass on prompt submit, second-pass
126
+ // regeneration, bootstrap, and voice) persists via the title service and
127
+ // broadcasts through `publishConversationTitleChanged` — the same helper
128
+ // the rename route uses. Like a rename, generation is content-only: the
129
+ // row stays in place and only the title flips, so web patches the cached
130
+ // row from the typed `conversation_title_updated` event and the per-
131
+ // conversation metadata tag is the belt-and-suspenders signal. The list
132
+ // umbrella is deliberately omitted so web never redrains the paginated
133
+ // list for a title change.
134
+ const conversation = createConversation("Generating…");
135
+
136
+ const received = await captureEvents(() => {
137
+ publishConversationTitleChanged(conversation.id, "Generated title");
138
+ }, 2);
139
+
140
+ expect(received.map((event) => event.message.type)).toEqual([
141
+ "conversation_title_updated",
142
+ "sync_changed",
143
+ ]);
144
+ expect(received[1]!.message).toEqual({
145
+ type: "sync_changed",
146
+ tags: [conversationMetadataSyncTag(conversation.id)],
147
+ });
148
+ expect((received[1]!.message as { tags: string[] }).tags).not.toContain(
149
+ SYNC_TAGS.conversationsList,
129
150
  );
130
- const titleUpdateBlocks =
131
- source.match(
132
- /type: "conversation_title_updated"[\s\S]{0,500}?type: "sync_changed"[\s\S]{0,250}?tags: \[[\s\S]*?\]/g,
133
- ) ?? [];
134
-
135
- expect(titleUpdateBlocks.length).toBeGreaterThanOrEqual(2);
136
- for (const block of titleUpdateBlocks) {
137
- expect(block).not.toContain("SYNC_TAGS.conversationsList");
138
- }
139
151
  });
140
152
 
141
153
  test("create emits a sync_changed with the conversationsList umbrella tag", async () => {