@vellumai/assistant 0.8.7 → 0.8.8

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 (387) hide show
  1. package/Dockerfile +20 -4
  2. package/docker-entrypoint.sh +4 -2
  3. package/docker-init-apt-root.sh +3 -1
  4. package/docker-kata-apt-env.sh +3 -1
  5. package/docker-kata-runtime-family.sh +12 -0
  6. package/docs/architecture/memory.md +1 -1
  7. package/docs/plugins.md +75 -79
  8. package/examples/plugins/echo/README.md +6 -12
  9. package/examples/plugins/echo/register.ts +0 -41
  10. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  11. package/openapi.yaml +3381 -348
  12. package/package.json +1 -1
  13. package/scripts/generate-openapi.ts +68 -41
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
  15. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  16. package/src/__tests__/agent-loop.test.ts +37 -87
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  18. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  19. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  20. package/src/__tests__/anthropic-provider.test.ts +95 -2
  21. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  22. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  23. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  24. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  25. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  26. package/src/__tests__/btw-routes.test.ts +62 -3
  27. package/src/__tests__/build-persisted-content.test.ts +184 -0
  28. package/src/__tests__/catalog-files.test.ts +1 -1
  29. package/src/__tests__/clawhub-files.test.ts +1 -1
  30. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  32. package/src/__tests__/config-watcher.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
  37. package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
  38. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  39. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  40. package/src/__tests__/conversation-pairing.test.ts +4 -31
  41. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
  43. package/src/__tests__/conversation-queue.test.ts +2 -0
  44. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
  48. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  49. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  50. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  51. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  52. package/src/__tests__/conversation-title-service.test.ts +135 -2
  53. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  54. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  55. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  56. package/src/__tests__/dm-persistence.test.ts +5 -1
  57. package/src/__tests__/empty-response-hook.test.ts +304 -0
  58. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  59. package/src/__tests__/gemini-image-service.test.ts +13 -0
  60. package/src/__tests__/helpers/mock-provider.ts +110 -0
  61. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  62. package/src/__tests__/history-repair-hook.test.ts +1 -0
  63. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  64. package/src/__tests__/identity-routes.test.ts +248 -7
  65. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  66. package/src/__tests__/injector-background-turn.test.ts +2 -8
  67. package/src/__tests__/injector-chain.test.ts +106 -270
  68. package/src/__tests__/injector-disk-pressure.test.ts +3 -12
  69. package/src/__tests__/injector-document-comments.test.ts +2 -2
  70. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  71. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  72. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  73. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  74. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  75. package/src/__tests__/llm-usage-store.test.ts +223 -1
  76. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  77. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  78. package/src/__tests__/native-web-search.test.ts +191 -0
  79. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  80. package/src/__tests__/openai-image-service.test.ts +17 -0
  81. package/src/__tests__/openai-provider.test.ts +31 -1
  82. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  83. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  84. package/src/__tests__/pipeline-runner.test.ts +29 -39
  85. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  86. package/src/__tests__/plugin-bootstrap.test.ts +13 -28
  87. package/src/__tests__/plugin-registry.test.ts +0 -27
  88. package/src/__tests__/plugin-types.test.ts +2 -125
  89. package/src/__tests__/process-message-display-content.test.ts +6 -2
  90. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  91. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  92. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  93. package/src/__tests__/schedule-routes.test.ts +603 -2
  94. package/src/__tests__/schedule-store.test.ts +41 -0
  95. package/src/__tests__/schedule-tools.test.ts +35 -0
  96. package/src/__tests__/server-history-render.test.ts +314 -1
  97. package/src/__tests__/skillssh-files.test.ts +1 -1
  98. package/src/__tests__/system-prompt.test.ts +20 -0
  99. package/src/__tests__/task-scheduler.test.ts +162 -1
  100. package/src/__tests__/terminal-tools.test.ts +6 -1
  101. package/src/__tests__/title-generate-hook.test.ts +319 -0
  102. package/src/__tests__/tool-error-hook.test.ts +278 -0
  103. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  104. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  106. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  107. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  108. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  109. package/src/__tests__/usage-routes.test.ts +285 -1
  110. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  111. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  112. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  113. package/src/agent/loop.ts +346 -442
  114. package/src/api/events/assistant-thinking-delta.ts +33 -0
  115. package/src/api/events/tool-output-chunk.ts +45 -0
  116. package/src/api/events/tool-use-preview-start.ts +32 -0
  117. package/src/api/events/trace-event.ts +69 -0
  118. package/src/api/index.ts +48 -13
  119. package/src/api/responses/conversation-message.ts +368 -0
  120. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  121. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  122. package/src/cli/commands/notifications.ts +112 -60
  123. package/src/config/assistant-feature-flags.ts +22 -11
  124. package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
  125. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  126. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  127. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  128. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  129. package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
  130. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  131. package/src/config/feature-flag-cache.ts +3 -3
  132. package/src/config/feature-flag-registry.json +35 -3
  133. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  134. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  135. package/src/config/schemas/llm.ts +1 -0
  136. package/src/config/schemas/memory-v2.ts +8 -0
  137. package/src/config/schemas/memory-v3.ts +8 -0
  138. package/src/config/schemas/platform.ts +8 -0
  139. package/src/config/seed-inference-profiles.ts +2 -2
  140. package/src/config/skills.ts +13 -0
  141. package/src/context/compactor.ts +1 -1
  142. package/src/context/strip-injections.ts +122 -0
  143. package/src/context/token-estimator.ts +23 -0
  144. package/src/context/tool-result-truncation.ts +0 -23
  145. package/src/context/window-manager.ts +3 -6
  146. package/src/credential-execution/executable-discovery.ts +16 -0
  147. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  148. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  149. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  150. package/src/daemon/assistant-attachments.ts +1 -1
  151. package/src/daemon/config-watcher.ts +2 -2
  152. package/src/daemon/context-overflow-reducer.ts +0 -1
  153. package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
  154. package/src/daemon/conversation-agent-loop.ts +281 -760
  155. package/src/daemon/conversation-history.ts +5 -4
  156. package/src/daemon/conversation-lifecycle.ts +3 -4
  157. package/src/daemon/conversation-messaging.ts +7 -6
  158. package/src/daemon/conversation-process.ts +11 -16
  159. package/src/daemon/conversation-runtime-assembly.ts +130 -347
  160. package/src/daemon/conversation-slash.ts +6 -25
  161. package/src/daemon/conversation-surfaces.ts +222 -4
  162. package/src/daemon/conversation-tool-setup.ts +2 -29
  163. package/src/daemon/conversation.ts +32 -14
  164. package/src/daemon/external-plugins-bootstrap.ts +9 -10
  165. package/src/daemon/handlers/config-a2a.ts +51 -36
  166. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  167. package/src/daemon/handlers/config-telegram.ts +16 -2
  168. package/src/daemon/handlers/shared.ts +156 -84
  169. package/src/daemon/handlers/skills.ts +39 -10
  170. package/src/daemon/lifecycle.ts +4 -0
  171. package/src/daemon/message-types/apps.ts +1 -29
  172. package/src/daemon/message-types/messages.ts +9 -57
  173. package/src/daemon/message-types/skills.ts +2 -0
  174. package/src/daemon/message-types/surfaces.ts +136 -3
  175. package/src/daemon/now-scratchpad.ts +21 -0
  176. package/src/daemon/orphan-reaper.test.ts +210 -0
  177. package/src/daemon/orphan-reaper.ts +240 -0
  178. package/src/daemon/persist-unsendable-image.ts +117 -0
  179. package/src/daemon/process-message.ts +1 -3
  180. package/src/daemon/trace-emitter.ts +6 -4
  181. package/src/daemon/trust-context.ts +19 -0
  182. package/src/daemon/wake-target-adapter.ts +3 -1
  183. package/src/home/home-greeting-cache.ts +24 -1
  184. package/src/ipc/gateway-client.test.ts +2 -2
  185. package/src/ipc/gateway-client.ts +3 -3
  186. package/src/media/gemini-image-service.ts +15 -0
  187. package/src/media/openai-image-service.ts +14 -0
  188. package/src/media/types.ts +34 -0
  189. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  190. package/src/memory/auth-fallback-events-store.ts +94 -0
  191. package/src/memory/conversation-title-service.ts +65 -41
  192. package/src/memory/db-init.ts +4 -0
  193. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  194. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  195. package/src/memory/jobs-store.ts +33 -0
  196. package/src/memory/jobs-worker.ts +31 -4
  197. package/src/memory/llm-usage-store.ts +224 -50
  198. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  199. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  200. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  201. package/src/memory/migrations/index.ts +2 -0
  202. package/src/memory/pkb/autoinject.ts +61 -0
  203. package/src/memory/pkb/context.ts +50 -0
  204. package/src/memory/pkb/types.ts +14 -0
  205. package/src/memory/schedule-attribution-sql.ts +104 -0
  206. package/src/memory/schema/infrastructure.ts +16 -0
  207. package/src/memory/usage-grouped-buckets.ts +6 -1
  208. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
  209. package/src/memory/v2/consolidation-job.ts +1 -1
  210. package/src/memory/v3/__tests__/health.test.ts +16 -0
  211. package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
  212. package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
  213. package/src/memory/v3/__tests__/router.test.ts +101 -29
  214. package/src/memory/v3/__tests__/selector.test.ts +93 -27
  215. package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
  216. package/src/memory/v3/health.ts +0 -0
  217. package/src/memory/v3/llm-retry.ts +32 -0
  218. package/src/memory/v3/orchestrate.ts +26 -14
  219. package/src/memory/v3/provider-blocks.ts +15 -5
  220. package/src/memory/v3/router.ts +48 -42
  221. package/src/memory/v3/selector.ts +57 -42
  222. package/src/memory/v3/shadow-plugin.ts +47 -15
  223. package/src/memory/v3/types.ts +8 -0
  224. package/src/notifications/conversation-pairing.ts +8 -15
  225. package/src/notifications/decision-engine.ts +6 -3
  226. package/src/notifications/home-feed-side-effect.ts +12 -1
  227. package/src/permissions/prompter.ts +4 -0
  228. package/src/plugin-api/constants.ts +4 -0
  229. package/src/plugin-api/index.ts +8 -1
  230. package/src/plugin-api/types.ts +151 -1
  231. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  232. package/src/plugins/defaults/empty-response/register.ts +8 -13
  233. package/src/plugins/defaults/index.ts +1 -15
  234. package/src/plugins/defaults/injectors/register.ts +243 -74
  235. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
  236. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  237. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  238. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  239. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  240. package/src/plugins/defaults/title-generate/package.json +1 -1
  241. package/src/plugins/defaults/title-generate/register.ts +18 -18
  242. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  243. package/src/plugins/defaults/tool-error/package.json +1 -1
  244. package/src/plugins/defaults/tool-error/register.ts +9 -21
  245. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  246. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  247. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  248. package/src/plugins/pipeline.ts +6 -18
  249. package/src/plugins/registry.ts +8 -25
  250. package/src/plugins/types.ts +43 -474
  251. package/src/proactive-artifact/aux-message-injector.ts +3 -3
  252. package/src/proactive-artifact/job.test.ts +7 -12
  253. package/src/prompts/__tests__/system-prompt.test.ts +36 -0
  254. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
  255. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  256. package/src/prompts/templates/system-sections.ts +15 -0
  257. package/src/providers/anthropic/client.ts +37 -29
  258. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  259. package/src/providers/openai/chat-completions-provider.ts +44 -0
  260. package/src/providers/openrouter/client.ts +1 -0
  261. package/src/providers/placeholder-sentinels.ts +35 -0
  262. package/src/runtime/__tests__/agent-wake.test.ts +5 -1
  263. package/src/runtime/agent-wake.ts +2 -2
  264. package/src/runtime/assistant-event-hub.ts +36 -6
  265. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  266. package/src/runtime/http-router.ts +16 -21
  267. package/src/runtime/http-types.ts +16 -70
  268. package/src/runtime/pending-interactions.ts +1 -0
  269. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  270. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  271. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  272. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  273. package/src/runtime/routes/app-management-routes.ts +6 -117
  274. package/src/runtime/routes/app-routes.ts +13 -15
  275. package/src/runtime/routes/attachment-routes.ts +26 -15
  276. package/src/runtime/routes/avatar-routes.ts +26 -0
  277. package/src/runtime/routes/btw-routes.ts +29 -23
  278. package/src/runtime/routes/consolidation-routes.ts +120 -20
  279. package/src/runtime/routes/conversation-query-routes.ts +2 -0
  280. package/src/runtime/routes/conversation-routes.ts +358 -184
  281. package/src/runtime/routes/documents-routes.ts +4 -0
  282. package/src/runtime/routes/domain-routes.ts +51 -37
  283. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  284. package/src/runtime/routes/events-routes.ts +28 -34
  285. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  286. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  287. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  288. package/src/runtime/routes/identity-routes.ts +208 -17
  289. package/src/runtime/routes/image-generation-routes.ts +40 -2
  290. package/src/runtime/routes/index.ts +2 -0
  291. package/src/runtime/routes/integrations/a2a.ts +12 -10
  292. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  293. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  294. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  295. package/src/runtime/routes/integrations/telegram.ts +6 -0
  296. package/src/runtime/routes/integrations/twilio.ts +42 -0
  297. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  298. package/src/runtime/routes/log-export-routes.ts +8 -0
  299. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  300. package/src/runtime/routes/memory-v3-routes.ts +50 -28
  301. package/src/runtime/routes/oauth-apps.ts +66 -12
  302. package/src/runtime/routes/oauth-providers.ts +44 -5
  303. package/src/runtime/routes/platform-routes.ts +81 -5
  304. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  305. package/src/runtime/routes/playground/force-compact.ts +1 -1
  306. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  307. package/src/runtime/routes/schedule-routes.ts +152 -42
  308. package/src/runtime/routes/secret-routes.ts +14 -2
  309. package/src/runtime/routes/skills-routes.ts +43 -14
  310. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  311. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  312. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  313. package/src/runtime/routes/tts-routes.ts +35 -0
  314. package/src/runtime/routes/types.ts +66 -8
  315. package/src/runtime/routes/usage-routes.ts +47 -39
  316. package/src/runtime/routes/webhook-routes.ts +41 -2
  317. package/src/runtime/routes/workspace-routes.ts +4 -0
  318. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  319. package/src/runtime/services/analyze-conversation.ts +2 -2
  320. package/src/schedule/schedule-store.ts +20 -1
  321. package/src/schedule/schedule-usage-store.ts +83 -0
  322. package/src/schedule/scheduler.ts +12 -5
  323. package/src/skills/catalog-files.ts +2 -2
  324. package/src/skills/catalog-install.ts +3 -0
  325. package/src/skills/categories-cache.ts +118 -0
  326. package/src/skills/clawhub-files.ts +1 -2
  327. package/src/skills/skillssh-files.ts +1 -2
  328. package/src/telemetry/types.ts +29 -1
  329. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  330. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  331. package/src/tools/executor.ts +1 -53
  332. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  333. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  334. package/src/tools/network/web-search-error.test.ts +248 -0
  335. package/src/tools/network/web-search-error.ts +267 -0
  336. package/src/tools/network/web-search.ts +207 -48
  337. package/src/tools/schedule/create.ts +2 -0
  338. package/src/tools/terminal/safe-env.ts +10 -1
  339. package/src/tools/ui-surface/definitions.ts +9 -1
  340. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  341. package/src/tts/provider-catalog.ts +76 -1
  342. package/src/util/mutex.ts +47 -0
  343. package/src/workspace/git-service.ts +1 -42
  344. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  345. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  346. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
  347. package/src/workspace/migrations/registry.ts +6 -0
  348. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  349. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  350. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  351. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  352. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  353. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  354. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  355. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  356. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  357. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  358. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  359. package/src/gallery/default-gallery.ts +0 -1359
  360. package/src/gallery/gallery-manifest.ts +0 -28
  361. package/src/home/feature-gate.ts +0 -22
  362. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  363. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  364. package/src/plugins/defaults/injectors/package.json +0 -15
  365. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  366. package/src/plugins/defaults/llm-call/package.json +0 -15
  367. package/src/plugins/defaults/llm-call/register.ts +0 -45
  368. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  369. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  370. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  371. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  372. package/src/plugins/defaults/persistence/package.json +0 -15
  373. package/src/plugins/defaults/persistence/register.ts +0 -38
  374. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  375. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  376. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  377. package/src/plugins/defaults/token-estimate/package.json +0 -15
  378. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  379. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  380. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  381. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  382. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  383. package/src/plugins/defaults/tool-execute/package.json +0 -15
  384. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  385. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  386. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  387. package/src/skills/category-inference.ts +0 -111
