@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,35 +1,24 @@
1
1
  /**
2
2
  * Default `compaction` plugin.
3
3
  *
4
- * The plugin's middleware is a passthrough it calls `next(args)` and returns
5
- * the result unchanged. The actual compaction lives in the terminal handler in
6
- * `./terminal.ts`, which is wired in as the pipeline's `terminal` argument by
7
- * the `runPipeline` call site in `daemon/conversation-agent-loop.ts`. This
8
- * separation matters: the default plugin is registered before any user plugin
9
- * (defaults load first in `bootstrapPlugins()`), which puts it at the
10
- * OUTERMOST position of the onion chain. If the default middleware were to
11
- * invoke the terminal directly without calling `next`, it would shadow every
12
- * later-registered plugin. Routing through `next(args)` lets user middleware
13
- * participate normally.
14
- *
15
- * Design doc: `.private/plans/agent-plugin-system.md` (PR 25).
4
+ * Compaction is implemented in `./compact.ts` as {@link defaultCompact}, which
5
+ * the agent loop calls directly with a {@link CompactionContext}. The plugin
6
+ * stays registered as a placeholder so it keeps a presence in the defaults
7
+ * list while we decide how plugins should surface compaction; it contributes
8
+ * no contract slot today.
16
9
  */
17
10
 
18
11
  import { type Plugin } from "../../types.js";
19
- import defaultCompactionMiddleware from "./middlewares/compaction.js";
20
12
  import pkg from "./package.json" with { type: "json" };
21
13
 
22
14
  /**
23
- * Manifest + middleware wiring for the default compaction plugin. The
24
- * registration happens in `daemon/external-plugins-bootstrap.ts` before
25
- * {@link bootstrapPlugins} fires plugin `init()` hooks.
15
+ * Manifest for the default compaction plugin. The registration happens in
16
+ * `daemon/external-plugins-bootstrap.ts` before {@link bootstrapPlugins} fires
17
+ * plugin `init()` hooks.
26
18
  */
27
19
  export const defaultCompactionPlugin: Plugin = {
28
20
  manifest: {
29
21
  name: pkg.name,
30
22
  version: pkg.version,
31
23
  },
32
- middleware: {
33
- compaction: defaultCompactionMiddleware,
34
- },
35
24
  };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Default `stop` hook: when the model yields a turn with no tool calls, decide
