@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -9,7 +9,7 @@
9
9
  * injection is bit-for-bit identical — the only difference is the
10
10
  * side-effect telemetry write.
11
11
  * - `memory-v3-live`: live injection. The injector (`memoryV3Injector` in
12
- * `./injector.ts`) additionally renders the working-set selection into a
12
+ * `./injector.ts`) additionally renders this turn's selections into a
13
13
  * `<memory>` block and returns it at v2's dynamic-memory placement
14
14
  * (`after-memory-prefix`). Selections are still logged.
15
15
  * - both OFF: orchestration is skipped entirely.
@@ -17,8 +17,8 @@
17
17
  * On each turn (either flag on):
18
18
  * 1. Lazy-init the v3 lanes ONCE across the whole process (section index,
19
19
  * section-grain BM25 needle, dense lane config, link-graph edge graph,
20
- * carry-forward working set), memoizing the init promise so concurrent
21
- * first turns share a single build.
20
+ * curated core set, frecency hot set), memoizing the init promise so
21
+ * concurrent first turns share a single build.
22
22
  * 2. Build a {@link MemoryRoutingTurn} from the conversation's recent messages.
23
23
  * 3. Run {@link orchestrate} and record its selection set to
24
24
  * `memory_v3_selections` with a best-effort lane attribution.
@@ -46,8 +46,11 @@ import {
46
46
  } from "../../../util/platform.js";
47
47
  import { stripCommentLines } from "../../../util/strip-comment-lines.js";
48
48
  import { capabilityOrDiskBody } from "./capabilities.js";
49
+ import { renderCard } from "./card.js";
50
+ import { loadCoreSet } from "./core-set.js";
49
51
  import type { EdgeGraph } from "./edge.js";
50
52
  import { buildEdgeGraph } from "./edge.js";
53
+ import { computeHotSet } from "./hot-set.js";
51
54
  import type { OrchestrateResult } from "./orchestrate.js";
52
55
  import { orchestrate } from "./orchestrate.js";
53
56
  import { ensureSectionCollection } from "./section-dense-store.js";
@@ -60,7 +63,6 @@ import {
60
63
  type SelectionSource,
61
64
  type Slug,
62
65
  } from "./types.js";
63
- import { WorkingSet } from "./working-set.js";
64
66
 
65
67
  export const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
66
68
  export const MEMORY_V3_LIVE = "memory-v3-live" as const;
@@ -71,10 +73,12 @@ const log = getLogger("memory-v3-shadow");
71
73
  const RECENT_CONTEXT_MESSAGES = 6;
72
74
 
73
75
  /**
74
- * The lazily-built, process-lifetime v3 lanes. The working set is stateful
75
- * (carry-forward across turns), so it is intentionally shared across every
76
- * shadow turn rather than rebuilt per turn — that is the whole point of the
77
- * carry-forward lane.
76
+ * The lazily-built, process-lifetime v3 lanes. The core and hot sets are
77
+ * computed here (not per turn) because they are the candidate pool's STABLE
78
+ * PREFIX — recomputing them mid-conversation would reorder the prefix and bust
79
+ * the selector's KV cache. Lane memoization is the recompute cadence:
80
+ * `invalidateLanes()` (called by the maintain job at consolidation) forces a
81
+ * rebuild on the next turn.
78
82
  */
