@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,6 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
+ import { isPlaceholderSentinelText } from "../../placeholder-sentinels.js";
3
4
  import {
5
+ EMPTY_ASSISTANT_TURN_PLACEHOLDER,
4
6
  OpenAIChatCompletionsProvider,
5
7
  type OpenAIChatCompletionsProviderOptions,
6
8
  } from "../chat-completions-provider.js";
@@ -348,6 +350,116 @@ describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
348
350
  expect(assistantMsg.reasoning_content).toBeUndefined();
349
351
  });
350
352
 
353
+ test("backfills placeholder content for a reasoning-only assistant turn when enabled", async () => {
354
+ const { provider, requests } = stubProvider(
355
+ [
356
+ {
357
+ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
358
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
359
+ },
360
+ ],
361
+ {
362
+ assistantReasoningField: "reasoning",
363
+ backfillEmptyAssistantContent: true,
364
+ },
365
+ );
366
+
367
+ await provider.sendMessage([
368
+ { role: "user", content: [{ type: "text", text: "question" }] },
369
+ {
370
+ role: "assistant",
371
+ content: [
372
+ {
373
+ type: "thinking",
374
+ thinking: "truncated chain of thought",
375
+ signature: "",
376
+ },
377
+ ],
378
+ },
379
+ ]);
380
+
381
+ const params = requests[0] as {
382
+ messages: Array<{
383
+ role: string;
384
+ content: string | null;
385
+ reasoning?: string;
386
+ tool_calls?: unknown;
387
+ }>;
388
+ };
389
+ const assistantMsg = params.messages.find((m) => m.role === "assistant")!;
390
+ // content or tool_calls must be set; reasoning alone does not satisfy it.
391
+ expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
392
+ expect(assistantMsg.tool_calls).toBeUndefined();
393
+ expect(assistantMsg.reasoning).toBe("truncated chain of thought");
394
+ // The placeholder is a recognized sentinel, so it is stripped from
395
+ // persisted/rendered history if a model echoes it back, and it carries no
396
+ // control characters that a strict OpenAI-compatible backend might reject.
397
+ expect(isPlaceholderSentinelText(EMPTY_ASSISTANT_TURN_PLACEHOLDER)).toBe(
398
+ true,
399
+ );
400
+ expect(EMPTY_ASSISTANT_TURN_PLACEHOLDER).not.toContain("\x00");
401
+ });
402
+
403
+ test("leaves reasoning-only assistant content null when backfill is disabled", async () => {
404
+ const { provider, requests } = stubProvider(
405
+ [
406
+ {
407
+ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
408
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
409
+ },
410
+ ],
411
+ { assistantReasoningField: "reasoning_content" },
412
+ );
413
+
414
+ await provider.sendMessage([
415
+ { role: "user", content: [{ type: "text", text: "question" }] },
416
+ {
417
+ role: "assistant",
418
+ content: [
419
+ {
420
+ type: "thinking",
421
+ thinking: "truncated chain of thought",
422
+ signature: "",
423
+ },
424
+ ],
425
+ },
426
+ ]);
427
+
428
+ const params = requests[0] as {
429
+ messages: Array<{ role: string; content: string | null }>;
430
+ };
431
+ const assistantMsg = params.messages.find((m) => m.role === "assistant")!;
432
+ // Backfill defaults off, so providers that tolerate null assistant content
433
+ // (e.g. OpenAI proper) are unaffected by the OpenRouter-specific guard.
434
+ expect(assistantMsg.content).toBeNull();
435
+ });
436
+
437
+ test("does not backfill content when tool calls are present", async () => {
438
+ const { provider, requests } = stubProvider([
439
+ {
440
+ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
441
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
442
+ },
443
+ ]);
444
+
445
+ await provider.sendMessage([
446
+ {
447
+ role: "assistant",
448
+ content: [
449
+ { type: "tool_use", id: "call_1", name: "search", input: { q: "x" } },
450
+ ],
451
+ },
452
+ ]);
453
+
454
+ const params = requests[0] as {
455
+ messages: Array<{ role: string; content: string | null }>;
456
+ };
457
+ // Tool-call-only assistant messages keep null content (preferred by
458
+ // Anthropic-proxy/Bedrock backends); the placeholder is only for the
459
+ // neither-content-nor-tool_calls case.
460
+ expect(params.messages[0].content).toBeNull();
461
+ });
462
+
351
463
  test("skips Anthropic-originated thinking blocks (with signatures)", async () => {
352
464
  const { provider, requests } = stubProvider(
353
465
  [
@@ -4,6 +4,7 @@ import { isAbortReason } from "../../util/abort-reasons.js";
4
4
  import { ProviderError } from "../../util/errors.js";
5
5
  import { extractRetryAfterMs } from "../../util/retry.js";
6
6
  import { escapeXmlAttr } from "../../util/xml.js";
7
+ import { PLACEHOLDER_EMPTY_TURN } from "../placeholder-sentinels.js";
7
8
  import { createStreamTimeout } from "../stream-timeout.js";
8
9
  import type {
9
10
  ContentBlock,
@@ -100,6 +101,26 @@ export function extractApiErrorDetail(
100
101
  * OpenRouter's `error.metadata.raw` strings, which are typically <1KB. */
101
102
  const MAX_API_ERROR_DETAIL_CHARS = 2000;
102
103
 
104
+ /**
105
+ * Fallback `content` for an assistant turn that has neither visible text nor
106
+ * tool calls (e.g. a reasoning-only turn truncated at the output-token limit).
107
+ *
108
+ * The OpenAI chat-completions schema requires an assistant message to carry
109
+ * `content` or `tool_calls`. OpenAI itself tolerates `content: null`/`""` here,
110
+ * but strict OpenAI-compatible backends do not: DeepSeek via OpenRouter rejects
111
+ * the request with `Invalid assistant message: content or tool_calls must be
112
+ * set`, and vLLM-style validators coerce empty-string content back to null and
113
+ * reject it the same way. The placeholder must therefore be a non-empty string.
114
+ *
115
+ * We reuse the shared empty-turn sentinel so that
116
+ * `isPlaceholderSentinelText`/`cleanAssistantContent` strip it from persisted
117
+ * and rendered history if a model ever echoes it back. The null-byte prefix is
118
+ * dropped because some OpenAI-compatible backends reject control characters in
119
+ * message content; the bare form is still recognized by
120
+ * `isPlaceholderSentinelText`.
121
+ */
122
+ export const EMPTY_ASSISTANT_TURN_PLACEHOLDER = PLACEHOLDER_EMPTY_TURN.slice(1);
123
+
103
124
  /**
104
125
  * Read the first matching header from an SDK error's headers object,
105
126
  * tolerating both Map-like (`Headers.get()`) and plain-object shapes.
@@ -153,6 +174,13 @@ export interface OpenAIChatCompletionsProviderOptions {
153
174
  * DeepSeek/Fireworks use `"reasoning_content"`; OpenRouter uses `"reasoning"`.
154
175
  * When unset, thinking blocks are dropped from outbound assistant messages. */
155
176
  assistantReasoningField?: "reasoning" | "reasoning_content";
177
+ /** Backfill a non-empty placeholder for assistant turns that would otherwise
178
+ * serialize with neither `content` nor `tool_calls` (e.g. reasoning-only
179
+ * turns). Off by default; enabled for OpenRouter, whose downstream providers
180
+ * (e.g. DeepSeek) reject such messages with `Invalid assistant message:
181
+ * content or tool_calls must be set`. See {@link
182
+ * EMPTY_ASSISTANT_TURN_PLACEHOLDER}. */
183
+ backfillEmptyAssistantContent?: boolean;
156
184
  }
157
185
 
158
186
  /** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
@@ -228,6 +256,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
228
256
  | "reasoning"
229
257
  | "reasoning_content"
230
258
  | undefined;
259
+ private backfillEmptyAssistantContent: boolean;
231
260
 
232
261
  constructor(
233
262
  apiKey: string,
@@ -251,6 +280,8 @@ export class OpenAIChatCompletionsProvider implements Provider {
251
280
  this.requestHeaders = options.requestHeaders ?? {};
252
281
  this.parseThinkTags = options.parseThinkTags ?? false;
253
282
  this.assistantReasoningField = options.assistantReasoningField;
283
+ this.backfillEmptyAssistantContent =
284
+ options.backfillEmptyAssistantContent ?? false;
254
285
  }
255
286
 
256
287
  async sendMessage(
@@ -794,6 +825,19 @@ export class OpenAIChatCompletionsProvider implements Provider {
794
825
  result.tool_calls = toolCalls;
795
826
  }
796
827
 
828
+ // An assistant message must carry `content` or `tool_calls`. A turn with
829
+ // neither (e.g. reasoning-only) would serialize to null/empty content with
830
+ // no tool calls, which strict OpenAI-compatible backends reject. Reasoning
831
+ // lives in a separate field and does not satisfy this constraint. Scoped to
832
+ // providers that need it (OpenRouter) via `backfillEmptyAssistantContent`.
833
+ if (
834
+ this.backfillEmptyAssistantContent &&
835
+ !result.tool_calls &&
836
+ (result.content === null || result.content === "")
837
+ ) {
838
+ result.content = EMPTY_ASSISTANT_TURN_PLACEHOLDER;
839
+ }
840
+
797
841
  return result;
798
842
  }
799
843
 
@@ -122,6 +122,7 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
122
122
  streamTimeoutMs: options.streamTimeoutMs,
123
123
  requestHeaders: OPENROUTER_APP_ATTRIBUTION_HEADERS,
124
124
  assistantReasoningField: "reasoning",
125
+ backfillEmptyAssistantContent: true,
125
126
  });
126
127
  this.openRouterApiKey = apiKey;
127
128
  this.defaultModel = model;
@@ -0,0 +1,35 @@
1
+ // Internal placeholder sentinels injected as assistant-message content when a
2
+ // turn would otherwise serialize with neither text nor tool calls. Provider
3
+ // request bodies must keep a non-empty content slot (Anthropic to preserve
4
+ // role alternation; strict OpenAI-compatible backends to satisfy the
5
+ // "content or tool_calls must be set" constraint), but these markers must
6
+ // never be persisted or rendered to users.
7
+ //
8
+ // The null-byte prefix makes the prefixed form impossible to produce via
9
+ // normal model output or user input, preventing false positives. Some
10
+ // OpenAI-compatible backends reject control characters in message content, so
11
+ // the OpenAI path emits the bare (prefix-stripped) form, which
12
+ // `isPlaceholderSentinelText` still recognizes.
13
+ export const PLACEHOLDER_EMPTY_TURN =
14
+ "\x00__PLACEHOLDER__[empty assistant turn]";
15
+ export const PLACEHOLDER_BLOCKS_OMITTED =
16
+ "\x00__PLACEHOLDER__[internal blocks omitted]";
17
+
18
+ // Compared against the payload with any leading `\x00` stripped, so the check
19
+ // matches both the prefixed sentinel we emit and any bare variant that lost
20
+ // the null byte in transit (e.g. the model echoing the text back without
21
+ // reproducing the control character).
22
+ const PLACEHOLDER_SENTINEL_BARE: ReadonlySet<string> = new Set([
23
+ PLACEHOLDER_EMPTY_TURN.slice(1),
24
+ PLACEHOLDER_BLOCKS_OMITTED.slice(1),
25
+ ]);
26
+
27
+ /**
28
+ * True when the text is one of the internal alternation-preserving sentinels,
29
+ * with or without the null-byte prefix. These must never be persisted or
30
+ * rendered to users — they exist only in outbound provider request bodies.
31
+ */
32
+ export function isPlaceholderSentinelText(text: string): boolean {
33
+ const normalized = text.startsWith("\x00") ? text.slice(1) : text;
34
+ return PLACEHOLDER_SENTINEL_BARE.has(normalized);
35
+ }
@@ -136,6 +136,10 @@ import {
136
136
  const runResult = (history: Message[]): AgentLoopRunResult => ({
137
137
  history,
138
138
  exitReason: null,
139
+ appendedNewMessages: true,
140
+ // The wake path slices its own new-message boundary off the returned
141
+ // history (it never destructures `newMessages`), so this is type-only.
142
+ newMessages: [],
139
143
  });
140
144
 
141
145
  interface MockTarget extends WakeTarget {
@@ -428,7 +432,7 @@ describe("wakeAgentForOpportunity", () => {
428
432
  expect(target.runCalls).toHaveLength(0);
429
433
  });
430
434
 
431
- test("threads cleanup-mode injection context for explicit local-owner wakes", async () => {
435
+ test("builds a guardian turn context for explicit local-owner cleanup-mode wakes", async () => {
432
436
  mockDiskPressureStatus = {
433
437
  enabled: true,
434
438
  state: "critical",
@@ -459,11 +463,11 @@ describe("wakeAgentForOpportunity", () => {
459
463
 
460
464
  expect(result).toEqual({ invoked: true, producedToolCalls: false });
461
465
  expect(target.runCalls).toHaveLength(1);
462
- expect(target.runCalls[0]!.turnContext).toMatchObject({
466
+ expect(target.runCalls[0]!.turnContext).toEqual({
467
+ requestId: "wake:local-cleanup",
463
468
  conversationId: target.conversationId,
464
- injectionInputs: {
465
- diskPressureContext: { cleanupModeActive: true },
466
- },
469
+ turnIndex: 0,
470
+ trust: { sourceChannel: "vellum", trustClass: "guardian" },
467
471
  });
468
472
  });
469
473
 
@@ -1077,7 +1081,7 @@ describe("wakeAgentForOpportunity", () => {
1077
1081
  expect(target.drainQueueCalls).toBe(1);
1078
1082
  // Critical ordering invariant: drain runs after processing=false.
1079
1083
  // If drain ran while processing was still true,
1080
- // `enqueueMessage`'s `if (!ctx.processing) return ...` gate would
1084
+ // `enqueueMessage`'s `if (!ctx.isProcessing()) return ...` gate would
1081
1085
  // see processing=true and the drained item would itself just
1082
1086
  // re-enqueue — no progress. Snapshot the live flag *inside* drain
1083
1087
  // (rather than inferring from toggle order) so a future regression
@@ -50,7 +50,7 @@ let surfaceCalls: Array<{
50
50
  surfaceId: string;
51
51
  }> = [];
52
52
 
53
- mock.module("../../daemon/conversation-store.js", () => ({
53
+ mock.module("../../daemon/conversation-registry.js", () => ({
54
54
  findConversation: (_conversationId: string) => {
55
55
  return mockConversation ?? undefined;
56
56
  },
@@ -140,7 +140,7 @@ export interface WakeTarget {
140
140
  * The wake invokes this in its `finally` block AFTER
141
141
  * `markProcessing(false)`. Order matters: if drain ran while
142
142
  * processing was still true, `enqueueMessage`'s gate
143
- * (`if (!ctx.processing) return ...`) would still see processing=true
143
+ * (`if (!ctx.isProcessing()) return ...`) would still see processing=true
144
144
  * and the drain itself would be a no-op against any racy late sends.
145
145
  * Running drain after processing is released matches the canonical
146
146
  * user-turn finally path in `conversation-agent-loop.ts`.
@@ -432,9 +432,6 @@ function buildWakeTurnContext(
432
432
  sourceChannel: opts.sourceChannel ?? "vellum",
433
433
  trustClass: "guardian",
434
434
  } satisfies TrustContext),
435
- injectionInputs: {
436
- diskPressureContext: { cleanupModeActive: true },
437
- },
438
435
  };
439
436
  }
440
437
 
@@ -963,7 +960,7 @@ export async function wakeAgentForOpportunity(
963
960
 
964
961
  // Run completed cleanly. The canonical user-turn pattern
965
962
  // (conversation-agent-loop.ts:1860, 2106-2126) updates
966
- // `ctx.messages` first, then resets `ctx.processing = false`, then
963
+ // `ctx.messages` first, then clears the flag via `ctx.setProcessing(false)`, then
967
964
  // calls `ctx.drainQueue(...)`. We mirror that order so a message
968
965
  // queued during the wake dequeues against an already-updated
969
966
  // history — otherwise `drainSingleMessage` reads `ctx.messages`
@@ -43,7 +43,7 @@ import { appendEventToStream } from "../signals/event-stream.js";
43
43
  import { getLogger } from "../util/logger.js";
44
44
  import type { AssistantEvent } from "./assistant-event.js";
45
45
  import { buildAssistantEvent } from "./assistant-event.js";
46
- import { stampAndBuffer } from "./conversation-stream-state.js";
46
+ import { stampAndBuffer } from "./assistant-stream-state.js";
47
47
 
48
48
  const log = getLogger("assistant-event-hub");
49
49
 
@@ -64,6 +64,13 @@ export interface AssistantEventSubscription {
64
64
  dispose(): void;
65
65
  /** True until `dispose()` has been called. */
66
66
  readonly active: boolean;
67
+ /**
68
+ * Per-connection identifier, unique within the hub instance. Distinguishes
69
+ * connections that share a `clientId` (e.g. an old connection and the new
70
+ * one that replaced it on reconnect) so subscribe / dispose / shed log
71
+ * lines can be attributed to a specific connection.
72
+ */
73
+ readonly connectionId: string;
67
74
  }
68
75
 
69
76
  // ── Subscriber entries (discriminated union) ─────────────────────────────────
@@ -75,6 +82,13 @@ interface BaseSubscriberEntry {
75
82
  onEvict: () => void;
76
83
  connectedAt: Date;
77
84
  lastActiveAt: Date;
85
+ /**
86
+ * Per-connection identifier, unique within the hub instance. Two entries
87
+ * with the same `clientId` (old vs reconnected connection) get distinct
88
+ * connection ids, making them traceable across subscribe / dispose / shed
89
+ * logs.
90
+ */
91
+ connectionId: string;
78
92
  }
79
93
 
80
94
  interface ClientEntry extends BaseSubscriberEntry {
@@ -106,10 +120,15 @@ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
106
120
  ? Omit<T, K>
107
121
  : never;
108
122
 
109
- /** Input shape for `subscribe()` — hub fills `active`, `connectedAt`, `lastActiveAt` and defaults `filter`/`onEvict`. */
123
+ /** Input shape for `subscribe()` — hub fills `active`, `connectedAt`, `lastActiveAt`, `connectionId` and defaults `filter`/`onEvict`. */
110
124
  type SubscriberInput = DistributiveOmit<
111
125
  SubscriberEntry,
112
- "active" | "connectedAt" | "lastActiveAt" | "filter" | "onEvict"
126
+ | "active"
127
+ | "connectedAt"
128
+ | "lastActiveAt"
129
+ | "filter"
130
+ | "onEvict"
131
+ | "connectionId"
113
132
  > & {
114
133
  filter?: AssistantEventFilter;
115
134
  onEvict?: () => void;
@@ -132,6 +151,8 @@ type SubscriberInput = DistributiveOmit<
132
151
  export class AssistantEventHub {
133
152
  private readonly subscribers = new Set<SubscriberEntry>();
134
153
  private readonly maxSubscribers: number;
154
+ /** Monotonic source for per-connection ids, scoped to this hub. */
155
+ private connectionCounter = 0;
135
156
 
136
157
  constructor(options?: { maxSubscribers?: number }) {
137
158
  this.maxSubscribers = options?.maxSubscribers ?? Infinity;
@@ -173,7 +194,11 @@ export class AssistantEventHub {
173
194
  }
174
195
  if (stale.length > 0) {
175
196
  log.info(
176
- { clientId: subscriber.clientId, count: stale.length },
197
+ {
198
+ clientId: subscriber.clientId,
199
+ count: stale.length,
200
+ disposedConnectionIds: stale.map((entry) => entry.connectionId),
201
+ },
177
202
  "disposed stale subscribers for reconnecting client",
178
203
  );
179
204
  }
@@ -196,6 +221,7 @@ export class AssistantEventHub {
196
221
  }
197
222
 
198
223
  const now = new Date();
224
+ const connectionId = `conn-${++this.connectionCounter}`;
199
225
  const entry: SubscriberEntry = {
200
226
  ...subscriber,
201
227
  filter: subscriber.filter ?? {},
@@ -203,6 +229,7 @@ export class AssistantEventHub {
203
229
  active: true,
204
230
  connectedAt: now,
205
231
  lastActiveAt: now,
232
+ connectionId,
206
233
  } as SubscriberEntry;
207
234
 
208
235
  if (entry.type === "client") {
@@ -211,11 +238,12 @@ export class AssistantEventHub {
211
238
  clientId: entry.clientId,
212
239
  interfaceId: entry.interfaceId,
213
240
  capabilities: entry.capabilities,
241
+ connectionId,
214
242
  },
215
243
  "subscriber registered (client)",
216
244
  );
217
245
  } else {
218
- log.info("subscriber registered (process)");
246
+ log.info({ connectionId }, "subscriber registered (process)");
219
247
  }
220
248
 
221
249
  this.subscribers.add(entry);
@@ -230,17 +258,19 @@ export class AssistantEventHub {
230
258
  {
231
259
  clientId: entry.clientId,
232
260
  interfaceId: entry.interfaceId,
261
+ connectionId,
233
262
  },
234
263
  "subscriber unregistered (client)",
235
264
  );
236
265
  } else {
237
- log.info("subscriber unregistered (process)");
266
+ log.info({ connectionId }, "subscriber unregistered (process)");
238
267
  }
239
268
  }
240
269
  },
241
270
  get active() {
242
271
  return entry.active;
243
272
  },
273
+ connectionId,
244
274
  };
245
275
  }
246
276
 
@@ -678,7 +708,7 @@ async function createCanonicalRequestForConfirmation(
678
708
  { DAEMON_INTERNAL_ASSISTANT_ID },
679
709
  { bridgeConfirmationRequestToGuardian },
680
710
  ] = await Promise.all([
681
- import("../daemon/conversation-store.js"),
711
+ import("../daemon/conversation-registry.js"),
682
712
  import("../memory/canonical-guardian-store.js"),
683
713
  import("../security/secret-scanner.js"),
684
714
  import("../tools/tool-input-summary.js"),