3
+ * whether to let the turn end or re-query the model with a nudge.
4
+ *
5
+ * Two cases warrant a nudge:
6
+ *
7
+ * 1. **Refusal stop.** The provider returned `stopReason === "refusal"` with no
8
+ * visible text (Anthropic's safety classifier zeroed the response). Nudged
9
+ * even on the first model call of the run — a refusal there guarantees no
10
+ * organic text exists yet, so without intervening the loop would persist an
11
+ * empty assistant bubble to the user. Uses `REFUSAL_NUDGE_TEXT`.
12
+ * 2. **Empty turn after tool use.** The turn produced no visible text, follows
13
+ * at least one prior assistant turn this run, and no earlier turn this run
14
+ * already delivered visible text. Uses `NUDGE_TEXT`.
15
+ *
16
+ * Every other case leaves the decision at `"stop"` (the model said its piece,
17
+ * or there is nothing to nudge about). The retry cap is owned by the agent
18
+ * loop: this hook always asks to continue when a nudge is warranted, and the
19
+ * loop stops anyway once the run's nudge budget is spent.
20
+ *
21
+ * Both prior-turn signals are derived from the current response cycle — the
22
+ * messages after the last genuine user prompt (a user turn that isn't purely
23
+ * tool results). Scoping this way keeps prior conversation turns from polluting
24
+ * the signals, and deriving the boundary from history content rather than an
25
+ * index means mid-run compaction (which rewrites the array in place) can't
26
+ * invalidate it. A prior assistant turn this cycle implies a completed tool-use
27
+ * iteration (an empty turn nudges-and-continues without pushing an assistant
28
+ * message), so "a prior assistant turn exists" is the equivalent of "this is
29
+ * not the first model call".
30
+ *
31
+ * Defaults register before any user plugin, so this hook runs at the front of
32
+ * the `stop` chain — later hooks see (and may override) its decision.
33
+ */
34
+
35
+ import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
36
+
37
+ import type { ContentBlock, Message } from "../../../../providers/types.js";
38
+
39
+ /**
40
+ * Canonical nudge text for an empty turn after tool use. Must stay verbatim so
41
+ * a plugin that wraps the default sees a stable string.
42
+ *
43
+ * Wire-compat note: this is shown to the LLM, not the user. Edits here affect
44
+ * model behavior but not end-user UX directly.
45
+ */
46
+ export const NUDGE_TEXT =
47
+ "<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
48
+
49
+ /**
50
+ * Refusal-specific nudge. Used when the provider stops with `"refusal"` and no
51
+ * visible text — i.e. the safety classifier zeroed the response. Kept distinct
52
+ * from `NUDGE_TEXT` so the model gets context-appropriate guidance (no "summary
53
+ * of what you found or did" — there is no tool trail to summarize on a refusal).
54
+ *
55
+ * Wire-compat note: this is shown to the LLM, not the user. Edits here affect
56
+ * retry behavior but not end-user UX directly.
57
+ */
58
+ export const REFUSAL_NUDGE_TEXT =
59
+ '<system_notice>Your previous response was empty because the upstream provider returned stop_reason="refusal". Please answer the user\'s last message directly with a plain-text response. Do not use any tools — just respond with text.</system_notice>';
60
+
61
+ function hasVisibleText(content: ReadonlyArray<ContentBlock>): boolean {
62
+ return content.some(
63
+ (block) => block.type === "text" && block.text.trim().length > 0,
64
+ );
65
+ }
66
+
67
+ function isAssistantTurn(message: Message): boolean {
68
+ return message.role === "assistant";
69
+ }
70
+
71
+ /** A user-role message carrying only tool results, not a fresh prompt. */
72
+ function isToolResultMessage(message: Message): boolean {
73
+ return (
74
+ message.role === "user" &&
75
+ message.content.length > 0 &&
76
+ message.content.every((block) => block.type === "tool_result")
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Messages belonging to the current response cycle: everything after the last
82
+ * genuine user prompt. Falls back to the whole history when none is found.
83
+ */
84
+ function currentCycleMessages(
85
+ messages: ReadonlyArray<Message>,
86
+ ): ReadonlyArray<Message> {
87
+ for (let i = messages.length - 1; i >= 0; i--) {
88
+ const message = messages[i];
89
+ if (message.role === "user" && !isToolResultMessage(message)) {
90
+ return messages.slice(i + 1);
91
+ }
92
+ }
93
+ return messages;
94
+ }
95
+
96
+ const stop: PluginHookFn<StopContext> = async (ctx) => {
97
+ const turnHasVisibleText = hasVisibleText(ctx.responseContent);
98
+
99
+ const appendNudge = (text: string): void => {
100
+ ctx.messages.push({ role: "user", content: [{ type: "text", text }] });
101
+ ctx.decision = "continue";
102
+ };
103
+
104
+ if (ctx.stopReason === "refusal" && !turnHasVisibleText) {
105
+ appendNudge(REFUSAL_NUDGE_TEXT);
106
+ return;
107
+ }
108
+
109
+ const cycleMessages = currentCycleMessages(ctx.messages);
110
+ const priorAssistantTurns = cycleMessages.filter(isAssistantTurn);
111
+ const hadPriorAssistantTurn = priorAssistantTurns.length > 0;
112
+ const priorAssistantHadVisibleText = priorAssistantTurns.some((message) =>
113
+ hasVisibleText(message.content),
114
+ );
115
+
116
+ const isEmptyTurnAfterTools =
117
+ !turnHasVisibleText &&
118
+ hadPriorAssistantTurn &&
119
+ !priorAssistantHadVisibleText;
120
+
121
+ if (isEmptyTurnAfterTools) {
122
+ appendNudge(NUDGE_TEXT);
123
+ }
124
+ };
125
+
126
+ export default stop;
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Default `emptyResponse` plugin.
2
+ * Default `empty-response` plugin.
3
3
  *
4
- * The plugin's middleware is a passthrough it calls `next(args)` and returns
5
- * the result unchanged. The actual decision lives in the terminal handler in
6
- * `./terminal.ts`, which is wired in as the pipeline's `terminal` argument by
7
- * the `runPipeline` call site in `agent/loop.ts`. This separation matters: the
8
- * default plugin is registered before any user plugin (defaults load first in
9
- * `bootstrapPlugins()`), which puts it at the OUTERMOST position of the onion
10
- * chain. If the default middleware were to decide directly without calling
11
- * `next`, it would shadow every later-registered plugin. Routing through
12
- * `next(args)` lets user middleware participate normally.
4
+ * Contributes a `stop` hook that re-queries the model when a turn yields with
5
+ * no tool calls but came back empty (or as a provider refusal). The decision
6
+ * logic lives in `./hooks/stop.ts`. Defaults register before user plugins, so
7
+ * this runs at the front of the `stop` hook chain.
13
8
  */
14
9
 
15
10
  import { type Plugin } from "../../types.js";
16
- import emptyResponse from "./middlewares/emptyResponse.js";
11
+ import stop from "./hooks/stop.js";
17
12
  import pkg from "./package.json" with { type: "json" };
18
13
 
19
14
  /** Singleton plugin — the registry rejects duplicate registrations by name. */
@@ -22,7 +17,7 @@ export const defaultEmptyResponsePlugin: Plugin = {
22
17
  name: pkg.name,
23
18
  version: pkg.version,
24
19
  },
25
- middleware: {
26
- emptyResponse,
20
+ hooks: {
21
+ stop,
27
22
  },
28
23
  };
@@ -23,22 +23,14 @@
23
23
  * registration work.
24
24
  */
25
25
 
26
- import { memoryV3ShadowPlugin } from "../../memory/v3/shadow-plugin.js";
27
26
  import { registerPlugin, resetPluginRegistryForTests } from "../registry.js";
28
27
  import { type Plugin, PluginExecutionError } from "../types.js";
29
- import { defaultCircuitBreakerPlugin } from "./circuit-breaker/register.js";
30
28
  import { defaultCompactionPlugin } from "./compaction/register.js";
31
29
  import { defaultEmptyResponsePlugin } from "./empty-response/register.js";
32
30
  import { defaultHistoryRepairPlugin } from "./history-repair/register.js";
33
- import { defaultInjectorsPlugin } from "./injectors/register.js";
34
- import { defaultLlmCallPlugin } from "./llm-call/register.js";
35
- import { defaultMemoryRetrievalPlugin } from "./memory-retrieval/register.js";
36
- import { defaultOverflowReducePlugin } from "./overflow-reduce/register.js";
37
- import { defaultPersistencePlugin } from "./persistence/register.js";
31
+ import { memoryV3ShadowPlugin } from "./memory-v3-shadow/register.js";
38
32
  import { defaultTitleGeneratePlugin } from "./title-generate/register.js";
39
- import { defaultTokenEstimatePlugin } from "./token-estimate/register.js";
40
33
  import { defaultToolErrorPlugin } from "./tool-error/register.js";
41
- import { defaultToolExecutePlugin } from "./tool-execute/register.js";
42
34
  import { defaultToolResultTruncatePlugin } from "./tool-result-truncate/register.js";
43
35
 
44
36
  /**
@@ -53,19 +45,11 @@ import { defaultToolResultTruncatePlugin } from "./tool-result-truncate/register
53
45
  */
54
46
  function getAllDefaultPlugins(): readonly Plugin[] {
55
47
  return [
56
- defaultLlmCallPlugin,
57
- defaultToolExecutePlugin,
58
48
  defaultToolResultTruncatePlugin,
59
49
  defaultEmptyResponsePlugin,
60
50
  defaultToolErrorPlugin,
61
- defaultMemoryRetrievalPlugin,
62
- defaultInjectorsPlugin,
63
- defaultTokenEstimatePlugin,
64
- defaultOverflowReducePlugin,
65
51
  defaultHistoryRepairPlugin,
66
52
  defaultCompactionPlugin,
67
- defaultCircuitBreakerPlugin,
68
- defaultPersistencePlugin,
69
53
  defaultTitleGeneratePlugin,
70
54
  memoryV3ShadowPlugin,
71
55
  ];
@@ -99,7 +83,7 @@ export function registerDefaultPlugins(): void {
99
83
  * so integration tests that exercise the full agent loop have a
100
84
  * production-parity plugin stack. Use this in `beforeEach` of tests that
101
85
  * dispatch through pipelines with a terminal that assumes the default
102
- * plugin handles every op (e.g. persistence, overflowReduce).
86
+ * plugin handles every op (e.g. compaction).
103
87
  *
104
88
  * Tests that specifically need an empty registry (pipeline-unit tests, the
105
89
  * plugin-registry tests themselves) should continue to call
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Default `memoryRetrieval` post-compaction hook.
3
+ *
4
+ * After the agent loop compacts a conversation mid-turn it must re-apply the
5
+ * runtime injections compaction stripped — the NOW.md scratchpad, PKB context,
6
+ * memory-v2 static block, workspace top-level context, and Slack chronological
7
+ * snapshot — onto the compacted history before the turn continues. This hook
8
+ * is the memory system's home for that transform: it receives the message
9
+ * history plus the resolved runtime-injection options and returns the edited
10
+ * history (and the blocks it captured), with no dependency on the agent loop's
11
+ * closure state.
12
+ *
13
+ * It re-applies the runtime injections via {@link applyRuntimeInjections},
14
+ * re-tracks the memory graph's cached nodes against the re-injected history,
15
+ * and converts now-historical `web_search_tool_result` blocks to text so their
16
+ * expired `encrypted_content` tokens are not replayed. The remaining
17
+ * orchestrator-side step (the post-injection bookkeeping the loop records) is
18
+ * expected to migrate here as the hook subsumes the loop's re-injection
19
+ * ceremony.
20
+ *
21
+ * The memory graph handle is sourced internally from the plugin's own
22
+ * conversation-keyed registry ({@link getLiveGraphMemory}) rather than being
23
+ * threaded in by the loop — it is memory-retrieval-specific state, not
24
+ * something the generic loop or the shared {@link TurnContext} should carry.
25
+ */
26
+
27
+ import {
28
+ applyRuntimeInjections,
29
+ type RuntimeInjectionOptions,
30
+ type RuntimeInjectionResult,
31
+ } from "../../../../daemon/conversation-runtime-assembly.js";
32
+ import { resolveTrustClass } from "../../../../daemon/trust-context.js";
33
+ import { stripHistoricalWebSearchResults } from "../../../../daemon/web-search-history.js";
34
+ import { getLiveGraphMemory } from "../../../../memory/graph/conversation-graph-memory.js";
35
+ import type { PluginLogger } from "../../../../plugin-api/types.js";
36
+ import type { Message } from "../../../../providers/types.js";
37
+ import type { TurnContext } from "../../../types.js";
38
+
39
+ /**
40
+ * The slice of the hook's context the agent loop supplies from its own working
41
+ * state. Re-injection inputs migrate loop-ward by growing this type; the loop
42
+ * hands the hook an object of this shape via
43
+ * {@link MidLoopCompaction.postCompactionHook}.
44
+ */
45
+ export interface PostCompactionHookInput {
46
+ /** Compacted message history to re-inject onto. */
47
+ history: Message[];
48
+ /** Per-turn conversation context forwarded to the injector chain. */
49
+ turnContext?: TurnContext;
50
+ }
51
+
52
+ /**
53
+ * Everything the hook needs in a single context: the loop-supplied
54
+ * {@link PostCompactionHookInput}, the resolved {@link RuntimeInjectionOptions}
55
+ * (spread top-level so each field stays individually addressable), and a
56
+ * turn-scoped logger. The memory graph handle is not part of this context —
57
+ * the hook sources it internally via {@link getLiveGraphMemory} — and the
58
+ * actor's trust class is derived from {@link PostCompactionHookInput.turnContext}
59
+ * rather than threaded in.
60
+ */
61
+ export interface PostCompactContext
62
+ extends RuntimeInjectionOptions, PostCompactionHookInput {
63
+ /** Turn-scoped logger for diagnostics emitted while re-injecting. */
64
+ logger: PluginLogger;
65
+ }
66
+
67
+ export default async function postCompactReinject(
68
+ ctx: PostCompactContext,
69
+ ): Promise<RuntimeInjectionResult> {
70
+ const { history, logger, ...options } = ctx;
71
+ const result = await applyRuntimeInjections(history, options);
72
+ // Re-track the nodes the memory graph last injected so they survive against
73
+ // the re-injected history. Untrusted actors and minimal-mode turns never
74
+ // received a memory-graph injection, so there is nothing to re-track. The
75
+ // actor's trust class is derived from the turn's own trust context (the same
76
+ // value the injector chain resolves), not threaded in from the loop. The
77
+ // live graph handle is looked up from the plugin's own registry by the
78
+ // turn's conversation id — the same instance the turn's retrieval mutated,
79
+ // so re-tracking sees the real cached-node state.
80
+ const isTrustedActor =
81
+ resolveTrustClass(options.turnContext?.trust) === "guardian";
82
+ if (isTrustedActor && options.mode !== "minimal") {
83
+ getLiveGraphMemory(
84
+ options.turnContext?.conversationId,
85
+ )?.retrackCachedNodes();
86
+ }
87
+ const strip = stripHistoricalWebSearchResults(result.messages);
88
+ if (strip.stats.blocksStripped > 0) {
89
+ logger.info(
90
+ { phase: "mid-loop-compact", ...strip.stats },
91
+ "Converted historical web_search_tool_result blocks to text summaries",
92
+ );
93
+ }
94
+ return { ...result, messages: strip.messages };
95
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Default `user-prompt-submit-temp` hook: runs the memory-graph retrieval the
3
+ * agent loop needs before building a turn's runtime-injection block.
4
+ *
5
+ * **Memory graph** via {@link ConversationGraphMemory.prepareMemory} —
6
+ * dispatches to context-load or per-turn retrieval depending on initialization
7
+ * state; gated on the actor being trusted (guardian).
8
+ *
9
+ * The hook also owns the retrieval's side effects — persisting the injected
10
+ * block onto the user message's metadata, writing the recall log, and emitting
11
+ * the `memory_recalled` event — so the loop only consumes the turn-scoped
12
+ * `latestMessages` written back onto the context. The PKB query-vector pair is
13
+ * recorded on the conversation's graph handle for the PKB-reminder injector to
14
+ * read back. PKB context and NOW.md are sourced directly by their injectors
15
+ * (gated on block presence), not produced here.
16
+ *
17
+ * This fires at the early "prompt submitted, before context assembly" moment,
18
+ * distinct from the canonical late `user-prompt-submit` hook (history repair,
19
+ * title): memory's outputs feed the injection and overflow-reduction transforms
20
+ * that run between the two moments. The `-temp` suffix marks this as a
21
+ * transitional staging point that folds into `user-prompt-submit` once
22
+ * compaction is cleared from the gap between the two call sites.
23
+ */
24
+
25
+ import type { PluginHookFn } from "@vellumai/plugin-api";
26
+ import type { Logger } from "pino";
27
+
28
+ import type { AssistantConfig } from "../../../../config/schema.js";
29
+ import type { ServerMessage } from "../../../../daemon/message-protocol.js";
30
+ import type { MemoryRecalled } from "../../../../daemon/message-types/memory.js";
31
+ import { updateMessageMetadata } from "../../../../memory/conversation-crud.js";
32
+ import type { ConversationGraphMemory } from "../../../../memory/graph/conversation-graph-memory.js";
33
+ import { recordMemoryRecallLog } from "../../../../memory/memory-recall-log-store.js";
34
+ import type { Message } from "../../../../providers/types.js";
35
+ import type { GraphMemoryResult } from "../../../types.js";
36
+
37
+ /**
38
+ * Context threaded through the `user-prompt-submit-temp` hook. The readonly
39
+ * fields carry the conversation-scoped state the retriever needs (graph
40
+ * handle, event sink, abort signal); the output fields are populated by the
41
+ * hook and read back by the agent loop. `latestMessages` straddles both: the
42
+ * loop seeds it with the pre-injection array and the hook overwrites it with
43
+ * the injected result.
44
+ */
45
+ export interface MemoryRetrievalHookContext {
46
+ /** Per-conversation memory graph handle. */
47
+ readonly graphMemory: ConversationGraphMemory;
48
+ /** Assistant config snapshot. */
49
+ readonly config: AssistantConfig;
50
+ /** Event sink used by the graph retriever and `memory_recalled` emission. */
51
+ readonly onEvent: (msg: ServerMessage) => void;
52
+ /** True when the actor for this turn is trusted (guardian-class). */
53
+ readonly isTrustedActor: boolean;
54
+ /** Conversation the turn belongs to — keys the recall-log row. */
55
+ readonly conversationId: string;
56
+ /** User message the injected memory block is persisted onto. */
57
+ readonly userMessageId: string;
58
+ /** Turn-scoped logger for non-fatal persistence warnings. */
59
+ readonly logger: Logger;
60
+ /**
61
+ * Per-turn abort signal forwarded to `prepareMemory`. An external cancel
62
+ * aborts the underlying retrieval instead of letting it run to completion.
63
+ */
64
+ readonly signal: AbortSignal;
65
+ /**
66
+ * Working message list for the turn. Seeded by the loop with the
67
+ * pre-injection messages and consumed as the retrieval input; the hook
68
+ * overwrites it with the memory-graph block injected, or leaves it
69
+ * unchanged when no graph retrieval ran (untrusted actor, or a no-op
70
+ * retrieval). Read back by the loop.
71
+ */
72
+ latestMessages: Message[];
73
+ }
74
+
75
+ /**
76
+ * Persist and broadcast the retrieval's side effects: the injected block on
77
+ * the user message's metadata (so it survives reloads), a recall-log row, and
78
+ * the `memory_recalled` debug event. All three are best-effort — a failure to
79
+ * persist must not abort the turn.
80
+ */
81
+ function recordRecallSideEffects(
82
+ graphResult: GraphMemoryResult,
83
+ ctx: MemoryRetrievalHookContext,
84
+ ): void {
85
+ // Persist the injected block text in message metadata so it survives
86
+ // conversation reloads (eviction, restart, fork). loadFromDb re-injects
87
+ // from metadata.
88
+ if (graphResult.injectedBlockText) {
89
+ try {
90
+ updateMessageMetadata(ctx.userMessageId, {
91
+ memoryInjectedBlock: graphResult.injectedBlockText,
92
+ });
93
+ } catch (err) {
94
+ ctx.logger.warn(
95
+ { err },
96
+ "Failed to persist memory injection to metadata (non-fatal)",
97
+ );
98
+ }
99
+ }
100
+
101
+ const m = graphResult.metrics;
102
+
103
+ try {
104
+ recordMemoryRecallLog({
105
+ conversationId: ctx.conversationId,
106
+ enabled: true,
107
+ degraded: false,
108
+ provider: m?.embeddingProvider ?? undefined,
109
+ model: m?.embeddingModel ?? undefined,
110
+ semanticHits: m?.semanticHits ?? 0,
111
+ mergedCount: m?.mergedCount ?? 0,
112
+ selectedCount: m?.selectedCount ?? 0,
113
+ tier1Count: m?.tier1Count ?? 0,
114
+ tier2Count: m?.tier2Count ?? 0,
115
+ hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
116
+ sparseVectorUsed: m?.sparseVectorUsed ?? false,
117
+ injectedTokens: graphResult.injectedTokens,
118
+ latencyMs: graphResult.latencyMs,
119
+ topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
120
+ key: c.nodeId,
121
+ type: c.type,
122
+ kind: "graph",
123
+ finalScore: c.score,
124
+ semantic: c.semanticSimilarity,
125
+ recency: c.recencyBoost,
126
+ })),
127
+ injectedText: graphResult.injectedBlockText ?? undefined,
128
+ reason: `graph:${graphResult.mode}`,
129
+ queryContext: m?.queryContext ?? undefined,
130
+ });
131
+ } catch (err) {
132
+ ctx.logger.warn({ err }, "Failed to persist memory recall log (non-fatal)");
133
+ }
134
+
135
+ if (m) {
136
+ const memoryRecalledEvent: MemoryRecalled = {
137
+ type: "memory_recalled",
138
+ provider: m.embeddingProvider ?? "unknown",
139
+ model: m.embeddingModel ?? "unknown",
140
+ semanticHits: m.semanticHits,
141
+ mergedCount: m.mergedCount,
142
+ selectedCount: m.selectedCount,
143
+ tier1Count: m.tier1Count,
144
+ tier2Count: m.tier2Count,
145
+ hybridSearchLatencyMs: m.hybridSearchLatencyMs,
146
+ sparseVectorUsed: m.sparseVectorUsed,
147
+ injectedTokens: graphResult.injectedTokens,
148
+ latencyMs: graphResult.latencyMs,
149
+ topCandidates: m.topCandidates.map((c) => ({
150
+ key: c.nodeId,
151
+ type: c.type,
152
+ kind: "graph",
153
+ finalScore: c.score,
154
+ semantic: c.semanticSimilarity,
155
+ recency: c.recencyBoost,
156
+ })),
157
+ };
158
+ ctx.onEvent(memoryRecalledEvent);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Run the default retrieval, writing `latestMessages` back onto the context and
164
+ * recording the PKB query-vector pair on the graph handle. Skips the
165
+ * memory-graph call entirely (leaving `latestMessages` as the seeded input
166
+ * messages and no query pair recorded) when the actor is not trusted.
167
+ *
168
+ * Memory retrieval blocks the turn — there is no soft timeout here. Memory is
169
+ * critical context, and silently dropping it produces a worse outcome than a
170
+ * slower turn. Cancellation still works via `ctx.signal`, which is threaded
171
+ * into `prepareMemory`.
172
+ */
173
+ const userPromptSubmitMemoryRetrieval: PluginHookFn<
174
+ MemoryRetrievalHookContext
175
+ > = async (ctx) => {
176
+ if (!ctx.isTrustedActor) {
177
+ // Untrusted actors skip memory-graph retrieval entirely.
178
+ return;
179
+ }
180
+
181
+ const graphResult = await ctx.graphMemory.prepareMemory(
182
+ ctx.latestMessages,
183
+ ctx.config,
184
+ ctx.signal,
185
+ ctx.onEvent,
186
+ );
187
+
188
+ recordRecallSideEffects(graphResult, ctx);
189
+
190
+ ctx.latestMessages = graphResult.runMessages;
191
+ // Select dense+sparse as a matched pair so RRF fusion combines two signals
192
+ // aligned to the same query text:
193
+ // 1. Context-load with a user query: user-query dense + user-query sparse
194
+ // — the cleanest pairing.
195
+ // 2. Otherwise (context-load without a user query, or per-turn): whatever
196
+ // `queryVector` / `sparseVector` the retriever produced, which are
197
+ // themselves co-aligned (both summary-derived in context-load, both
198
+ // user-last-message-derived in per-turn).
199
+ // Never pair a user-query dense with a summary-aligned sparse.
200
+ // The PKB-reminder injector reads this pair back off the same graph handle
201
+ // (looked up by conversation id) rather than receiving it threaded through
202
+ // the agent loop.
203
+ if (graphResult.userQueryVector) {
204
+ ctx.graphMemory.recordPkbQueryVectors(
205
+ graphResult.userQueryVector,
206
+ graphResult.userQuerySparseVector,
207
+ );
208
+ } else {
209
+ ctx.graphMemory.recordPkbQueryVectors(
210
+ graphResult.queryVector,
211
+ graphResult.sparseVector,
212
+ );
213
+ }
214
+ };
215
+
216
+ export default userPromptSubmitMemoryRetrieval;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The assembled runtime injector chain.
3
+ *
4
+ * Injection is not a plugin contribution: the first-party injectors are
5
+ * imported directly and sorted once, by ascending `order`, into the single
6
+ * sequence `applyRuntimeInjections` walks each turn. This co-locates the
7
+ * chain with the memory-retrieval domain it serves, rather than aggregating
8
+ * injectors out of the plugin registry.
9
+ *
10
+ * The chain combines the default injectors ({@link defaultInjectors}) with the
11
+ * memory-v3 injector ({@link memoryV3Injector}). The sort mirrors the previous
12
+ * registry aggregation (`Array.prototype.sort` is stable, so injectors sharing
13
+ * an `order` keep their listed order), so the produced sequence — and therefore
14
+ * the injected content — is identical.
15
+ *
16
+ * The chain is assembled lazily on first access and memoized, so the sort runs
17
+ * once per process rather than per turn and module evaluation stays free of any
18
+ * ordering assumptions about when `defaultInjectors` finishes initializing.
19
+ */
20
+
21
+ import type { Injector } from "../../types.js";
22
+ import { memoryV3Injector } from "../memory-v3-shadow/injector.js";
23
+ import { defaultInjectors } from "./injectors.js";
24
+
25
+ let cachedChain: Injector[] | null = null;
26
+
27
+ /** The order-sorted runtime injector chain, assembled once and memoized. */
28
+ export function getInjectorChain(): Injector[] {
29
+ if (cachedChain === null) {
30
+ cachedChain = [...defaultInjectors, memoryV3Injector].sort(
31
+ (a, b) => a.order - b.order,
32
+ );
33
+ }
34
+ return cachedChain;
35
+ }