79
83
  export interface ShadowLanes {
80
84
  sectionIndex: SectionIndex;
@@ -83,9 +87,20 @@ export interface ShadowLanes {
83
87
  * collection. */
84
88
  denseConfig: AssistantConfig;
85
89
  edgeGraph: EdgeGraph;
86
- workingSet: WorkingSet;
90
+ /** Curated core set in file order, filtered to pages in the section index. */
91
+ coreSlugs: string[];
92
+ /** Frecency hot set in score order: core excluded, filtered to pages in the
93
+ * section index. */
94
+ hotSlugs: string[];
95
+ /** Pre-rendered FULL cards for the stable-prefix (core+hot) slugs, keyed by
96
+ * slug. Frozen at lane build so the selector's stable prefix is
97
+ * byte-identical across turns until the next invalidation. */
98
+ prefixCards: Map<Slug, string>;
87
99
  }
88
100
 
101
+ /** Milliseconds per day — converts `hotSet.halfLifeDays` config to ms. */
102
+ const DAY_MS = 24 * 60 * 60 * 1000;
103
+
89
104
  /**
90
105
  * Memoized init promise. Caching the PROMISE (not the resolved value) means
91
106
  * concurrent first turns all await the same build instead of racing several
@@ -151,11 +166,49 @@ async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
151
166
 
152
167
  const sectionIndex = await buildSectionIndex(slugs, pageBody);
153
168
  const needle = buildSectionNeedle(sectionIndex);
169
+
170
+ // The stable-prefix lanes. Core is the maintainer-curated file (file order
171
+ // preserved — it is the prefix's stable sort), filtered to pages that exist
172
+ // in the live section index so a dangling entry can never reach the pool.
173
+ // Hot is the frecency top-K over `memory_v3_selections` with core excluded
174
+ // (hot never duplicates core), filtered the same way — selection rows can
175
+ // outlive their pages. Both are recomputed only on lane invalidation (the
176
+ // consolidation cadence), keeping the prefix stable between rebuilds.
177
+ const coreSlugs = loadCoreSet(getWorkspaceDir()).filter((slug) =>
178
+ sectionIndex.byArticle.has(slug),
179
+ );
180
+ const hotSlugs = computeHotSet(
181
+ { db: getDb() },
182
+ {
183
+ k: config.memory.v3.hotSet.k,
184
+ halfLifeMs: config.memory.v3.hotSet.halfLifeDays * DAY_MS,
185
+ now: Date.now(),
186
+ excludeSlugs: new Set(coreSlugs),
187
+ },
188
+ )
189
+ .map((entry) => entry.slug)
190
+ .filter((slug) => sectionIndex.byArticle.has(slug));
191
+
192
+ // Pre-render the stable-prefix cards ONCE per lane build: the selector's
193
+ // stable prefix must be byte-identical across turns to ride the provider KV
194
+ // cache, so the cards are frozen here (lane invalidation at consolidation is
195
+ // the recompute point) instead of being re-read per turn. Capability slugs
196
+ // render their capability content; disk pages render raw (frontmatter +
197
+ // body) so `kind: index` pages surface their `links:` map in the card TOC.
198
+ const prefixCards = new Map<Slug, string>();
199
+ for (const slug of [...coreSlugs, ...hotSlugs]) {
200
+ const raw = await capabilityOrDiskBody(
201
+ slug,
202
+ async (s) => (await loadPage(s))?.raw ?? "",
203
+ );
204
+ prefixCards.set(slug, renderCard(slug, raw));
205
+ }
206
+
154
207
  const edgeGraph = await buildEdgeGraph(pageIndex.entries, pageRaw, {
155
208
  hubDegree: config.memory.v3.edge.hubDegree,
156
209
  });
157
210
  // Ensuring the dense collection is best-effort: the needle + edge lanes and
158
- // carry-forward are in-memory and independent of Qdrant, so a Qdrant outage
211
+ // the core/hot prefix are in-memory and independent of Qdrant, so a Qdrant outage
159
212
  // must NOT reject lane init (which would return `null` from observeTurn and
160
213
  // disable ALL of v3, plus poison the memoized lanes until invalidation). On
161
214
  // failure we log and continue with the dense lane degraded — denseLane already
@@ -170,17 +223,14 @@ async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
170
223
  );
171
224
  }
172
225
 
173
- const workingSet = new WorkingSet(
174
- config.memory.v3.workingSet.maxPages,
175
- config.memory.v3.workingSet.evictWindow,
176
- );
177
-
178
226
  return {
179
227
  sectionIndex,
180
228
  needle,
181
229
  denseConfig: config,
182
230
  edgeGraph,
183
- workingSet,
231
+ coreSlugs,
232
+ hotSlugs,
233
+ prefixCards,
184
234
  };
185
235
  }
186
236
 
@@ -273,33 +323,31 @@ interface SelectionRow {
273
323
  }
274
324
 
275
325
  /**
276
- * Map an orchestrate result onto telemetry rows with precise per-lane source
277
- * attribution.
278
- *
279
- * - A current selection is tagged with the candgen lane that FIRST surfaced its
280
- * page this turn — `result.laneBySlug` (`"needle"` / `"dense"` / `"edge"`),
281
- * recorded at pool-build time. (`"needle"` is the fallback if a selected slug
282
- * is somehow absent from the lane map, which should not happen since every
283
- * pooled candidate is recorded there.)
284
- * - A slug in `finalInjection` but NOT re-selected this turn → `"carry-forward"`.
326
+ * Map an orchestrate result onto telemetry rows with per-lane source
327
+ * attribution, by pool position: a selection of a stable-prefix page is
328
+ * attributed `"core"` / `"hot"` (the lane that placed it in the pool), and any
329
+ * other selection is attributed the finder lane that FIRST surfaced it
330
+ * (`"needle"` / `"dense"` / `"edge"`, recorded at pool-build time). A finder
331
+ * hit on a core/hot page therefore still logs as core/hot — the prefix is
332
+ * where the candidate lived. (`"needle"` is the fallback if a selected slug is
333
+ * somehow absent from every lane, which should not happen since every pooled
334
+ * candidate comes from one.)
285
335
  */
286
336
  export function attributeSelections(result: OrchestrateResult): SelectionRow[] {
287
- const rows: SelectionRow[] = [];
288
- const seen = new Set<Slug>();
289
- for (const sel of result.currentSelections) {
290
- seen.add(sel.slug);
291
- rows.push({
292
- slug: sel.slug,
293
- source: result.laneBySlug.get(sel.slug) ?? "needle",
294
- pinned: sel.pinned ? 1 : 0,
295
- });
296
- }
297
- for (const slug of result.finalInjection) {
298
- if (seen.has(slug)) continue;
299
- seen.add(slug);
300
- rows.push({ slug, source: "carry-forward", pinned: 0 });
301
- }
302
- return rows;
337
+ const core = new Set<Slug>(result.lanes.core);
338
+ const hot = new Set<Slug>(result.lanes.hot);
339
+ const finderLane = new Map(
340
+ result.lanes.finder.map((c) => [c.slug, c.lane] as const),
341
+ );
342
+ return result.selections.map((sel) => ({
343
+ slug: sel.slug,
344
+ source: core.has(sel.slug)
345
+ ? ("core" as const)
346
+ : hot.has(sel.slug)
347
+ ? ("hot" as const)
348
+ : (finderLane.get(sel.slug) ?? "needle"),
349
+ pinned: sel.pinned ? 1 : 0,
350
+ }));
303
351
  }
304
352
 
305
353
  /** Write the attributed selection rows to `memory_v3_selections`. */
@@ -338,6 +386,7 @@ export async function observeTurn(
338
386
  if (!turn) return null;
339
387
 
340
388
  const cfg = getConfig();
389
+ if (cfg.memory.enabled === false) return null;
341
390
  const lanes = await getLanes(cfg);
342
391
  const v3 = cfg.memory.v3;
343
392
  const result = await orchestrate(turn, {
@@ -345,7 +394,9 @@ export async function observeTurn(
345
394
  needle: lanes.needle,
346
395
  denseConfig: lanes.denseConfig,
347
396
  edgeGraph: lanes.edgeGraph,
348
- workingSet: lanes.workingSet,
397
+ coreSlugs: lanes.coreSlugs,
398
+ hotSlugs: lanes.hotSlugs,
399
+ prefixCards: lanes.prefixCards,
349
400
  needleK: v3.needleK,
350
401
  denseK: v3.denseK,
351
402
  edgeSeeds: v3.edge.seedCount,
@@ -377,6 +428,8 @@ export async function runShadowObservation(
377
428
  conversationId: string,
378
429
  turnIndex: number,
379
430
  ): Promise<void> {
380
- if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, getConfig())) return;
431
+ const config = getConfig();
432
+ if (config.memory.enabled === false) return;
433
+ if (!isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config)) return;
381
434
  await observeTurn(conversationId, turnIndex);
382
435
  }
@@ -10,6 +10,26 @@ export type Slug = string;
10
10
  */
11
11
  export const MEMORY_V3_BLOCK_ID = "memory-v3" as const;
12
12
 
13
+ /**
14
+ * `meta` key under which the v3 cards block carries its attachment-commit
15
+ * callback. The injector defers its everInjected-store write (and the
16
+ * prune-valve schedule) into this callback; runtime assembly invokes it only
17
+ * when the turn's tail is a user message — the same gate as metadata capture
18
+ * — so a block that silently fails to attach never claims its cards in the
19
+ * dedup store. Shared between the producer (`injector.ts`) and the consumer
20
+ * (`conversation-runtime-assembly.ts`) so a rename is a compile error on both
21
+ * sides instead of a silent never-commit.
22
+ */
23
+ export const MEMORY_V3_COMMIT_META_KEY = "memoryV3Commit" as const;
24
+
25
+ /**
26
+ * Injection-block id for the v3 ephemeral `<memory_spotlight>` block (the
27
+ * current window's matched sections, re-rendered at the user tail each turn).
28
+ * Distinct from {@link MEMORY_V3_BLOCK_ID}: the spotlight never participates
29
+ * in v2 suppression and is never persisted to message metadata.
30
+ */
31
+ export const MEMORY_V3_SPOTLIGHT_BLOCK_ID = "memory-v3-spotlight" as const;
32
+
13
33
  /**
14
34
  * A single section of a page: the lead (text before the first `## heading`,
15
35
  * ordinal 0) or a heading-delimited block. Over-long sections are split into
@@ -40,13 +60,6 @@ export interface SelectedPage {
40
60
  pinned: boolean;
41
61
  }
42
62
 
43
- export interface WorkingSetEntry {
44
- slug: Slug;
45
- selectedAtTurn: number;
46
- pinned: boolean;
47
- lastSeenTurn: number;
48
- }
49
-
50
63
  export interface MemoryRoutingTurn {
51
64
  conversationId: string;
52
65
  turnNumber: number;
@@ -68,25 +81,29 @@ export interface MemoryRoutingTurn {
68
81
  * exactly one place and the runtime list (used for telemetry roll-ups and
69
82
  * source validation) can never drift from the type.
70
83
  *
84
+ * `core` / `hot` are the stable-prefix lanes (curated core set, frecency hot
85
+ * set); `needle` / `dense` / `edge` are the per-turn finder lanes.
86
+ *
71
87
  * The `memory_v3_selections.source` column is free-text, so tightening this set
72
- * needs no migration: any historical rows with older labels still read back
73
- * fine via the permissive `z.string()` row schema.
88
+ * needs no migration: any historical rows with retired labels (e.g. the old
89
+ * per-turn carry source) still read back fine via the permissive `z.string()`
90
+ * row schema — they just don't aggregate into a named bucket.
74
91
  */
75
92
  export const SELECTION_SOURCES = [
93
+ "core",
94
+ "hot",
76
95
  "needle",
77
96
  "dense",
78
97
  "edge",
79
- "carry-forward",
80
98
  ] as const;
81
99
 
82
100
  export type SelectionSource = (typeof SELECTION_SOURCES)[number];
83
101
 
84
102
  /**
85
- * The candidate-generation lanes — the strict subset of {@link SelectionSource}
86
- * a pooled candidate can be tagged with at pool-build time. (`carry-forward` is
87
- * the one source assigned later, by the orchestrator's working-set union, not by
88
- * a candgen lane.) Defined as `Exclude<SelectionSource, "carry-forward">` so it
89
- * can never drift from {@link SELECTION_SOURCES}: adding a lane there widens this
90
- * automatically.
103
+ * The per-turn finder lanes — the strict subset of {@link SelectionSource} a
104
+ * finder candidate can be tagged with at pool-build time. (`core` / `hot` are
105
+ * assigned by stable-prefix membership, not by a finder.) Defined via
106
+ * `Exclude` so it can never drift from {@link SELECTION_SOURCES}: adding a
107
+ * finder lane there widens this automatically.
91
108
  */
92
- export type CandidateLane = Exclude<SelectionSource, "carry-forward">;
109
+ export type FinderLane = Exclude<SelectionSource, "core" | "hot">;
@@ -50,23 +50,21 @@ function countUserTurns(messages: ReadonlyArray<Message>): number {
50
50
  }
51
51
 
52
52
  const stop: PluginHookFn<StopContext> = async (ctx) => {
53
- // Only re-title at a genuine turn end. A `"continue"` decision means an
54
- // earlier hook is re-querying the model (e.g. an empty-response nudge), so
55
- // defer to the eventual terminal stop.
56
- if (ctx.decision !== "stop") return;
53
+ // Re-title only at a genuine successful turn end (the model returned a reply
54
+ // with no tool calls). Any other terminal — a provider rejection, abort, or
55
+ // an output-limit cutoff — produced no new topic to re-title from.
56
+ if (ctx.exitReason !== "no_tool_calls") return;
57
57
 
58
58
  if (getConfig().conversations.skipAutoRetitling) return;
59
59
 
60
60
  if (countUserTurns(ctx.messages) !== SECOND_PASS_USER_TURN) return;
61
61
 
62
62
  const { conversationId } = ctx;
63
- // Deferred to a later macrotask so the just-completed turn lands first. The
64
- // hook fires at the stop boundary, before the loop appends the turn's
65
- // assistant reply to history and emits `message_complete` (which persists
66
- // it). The service regenerates from the most recent stored messages, so it
67
- // must run after the reply is persisted to reflect it. The service is itself
68
- // fire-and-forget and re-checks replaceability, owning provider resolution,
69
- // persistence, and the resulting broadcast.
63
+ // Deferred to a later macrotask so the just-completed turn's persistence
64
+ // settles first. The service regenerates from the most recent stored
65
+ // messages, so it must run after the reply is persisted to reflect it. The
66
+ // service is itself fire-and-forget and re-checks replaceability, owning
67
+ // provider resolution, persistence, and the resulting broadcast.
70
68
  setTimeout(() => {
71
69
  queueRegenerateConversationTitle({ conversationId });
72
70
  }, 0);
@@ -4,7 +4,9 @@
4
4
  * A "hook" is a named lifecycle event (`user-prompt-submit`, `post-tool-use`,
5
5
  * …) that every registered plugin may handle. The runner walks each plugin's
6
6
  * hook for a given event in registration order, threading a context value
7
- * through the chain so hooks can observe and transform it.
7
+ * through the chain so hooks can observe and transform it. A hook either
8
+ * mutates the context in place (returning `void`) or returns a partial
9
+ * context whose fields are merged onto the threaded value.
8
10
  *
9
11
  * Design doc: `.private/plans/agent-plugin-system.md`.
10
12
  */
@@ -17,9 +19,10 @@ import { getHooksFor } from "./registry.js";
17
19
  /**
18
20
  * Execute a hook chain: walk every registered plugin's hook for `name` in
19
21
  * registration order, threading `initialCtx` through each. Hooks may either
20
- * mutate the context in place (returning `void`) or return a new context
21
- * that replaces the threaded value for subsequent hooks. The final context
22
- * after the chain settles is returned.
22
+ * mutate the context in place (returning `void`) or return a partial context
23
+ * whose fields are merged onto the threaded value — keys the hook returns
24
+ * overwrite the running context, every other field is preserved. The final
25
+ * context after the chain settles is returned.
23
26
  *
24
27
  * @param name The hook identifier — pick one from {@link HOOKS}.
25
28
  * @param initialCtx Context the first hook receives.
@@ -35,7 +38,7 @@ export async function runHook<TCtx>(
35
38
  for (const hook of getHooksFor<TCtx>(name)) {
36
39
  const result = await hook(active);
37
40
  if (result !== undefined) {
38
- active = result;
41
+ active = { ...active, ...result };
39
42
  }
40
43
  }
41
44
  return active;
@@ -317,12 +317,11 @@ export interface Injector {
317
317
  }
318
318
 
319
319
  // ─── Model-visible capability slots ──────────────────────────────────────────
320
- // Concrete shapes are defined by the tool/route/skill registries. Tool
321
- // contributions (PR 31) use the canonical `Tool` interface; route
322
- // contributions (PR 32) use the `SkillRoute` shape from the skill-route
323
- // registry; skill contributions (PR 33) ship with the concrete
324
- // `PluginSkillRegistration` shape below so plugins can declare
325
- // catalog-discoverable skills today.
320
+ // Concrete shapes are defined by the tool/route registries. Tool
321
+ // contributions use the canonical `Tool` interface; route contributions use
322
+ // the `SkillRoute` shape from the skill-route registry. Skills ship on disk
323
+ // inside an installed plugin (`plugins/<name>/skills/<id>/SKILL.md`) and are
324
+ // discovered by the skill catalog loader, so they need no contribution slot.
326
325
 
327
326
  /**
328
327
  * HTTP route registration contributed by a plugin. Plugins express routes as
@@ -336,49 +335,6 @@ export interface Injector {
336
335
  */
337
336
  export type PluginRouteRegistration = SkillRoute;
338
337
 
339
- /**
340
- * A skill contributed by a plugin.
341
- *
342
- * When a plugin declares {@link Plugin.skills}, the bootstrap registers each
343
- * entry into an in-memory side catalog that {@link loadSkillCatalog} merges
344
- * into its output. The entry is then discoverable by the model's `skill_load`
345
- * / `skill_execute` flow under `source: "plugin"` — the same code paths used
346
- * for filesystem-backed skills.
347
- *
348
- * The fields mirror the subset of `SkillSummary` / `SkillDefinition` that
349
- * makes sense for an in-memory contribution. Inline commands and reference
350
- * files are out of scope for plugin skills in this PR — add them later if a
351
- * real plugin needs them.
352
- */
353
- export interface PluginSkillRegistration {
354
- /** Stable skill id (kebab-case). Must be unique across the catalog. */
355
- id: string;
356
- /**
357
- * Skill "name" as surfaced to the model. Matches the SKILL.md frontmatter
358
- * `name` field for filesystem skills.
359
- */
360
- name: string;
361
- /**
362
- * Human-readable display name shown in UI lists. Defaults to `name` when
363
- * omitted — matches the filesystem-skill default.
364
- */
365
- displayName?: string;
366
- /** One-line description shown by `skill_load` / UI. */
367
- description: string;
368
- /** Full skill body returned when `skill_load` fires for this skill. */
369
- body: string;
370
- /** Optional emoji shown beside the skill in UI surfaces. */
371
- emoji?: string;
372
- /** Optional assistant feature-flag key — when set and the flag is OFF, the skill is filtered out. */
373
- featureFlag?: string;
374
- /** Compact routing cues injected into `<available_skills>` to guide selection. */
375
- activationHints?: string[];
376
- /** Conditions under which this skill should NOT be loaded. */
377
- avoidWhen?: string[];
378
- /** IDs of child skills that this skill includes (metadata-only, not auto-activated). */
379
- includes?: string[];
380
- }
381
-
382
338
  // ─── Plugin ──────────────────────────────────────────────────────────────────
383
339
 
384
340
  /**
@@ -420,8 +376,6 @@ export interface Plugin {
420
376
  tools?: Tool[];
421
377
  /** HTTP route registrations served by the assistant. */
422
378
  routes?: PluginRouteRegistration[];
423
- /** Skill registrations loaded at startup. */
424
- skills?: PluginSkillRegistration[];
425
379
  }
426
380
 
427
381
  // ─── Errors ──────────────────────────────────────────────────────────────────
@@ -0,0 +1,26 @@
1
+ import type { ContentBlock } from "./types.js";
2
+
3
+ /**
4
+ * Build a text content block carrying an ephemeral `cache_control` breakpoint
5
+ * with a 1h TTL, marking a byte-stable prefix the provider KV cache should
6
+ * persist across calls.
7
+ *
8
+ * The Anthropic SDK accepts the field as an extra property on text blocks, but
9
+ * the internal `TextContent` type intentionally omits it (only the Anthropic
10
+ * provider transforms it onto the wire — block-level `cache_control` is
11
+ * preserved through `toAnthropicBlockSafe`), so we reach through a `Record`
12
+ * cast for the same reason `anthropic/client.ts` does — it keeps the core
13
+ * types provider-agnostic. The 1h TTL matches the provider's auto-applied
14
+ * breakpoints (see `cacheTtl` in `providers/anthropic/client.ts`); the
15
+ * `extended-cache-ttl-2025-04-11` beta header is added unconditionally for
16
+ * non-Haiku models in `client.ts` (Haiku models have the `ttl` stripped
17
+ * provider-side), so this works without any call-site config.
18
+ */
19
+ export function cachedTextBlock(text: string): ContentBlock {
20
+ const block: ContentBlock = { type: "text", text };
21
+ (block as unknown as Record<string, unknown>).cache_control = {
22
+ type: "ephemeral",
23
+ ttl: "1h",
24
+ };
25
+ return block;
26
+ }
@@ -37,8 +37,7 @@ mock.module("../../../memory/db-connection.js", () => ({
37
37
  }));
38
38
 
39
39
  mock.module("../../../config/assistant-feature-flags.js", () => ({
40
- isAssistantFeatureFlagEnabled: (flag: string) =>
41
- flag === "openai-compatible-endpoints",
40
+ isAssistantFeatureFlagEnabled: () => false,
42
41
  }));
43
42
 
44
43
  mock.module("../../../config/loader.js", () => ({
@@ -1040,6 +1040,19 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1040
1040
  pricing: { inputPer1mTokens: 0.6, outputPer1mTokens: 2.5 },
1041
1041
  },
1042
1042
  // MiniMax
1043
+ {
1044
+ id: "minimax/minimax-m3",
1045
+ displayName: "MiniMax M3",
1046
+ // The model supports 1M context, but OpenRouter's only route
1047
+ // (MiniMax) accepts 524,288 tokens; advertise the routed limit.
1048
+ contextWindowTokens: 524288,
1049
+ maxOutputTokens: 512000,
1050
+ supportsThinking: true,
1051
+ supportsCaching: false,
1052
+ supportsVision: true,
1053
+ supportsToolUse: true,
1054
+ pricing: { inputPer1mTokens: 0.3, outputPer1mTokens: 1.2 },
1055
+ },
1043
1056
  {
1044
1057
  id: "minimax/minimax-m2.7",
1045
1058
  displayName: "MiniMax M2.7",
@@ -1212,7 +1225,6 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1212
1225
  setupHint:
1213
1226
  "Enter the base URL of your endpoint and at least one model identifier.",
1214
1227
  apiKeyPlaceholder: "Your provider's API key",
1215
- featureFlag: "openai-compatible-endpoints",
1216
1228
  models: [],
1217
1229
  defaultModel: "",
1218
1230
  },