@@ -71,6 +71,8 @@ describe("onboarding template contracts", () => {
71
71
 
72
72
  test("offers assistant migration during low-signal first openings", () => {
73
73
  expect(bootstrap).toContain("## Assistant migration");
74
+ expect(bootstrap).toContain("onboarding self-introduction");
75
+ expect(bootstrap).toContain("treat it as the real first user turn");
74
76
  expect(bootstrap).toContain(
75
77
  "If the first real user turn is only a greeting",
76
78
  );
@@ -351,6 +351,23 @@ describe("mapOpenAIError", () => {
351
351
  expect(msg).toContain("Rate limit");
352
352
  });
353
353
 
354
+ test("maps 402 status to a non-retryable out-of-credits message", () => {
355
+ const msg = mapOpenAIError(new FakeAPIError(402, "payment required"));
356
+ expect(msg).toContain("out of credits");
357
+ expect(msg).not.toContain("Please try again");
358
+ });
359
+
360
+ test("maps insufficient_quota (reported as 429) to out-of-credits message", () => {
361
+ const msg = mapOpenAIError(
362
+ new FakeAPIError(
363
+ 429,
364
+ "You exceeded your current quota, please check your plan and billing details. (insufficient_quota)",
365
+ ),
366
+ );
367
+ expect(msg).toContain("out of credits");
368
+ expect(msg).not.toContain("Rate limit");
369
+ });
370
+
354
371
  test("maps 500 status to server error message", () => {
355
372
  const msg = mapOpenAIError(new FakeAPIError(500, "internal"));
356
373
  expect(msg).toContain("temporarily unavailable");
@@ -105,7 +105,10 @@ mock.module("openai", () => ({
105
105
  import { FireworksProvider } from "../providers/fireworks/client.js";
106
106
  import { MinimaxProvider } from "../providers/minimax/client.js";
107
107
  import { OllamaProvider } from "../providers/ollama/client.js";
108
- import { OpenAIChatCompletionsProvider } from "../providers/openai/chat-completions-provider.js";
108
+ import {
109
+ EMPTY_ASSISTANT_TURN_PLACEHOLDER,
110
+ OpenAIChatCompletionsProvider,
111
+ } from "../providers/openai/chat-completions-provider.js";
109
112
  import { OpenAIProvider } from "../providers/openai/client.js";
110
113
  import { OpenRouterProvider } from "../providers/openrouter/client.js";
111
114
 
@@ -1584,6 +1587,33 @@ describe("OpenRouterProvider reasoning", () => {
1584
1587
  expect(lastCreateParams).not.toHaveProperty("usageAttributionHeaders");
1585
1588
  });
1586
1589
 
1590
+ test("backfills placeholder content for a reasoning-only assistant turn", async () => {
1591
+ const provider = new OpenRouterProvider("or-key", "deepseek/deepseek-chat");
1592
+ await provider.sendMessage([
1593
+ userMsg("question"),
1594
+ {
1595
+ role: "assistant",
1596
+ content: [
1597
+ { type: "thinking", thinking: "truncated reasoning", signature: "" },
1598
+ ],
1599
+ },
1600
+ ]);
1601
+
1602
+ const sent = lastCreateParams!.messages as Array<{
1603
+ role: string;
1604
+ content: string | null;
1605
+ reasoning?: string;
1606
+ tool_calls?: unknown;
1607
+ }>;
1608
+ const assistantMsg = sent.find((m) => m.role === "assistant")!;
1609
+ // DeepSeek via OpenRouter rejects an assistant message with neither content
1610
+ // nor tool_calls, so the reasoning-only turn is backfilled with the sentinel
1611
+ // while the reasoning itself travels in the separate `reasoning` field.
1612
+ expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
1613
+ expect(assistantMsg.tool_calls).toBeUndefined();
1614
+ expect(assistantMsg.reasoning).toBe("truncated reasoning");
1615
+ });
1616
+
1587
1617
  test("RetryProvider + OpenRouterProvider enables thinking end-to-end", async () => {
1588
1618
  const provider = new OpenRouterProvider("or-key", "x-ai/grok-4");
1589
1619
  const retry = new RetryProvider(provider);
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Regression tests for the image-too-large persistence path (JARVIS-1037).
3
+ *
4
+ * An image the provider can never accept — over the per-side pixel cap or the
5
+ * per-image byte cap, and not shrinkable on this host — must be durably swapped
6
+ * for a text note in its stored message. If it stays in the stored content,
7
+ * every later turn rehydrates it from the DB and the model reports seeing both
8
+ * the rejected image and any smaller re-upload. `persistUnsendableImageDowngrades`
9
+ * makes the swap durable so the rejected upload cannot resurface.
10
+ *
11
+ * Uses the real SQLite DB wired up via `test-preload.ts` (per-file temp
12
+ * workspace).
13
+ */
14
+
15
+ import { beforeEach, describe, expect, test } from "bun:test";
16
+
17
+ import { persistUnsendableImageDowngrades } from "../daemon/persist-unsendable-image.js";
18
+ import {
19
+ addMessage,
20
+ createConversation,
21
+ getMessages,
22
+ } from "../memory/conversation-crud.js";
23
+ import { getDb } from "../memory/db-connection.js";
24
+ import { initializeDb } from "../memory/db-init.js";
25
+ import type { ContentBlock } from "../providers/types.js";
26
+
27
+ initializeDb();
28
+
29
+ function resetTables(): void {
30
+ const db = getDb();
31
+ db.run("DELETE FROM message_attachments");
32
+ db.run("DELETE FROM attachments");
33
+ db.run("DELETE FROM memory_segments");
34
+ db.run("DELETE FROM memory_embeddings");
35
+ db.run("DELETE FROM messages");
36
+ db.run("DELETE FROM conversations");
37
+ }
38
+
39
+ /**
40
+ * Build a minimal PNG whose IHDR declares the given dimensions. Only the
41
+ * 8-byte signature and the width/height fields (read by `parseImageDimensions`)
42
+ * need to be correct; the rest is padding. `optimizeImageForTransport` cannot
43
+ * downscale this off macOS (no `sips`), so it stays a no-op — exactly the
44
+ * host condition that produces an unsendable stored image.
45
+ */
46
+ function makePngBase64(width: number, height: number, padBytes = 0): string {
47
+ const header = Buffer.from(
48
+ Uint8Array.from([
49
+ 0x89,
50
+ 0x50,
51
+ 0x4e,
52
+ 0x47,
53
+ 0x0d,
54
+ 0x0a,
55
+ 0x1a,
56
+ 0x0a, // PNG signature
57
+ 0x00,
58
+ 0x00,
59
+ 0x00,
60
+ 0x0d, // IHDR length (13)
61
+ 0x49,
62
+ 0x48,
63
+ 0x44,
64
+ 0x52, // "IHDR"
65
+ (width >>> 24) & 0xff,
66
+ (width >>> 16) & 0xff,
67
+ (width >>> 8) & 0xff,
68
+ width & 0xff,
69
+ (height >>> 24) & 0xff,
70
+ (height >>> 16) & 0xff,
71
+ (height >>> 8) & 0xff,
72
+ height & 0xff,
73
+ 0x08,
74
+ 0x06,
75
+ 0x00,
76
+ 0x00,
77
+ 0x00, // bit depth / color type / etc.
78
+ ]),
79
+ ).toString("base64");
80
+ return padBytes > 0 ? header + "A".repeat(padBytes) : header;
81
+ }
82
+
83
+ function imageBlock(data: string): ContentBlock {
84
+ return {
85
+ type: "image",
86
+ source: { type: "base64", media_type: "image/png", data },
87
+ };
88
+ }
89
+
90
+ function storedContent(conversationId: string): ContentBlock[][] {
91
+ return getMessages(conversationId).map(
92
+ (row) => JSON.parse(row.content) as ContentBlock[],
93
+ );
94
+ }
95
+
96
+ const PROVIDER_MAX_IMAGE_DIMENSION = 8000;
97
+
98
+ describe("persistUnsendableImageDowngrades", () => {
99
+ beforeEach(() => {
100
+ resetTables();
101
+ });
102
+
103
+ /** A stored image past the provider pixel cap is swapped for a text note. */
104
+ test("replaces an oversized image block with a text note", async () => {
105
+ // GIVEN a message holding text plus an image past the pixel cap
106
+ const conv = createConversation();
107
+ const oversized = makePngBase64(PROVIDER_MAX_IMAGE_DIMENSION + 1000, 6000);
108
+ await addMessage(
109
+ conv.id,
110
+ "user",
111
+ JSON.stringify([
112
+ { type: "text", text: "look at this" },
113
+ imageBlock(oversized),
114
+ ]),
115
+ { skipIndexing: true },
116
+ );
117
+
118
+ // WHEN the downgrade is persisted
119
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
120
+
121
+ // THEN one message is rewritten with no image block left
122
+ expect(rewritten).toBe(1);
123
+ const [content] = storedContent(conv.id);
124
+ expect(content.some((b) => b.type === "image")).toBe(false);
125
+ // AND the original text is preserved alongside the substituted note
126
+ expect(content.filter((b) => b.type === "text")).toHaveLength(2);
127
+ });
128
+
129
+ /** The JARVIS-1037 scenario: the rejected original must not resurface next
130
+ * to a valid re-upload. */
131
+ test("re-uploaded smaller image survives while the rejected original is removed", async () => {
132
+ // GIVEN turn 1 contains an oversized upload that was rejected
133
+ const conv = createConversation();
134
+ await addMessage(
135
+ conv.id,
136
+ "user",
137
+ JSON.stringify([imageBlock(makePngBase64(12000, 9000))]),
138
+ { skipIndexing: true },
139
+ );
140
+ // AND turn 2 contains a properly sized re-upload
141
+ await addMessage(
142
+ conv.id,
143
+ "user",
144
+ JSON.stringify([imageBlock(makePngBase64(800, 600))]),
145
+ { skipIndexing: true },
146
+ );
147
+
148
+ // WHEN the downgrade is persisted
149
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
150
+
151
+ // THEN only the rejected original is removed
152
+ expect(rewritten).toBe(1);
153
+ const [first, second] = storedContent(conv.id);
154
+ expect(first.some((b) => b.type === "image")).toBe(false);
155
+ // AND the valid re-upload is left intact
156
+ expect(second.some((b) => b.type === "image")).toBe(true);
157
+ });
158
+
159
+ /** Sendable images are never disturbed by the recovery path. */
160
+ test("leaves a normally-sized image untouched", async () => {
161
+ // GIVEN a message with an image well within provider limits
162
+ const conv = createConversation();
163
+ await addMessage(
164
+ conv.id,
165
+ "user",
166
+ JSON.stringify([imageBlock(makePngBase64(1024, 768))]),
167
+ { skipIndexing: true },
168
+ );
169
+
170
+ // WHEN the downgrade is persisted
171
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
172
+
173
+ // THEN nothing is rewritten and the image remains
174
+ expect(rewritten).toBe(0);
175
+ const [content] = storedContent(conv.id);
176
+ expect(content.some((b) => b.type === "image")).toBe(true);
177
+ });
178
+
179
+ /** The byte-size cap is enforced independently of pixel dimensions. */
180
+ test("removes an image whose payload exceeds the per-image byte cap", async () => {
181
+ // GIVEN an image within the pixel cap but with a payload over 5 MB
182
+ const conv = createConversation();
183
+ const huge = makePngBase64(1000, 1000, 6 * 1024 * 1024);
184
+ await addMessage(conv.id, "user", JSON.stringify([imageBlock(huge)]), {
185
+ skipIndexing: true,
186
+ });
187
+
188
+ // WHEN the downgrade is persisted
189
+ const rewritten = persistUnsendableImageDowngrades(conv.id);
190
+
191
+ // THEN the oversized-payload image is removed
192
+ expect(rewritten).toBe(1);
193
+ const [content] = storedContent(conv.id);
194
+ expect(content.some((b) => b.type === "image")).toBe(false);
195
+ });
196
+
197
+ /** Re-running after a rewrite is a safe no-op (no image blocks remain). */
198
+ test("is idempotent — a second run rewrites nothing", async () => {
199
+ // GIVEN a conversation whose oversized image has already been downgraded
200
+ const conv = createConversation();
201
+ await addMessage(
202
+ conv.id,
203
+ "user",
204
+ JSON.stringify([imageBlock(makePngBase64(10000, 10000))]),
205
+ { skipIndexing: true },
206
+ );
207
+ expect(persistUnsendableImageDowngrades(conv.id)).toBe(1);
208
+
209
+ // WHEN the downgrade runs a second time
210
+ const secondRun = persistUnsendableImageDowngrades(conv.id);
211
+
212
+ // THEN nothing further is rewritten
213
+ expect(secondRun).toBe(0);
214
+ });
215
+ });
@@ -149,6 +149,7 @@ function makeDeps(): EventHandlerDeps {
149
149
  userMessageInterface: "macos",
150
150
  assistantMessageInterface: "macos",
151
151
  } as EventHandlerDeps["turnInterfaceContext"],
152
+ applyCompaction: async () => {},
152
153
  } as EventHandlerDeps;
153
154
  }
154
155
 
@@ -170,12 +170,12 @@ describe("runPipeline — error propagation", () => {
170
170
 
171
171
  await expect(
172
172
  runPipeline(
173
- "persistence",
173
+ "circuitBreaker",
174
174
  [thrower],
175
175
  terminal,
176
176
  { value: 1 },
177
177
  makeCtx(),
178
- DEFAULT_TIMEOUTS.persistence,
178
+ DEFAULT_TIMEOUTS.circuitBreaker,
179
179
  ),
180
180
  ).rejects.toBeInstanceOf(Boom);
181
181
  });
@@ -187,12 +187,12 @@ describe("runPipeline — error propagation", () => {
187
187
 
188
188
  await expect(
189
189
  runPipeline(
190
- "persistence",
190
+ "circuitBreaker",
191
191
  [],
192
192
  terminal,
193
193
  { value: 1 },
194
194
  makeCtx(),
195
- DEFAULT_TIMEOUTS.persistence,
195
+ DEFAULT_TIMEOUTS.circuitBreaker,
196
196
  ),
197
197
  ).rejects.toBeInstanceOf(TypeError);
198
198
  });
@@ -210,7 +210,7 @@ describe("runPipeline — timeout", () => {
210
210
  let caught: unknown;
211
211
  try {
212
212
  await runPipeline(
213
- "memoryRetrieval",
213
+ "compaction",
214
214
  [sleeper],
215
215
  terminal,
216
216
  { value: 1 },
@@ -223,10 +223,10 @@ describe("runPipeline — timeout", () => {
223
223
 
224
224
  expect(caught).toBeInstanceOf(PluginTimeoutError);
225
225
  const tErr = caught as PluginTimeoutError;
226
- expect(tErr.pipeline).toBe("memoryRetrieval");
226
+ expect(tErr.pipeline).toBe("compaction");
227
227
  expect(tErr.pluginName).toBe("slow-plugin");
228
228
  expect(tErr.elapsedMs).toBeGreaterThanOrEqual(0);
229
- expect(tErr.message).toContain("memoryRetrieval");
229
+ expect(tErr.message).toContain("compaction");
230
230
  expect(tErr.message).toContain("slow-plugin");
231
231
  });
232
232
 
@@ -235,32 +235,32 @@ describe("runPipeline — timeout", () => {
235
235
  value: args.value,
236
236
  });
237
237
  const result = await runPipeline(
238
- "tokenEstimate",
238
+ "compaction",
239
239
  [],
240
240
  terminal,
241
241
  { value: 42 },
242
242
  makeCtx(),
243
- DEFAULT_TIMEOUTS.tokenEstimate,
243
+ DEFAULT_TIMEOUTS.compaction,
244
244
  );
245
245
  expect(result).toEqual({ value: 42 });
246
246
  });
247
247
 
248
248
  test("null timeout skips the race entirely", async () => {
249
- // llmCall has DEFAULT_TIMEOUTS.llmCall === null — runner must not arm a
250
- // timer. We verify by completing after an artificial 30ms wait and
251
- // confirming success without interference.
249
+ // compaction has DEFAULT_TIMEOUTS.compaction === null — runner must
250
+ // not arm a timer. We verify by completing after an artificial 30ms wait
251
+ // and confirming success without interference.
252
252
  const sleeper: Middleware<Args, Result> = async (args, next) =>
253
253
  new Promise<Result>((resolve) => {
254
254
  setTimeout(() => resolve(next(args)), 30);
255
255
  });
256
256
  const terminal = async (_args: Args): Promise<Result> => ({ value: 1 });
257
257
  const result = await runPipeline(
258
- "llmCall",
258
+ "compaction",
259
259
  [sleeper],
260
260
  terminal,
261
261
  { value: 0 },
262
262
  makeCtx(),
263
- DEFAULT_TIMEOUTS.llmCall,
263
+ DEFAULT_TIMEOUTS.compaction,
264
264
  );
265
265
  expect(result).toEqual({ value: 1 });
266
266
  });
@@ -356,7 +356,7 @@ describe("runPipeline — timeout aborts linked signal", () => {
356
356
  });
357
357
 
358
358
  test("args without an AbortSignal property is passed through unchanged", async () => {
359
- // Sanity — pipelines that don't carry a signal (persistence, tokenEstimate)
359
+ // Sanity — pipelines that don't carry a signal (circuitBreaker)
360
360
  // see identical args identity as before the abort-linking change.
361
361
  const args: Args = { value: 42 };
362
362
  let seen: Args | undefined;
@@ -365,12 +365,12 @@ describe("runPipeline — timeout aborts linked signal", () => {
365
365
  return { value: innerArgs.value };
366
366
  };
367
367
  await runPipeline(
368
- "persistence",
368
+ "circuitBreaker",
369
369
  [],
370
370
  terminal,
371
371
  args,
372
372
  makeCtx(),
373
- DEFAULT_TIMEOUTS.persistence,
373
+ DEFAULT_TIMEOUTS.circuitBreaker,
374
374
  );
375
375
  expect(seen).toBe(args);
376
376
  });
@@ -428,19 +428,19 @@ describe("runPipeline — structured log record", () => {
428
428
 
429
429
  await expect(
430
430
  runPipeline(
431
- "toolError",
431
+ "circuitBreaker",
432
432
  [thrower],
433
433
  terminal,
434
434
  { value: 1 },
435
435
  makeCtx({ pluginName: "noisy-plugin" }),
436
- DEFAULT_TIMEOUTS.toolError,
436
+ DEFAULT_TIMEOUTS.circuitBreaker,
437
437
  ),
438
438
  ).rejects.toBeInstanceOf(Boom);
439
439
 
440
440
  expect(fakeLogger.calls.length).toBe(1);
441
441
  const [record] = fakeLogger.calls[0]!;
442
442
  expect(record.outcome).toBe("error");
443
- expect(record.pipeline).toBe("toolError");
443
+ expect(record.pipeline).toBe("circuitBreaker");
444
444
  expect(record.errorName).toBe("BoomError");
445
445
  expect(record.errorMessage).toBe("kaboom");
446
446
  expect(typeof record.errorStack).toBe("string");
@@ -456,7 +456,7 @@ describe("runPipeline — structured log record", () => {
456
456
 
457
457
  await expect(
458
458
  runPipeline(
459
- "emptyResponse",
459
+ "circuitBreaker",
460
460
  [sleeper],
461
461
  terminal,
462
462
  { value: 1 },
@@ -468,9 +468,9 @@ describe("runPipeline — structured log record", () => {
468
468
  expect(fakeLogger.calls.length).toBe(1);
469
469
  const [record] = fakeLogger.calls[0]!;
470
470
  expect(record.outcome).toBe("timeout");
471
- expect(record.pipeline).toBe("emptyResponse");
471
+ expect(record.pipeline).toBe("circuitBreaker");
472
472
  expect(record.errorName).toBe("PluginTimeoutError");
473
- expect(String(record.errorMessage)).toContain("emptyResponse");
473
+ expect(String(record.errorMessage)).toContain("circuitBreaker");
474
474
  expect(String(record.errorMessage)).toContain("slow-plugin");
475
475
  expect(record.timeoutMs).toBe(15);
476
476
  expect(record.pluginName).toBe("slow-plugin");
@@ -481,17 +481,17 @@ describe("runPipeline — structured log record", () => {
481
481
  value: args.value,
482
482
  });
483
483
  await runPipeline(
484
- "llmCall",
484
+ "compaction",
485
485
  [],
486
486
  terminal,
487
487
  { value: 5 },
488
488
  makeCtx(),
489
- DEFAULT_TIMEOUTS.llmCall,
489
+ DEFAULT_TIMEOUTS.compaction,
490
490
  );
491
491
 
492
492
  expect(fakeLogger.calls.length).toBe(1);
493
493
  const [record] = fakeLogger.calls[0]!;
494
- expect(record.pipeline).toBe("llmCall");
494
+ expect(record.pipeline).toBe("compaction");
495
495
  expect(record.outcome).toBe("success");
496
496
  expect(record.timeoutMs).toBeUndefined();
497
497
  });
@@ -506,7 +506,7 @@ describe("runPipeline — structured log record", () => {
506
506
  logger: fakeLogger,
507
507
  } as TurnContext;
508
508
  await runPipeline(
509
- "persistence",
509
+ "circuitBreaker",
510
510
  [],
511
511
  terminal,
512
512
  { value: 0 },
@@ -531,12 +531,12 @@ describe("runPipeline — structured log record", () => {
531
531
  value: args.value,
532
532
  });
533
533
  await runPipeline(
534
- "tokenEstimate",
534
+ "compaction",
535
535
  [a, b, c],
536
536
  terminal,
537
537
  { value: 0 },
538
538
  makeCtx(),
539
- DEFAULT_TIMEOUTS.tokenEstimate,
539
+ DEFAULT_TIMEOUTS.compaction,
540
540
  );
541
541
  const [record] = fakeLogger.calls[0]!;
542
542
  expect(record.chain).toEqual(["outerA", "middleB", "innerC"]);
@@ -546,18 +546,8 @@ describe("runPipeline — structured log record", () => {
546
546
  describe("DEFAULT_TIMEOUTS", () => {
547
547
  test("matches the design-doc table exactly", () => {
548
548
  expect(DEFAULT_TIMEOUTS).toEqual({
549
- turn: null,
550
- llmCall: null,
551
- toolExecute: null,
552
- memoryRetrieval: null,
553
- tokenEstimate: null,
554
549
  compaction: null,
555
550
  overflowReduce: null,
556
- persistence: null,
557
- titleGenerate: null,
558
- toolResultTruncate: null,
559
- emptyResponse: null,
560
- toolError: null,
561
551
  circuitBreaker: null,
562
552
  });
563
553
  });
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
6
  import {
7
7
  getPkbAutoInjectList,
8
8
  readAutoinjectList,
9
- } from "../daemon/conversation-runtime-assembly.js";
9
+ } from "../memory/pkb/autoinject.js";
10
10
 
11
11
  const PKB_DEFAULT_FILES = [
12
12
  "INDEX.md",
@@ -80,10 +80,7 @@ describe("readAutoinjectList", () => {
80
80
  "INDEX.md\ncustom-topic.md\n",
81
81
  "utf-8",
82
82
  );
83
- expect(readAutoinjectList(pkbDir)).toEqual([
84
- "INDEX.md",
85
- "custom-topic.md",
86
- ]);
83
+ expect(readAutoinjectList(pkbDir)).toEqual(["INDEX.md", "custom-topic.md"]);
87
84
  });
88
85
 
89
86
  test("strips blank lines and whitespace", () => {
@@ -38,7 +38,6 @@ import { RiskLevel } from "../permissions/types.js";
38
38
  import { registerDefaultPlugins } from "../plugins/defaults/index.js";
39
39
  import {
40
40
  closeRegistration,
41
- getInjectors,
42
41
  getMiddlewaresFor,
43
42
  getRegisteredPlugins,
44
43
  registerPlugin,
@@ -342,8 +341,8 @@ describe("plugin bootstrap", () => {
342
341
  // THEN the defaults are still registered, ahead of the user plugin, so the
343
342
  // middleware onion order is unchanged
344
343
  const names = getRegisteredPlugins().map((p) => p.manifest.name);
345
- expect(names).toContain("default-llm-call");
346
- expect(names.indexOf("default-llm-call")).toBeLessThan(
344
+ expect(names).toContain("default-compaction");
345
+ expect(names.indexOf("default-compaction")).toBeLessThan(
347
346
  names.indexOf("user-after-defaults"),
348
347
  );
349
348
  });
@@ -486,32 +485,22 @@ describe("plugin bootstrap", () => {
486
485
  expect(initFired).toBe(false);
487
486
  });
488
487
 
489
- test("requiresFlag disabled: plugin middleware and injectors are dropped from the registry", async () => {
488
+ test("requiresFlag disabled: plugin middleware is dropped from the registry", async () => {
490
489
  // Regression: prior to the unregisterPlugin() call on the flag-gated skip
491
- // path, `getMiddlewaresFor()` and `getInjectors()` iterated over every
492
- // entry in `registeredPlugins` — so a gated-off plugin's middleware and
493
- // injectors still ran on every pipeline invocation and system-prompt
494
- // assembly even though `init()` had never fired to set up the state they
495
- // depended on.
490
+ // path, `getMiddlewaresFor()` iterated over every entry in
491
+ // `registeredPlugins` — so a gated-off plugin's middleware still ran on
492
+ // every pipeline invocation even though `init()` had never fired to set up
493
+ // the state it depended on.
496
494
  setOverridesForTesting({ "plugin-middleware-disabled": false });
497
495
 
498
- const gatedMiddleware: PipelineMiddlewareMap["llmCall"] = async (
496
+ const gatedMiddleware: PipelineMiddlewareMap["compaction"] = async (
499
497
  args,
500
498
  next,
501
499
  ) => next(args);
502
500
  const plugin = buildPlugin(
503
501
  "gated-middleware",
504
502
  {
505
- middleware: { llmCall: gatedMiddleware },
506
- injectors: [
507
- {
508
- name: "gated-middleware-injector",
509
- order: 100,
510
- async produce() {
511
- return null;
512
- },
513
- },
514
- ],
503
+ middleware: { compaction: gatedMiddleware },
515
504
  },
516
505
  { requiresFlag: ["plugin-middleware-disabled"] },
517
506
  );
@@ -519,14 +508,10 @@ describe("plugin bootstrap", () => {
519
508
 
520
509
  await bootstrapPlugins();
521
510
 
522
- // Neither the middleware slot nor the injector list should expose the
523
- // flag-gated plugin's contributions. The default plugins also contribute
524
- // llmCall middleware / injectors, so we key on identity rather than
525
- // asserting empty lists.
526
- expect(getMiddlewaresFor("llmCall")).not.toContain(gatedMiddleware);
527
- expect(
528
- getInjectors().some((i) => i.name === "gated-middleware-injector"),
529
- ).toBe(false);
511
+ // The middleware slot must not expose the flag-gated plugin's contribution.
512
+ // The default plugins also contribute compaction middleware, so we key on
513
+ // identity rather than asserting an empty list.
514
+ expect(getMiddlewaresFor("compaction")).not.toContain(gatedMiddleware);
530
515
  });
531
516
 
532
517
  test("requiresFlag disabled: no shutdown hook entry installed for the skipped plugin", async () => {
@@ -10,7 +10,6 @@ import { beforeEach, describe, expect, test } from "bun:test";
10
10
 
11
11
  import {
12
12
  closeRegistration,
13
- getInjectors,
14
13
  getMiddlewaresFor,
15
14
  getRegisteredPlugins,
16
15
  registerPlugin,
@@ -19,7 +18,6 @@ import {
19
18
  import {
20
19
  type CompactionArgs,
21
20
  type CompactionResult,
22
- type Injector,
23
21
  type Middleware,
24
22
  type Plugin,
25
23
  PluginExecutionError,
@@ -149,31 +147,6 @@ describe("plugin registry", () => {
149
147
  expect(() => registerPlugin(bad)).toThrow(/manifest\.version is required/);
150
148
  });
151
149
 
152
- test("getInjectors returns injectors sorted by order ascending", () => {
153
- const high: Injector = {
154
- name: "high-order",
155
- order: 20,
156
- async produce() {
157
- return null;
158
- },
159
- };
160
- const low: Injector = {
161
- name: "low-order",
162
- order: 10,
163
- async produce() {
164
- return null;
165
- },
166
- };
167
-
168
- // Register the higher-order plugin first so registration order alone
169
- // would produce the wrong sequence — the test proves sort-by-order wins.
170
- registerPlugin(buildPlugin("high", { injectors: [high] }));
171
- registerPlugin(buildPlugin("low", { injectors: [low] }));
172
-
173
- const injectors = getInjectors();
174
- expect(injectors.map((i) => i.name)).toEqual(["low-order", "high-order"]);
175
- });
176
-
177
150
  test("getMiddlewaresFor returns middleware in registration order", () => {
178
151
  const firstMw: Middleware<CompactionArgs, CompactionResult> = async (
179
152
  args,