@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
@@ -1,33 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
- /**
4
- * Memory v3 (section-grain lane retrieval) working-set configuration.
5
- *
6
- * The working set is the per-conversation set of concept pages carried
7
- * forward across turns. Eviction keeps it bounded: pages unseen for longer
8
- * than `evictWindow` turns are dropped, and the set is capped at `maxPages`.
9
- */
10
- export const MemoryV3WorkingSetSchema = z
11
- .object({
12
- maxPages: z
13
- .number({ error: "memory.v3.workingSet.maxPages must be a number" })
14
- .int("memory.v3.workingSet.maxPages must be an integer")
15
- .positive("memory.v3.workingSet.maxPages must be a positive integer")
16
- .default(150)
17
- .describe(
18
- "Upper bound on the number of pages retained in the working set. Once exceeded, the least-salient non-pinned pages are evicted until the set fits.",
19
- ),
20
- evictWindow: z
21
- .number({ error: "memory.v3.workingSet.evictWindow must be a number" })
22
- .int("memory.v3.workingSet.evictWindow must be an integer")
23
- .positive("memory.v3.workingSet.evictWindow must be a positive integer")
24
- .default(5)
25
- .describe(
26
- "Number of turns a non-pinned page may go unseen before it is evicted from the working set.",
27
- ),
28
- })
29
- .describe("Memory v3 working-set retention/eviction tuning.");
30
-
31
3
  /**
32
4
  * Edge-lane tuning for the link-graph expansion that folds a turn's lexical and
33
5
  * dense seeds outward to their first-class neighbours.
@@ -67,10 +39,111 @@ export const MemoryV3EdgeSchema = z
67
39
  })
68
40
  .describe("Memory v3 edge-lane (link-graph expansion) tuning.");
69
41
 
42
+ /**
43
+ * Hot-set lane tuning: the top-K pages by exponentially-decayed selection
44
+ * frequency (frecency) folded into the candidate pool as a stable lane.
45
+ */
46
+ export const MemoryV3HotSetSchema = z
47
+ .object({
48
+ k: z
49
+ .number({ error: "memory.v3.hotSet.k must be a number" })
50
+ .int("memory.v3.hotSet.k must be an integer")
51
+ .positive("memory.v3.hotSet.k must be a positive integer")
52
+ .default(40)
53
+ .describe(
54
+ "Number of top frecency-scored pages included in the hot-set lane.",
55
+ ),
56
+ halfLifeDays: z
57
+ .number({ error: "memory.v3.hotSet.halfLifeDays must be a number" })
58
+ .positive("memory.v3.hotSet.halfLifeDays must be a positive number")
59
+ .default(14)
60
+ .describe(
61
+ "Frecency decay half-life in days: a selection this old contributes half the weight of one made now.",
62
+ ),
63
+ })
64
+ .describe("Memory v3 hot-set lane (decayed selection frequency) tuning.");
65
+
66
+ /**
67
+ * Ephemeral section-spotlight tuning: how many of the current turn's selected
68
+ * finder hits render their matched section into the `<memory_spotlight>`
69
+ * block, and how many previous turns' spotlight entries are carried along
70
+ * before they age out. The block is strip-and-replaced every turn, so its
71
+ * size is bounded by `n × (windowTurns + 1)` entries.
72
+ */
73
+ export const MemoryV3SpotlightSchema = z
74
+ .object({
75
+ n: z
76
+ .number({ error: "memory.v3.spotlight.n must be a number" })
77
+ .int("memory.v3.spotlight.n must be an integer")
78
+ .positive("memory.v3.spotlight.n must be a positive integer")
79
+ .default(6)
80
+ .describe(
81
+ "Number of the current turn's selected finder hits whose matched sections render into the spotlight block.",
82
+ ),
83
+ windowTurns: z
84
+ .number({ error: "memory.v3.spotlight.windowTurns must be a number" })
85
+ .int("memory.v3.spotlight.windowTurns must be an integer")
86
+ .nonnegative(
87
+ "memory.v3.spotlight.windowTurns must be a non-negative integer",
88
+ )
89
+ .default(2)
90
+ .describe(
91
+ "Number of previous turns whose spotlight entries are carried into the current block before aging out (0 = current turn only).",
92
+ ),
93
+ })
94
+ .describe("Memory v3 ephemeral section-spotlight tuning.");
95
+
96
+ /**
97
+ * Prune-valve bounds on the resident (non-pruned) frozen-card footprint.
98
+ *
99
+ * Frozen cards accumulate in history with no per-turn bound, so the valve is
100
+ * the structural backstop: once resident card bytes exceed
101
+ * `maxResidentBytes`, the least-recently-selected non-core/non-hot cards are
102
+ * pruned until the footprint is back at `targetResidentBytes`.
103
+ *
104
+ * Defaults rationale: production v2 ran 303KB of accumulated memory unpruned
105
+ * on the widest observed conversation — 384KB max / 256KB target make the
106
+ * valve insurance for growth beyond that, not routine behavior.
107
+ */
108
+ export const MemoryV3PruneSchema = z
109
+ .object({
110
+ maxResidentBytes: z
111
+ .number({ error: "memory.v3.prune.maxResidentBytes must be a number" })
112
+ .int("memory.v3.prune.maxResidentBytes must be an integer")
113
+ .positive("memory.v3.prune.maxResidentBytes must be a positive integer")
114
+ .default(393216 /* 384KB */)
115
+ .describe(
116
+ "Resident (non-pruned) card bytes above which the prune valve fires.",
117
+ ),
118
+ targetResidentBytes: z
119
+ .number({
120
+ error: "memory.v3.prune.targetResidentBytes must be a number",
121
+ })
122
+ .int("memory.v3.prune.targetResidentBytes must be an integer")
123
+ .positive(
124
+ "memory.v3.prune.targetResidentBytes must be a positive integer",
125
+ )
126
+ .default(262144 /* 256KB */)
127
+ .describe(
128
+ "Resident card bytes a fired prune reduces the footprint to (must be below maxResidentBytes).",
129
+ ),
130
+ })
131
+ .refine((value) => value.targetResidentBytes < value.maxResidentBytes, {
132
+ error:
133
+ "memory.v3.prune.targetResidentBytes must be less than memory.v3.prune.maxResidentBytes",
134
+ })
135
+ .describe("Memory v3 prune-valve (resident card footprint) bounds.");
136
+
137
+ // NOTE: a retired `workingSet` sub-config (maxPages/evictWindow for the old
138
+ // per-turn carry set) used to live here. Existing user config files may still
139
+ // contain the key; zod default unknown-key stripping accepts and ignores it,
140
+ // so legacy configs keep parsing. Do not make this object `.strict()`.
70
141
  export const MemoryV3ConfigSchema = z
71
142
  .object({
72
- workingSet: MemoryV3WorkingSetSchema.default(
73
- MemoryV3WorkingSetSchema.parse({}),
143
+ prune: MemoryV3PruneSchema.default(MemoryV3PruneSchema.parse({})),
144
+ hotSet: MemoryV3HotSetSchema.default(MemoryV3HotSetSchema.parse({})),
145
+ spotlight: MemoryV3SpotlightSchema.default(
146
+ MemoryV3SpotlightSchema.parse({}),
74
147
  ),
75
148
  needleK: z
76
149
  .number({ error: "memory.v3.needleK must be a number" })
@@ -90,8 +163,6 @@ export const MemoryV3ConfigSchema = z
90
163
  ),
91
164
  edge: MemoryV3EdgeSchema.default(MemoryV3EdgeSchema.parse({})),
92
165
  })
93
- .describe(
94
- "Memory v3 — section-grain lane retrieval with a carry-forward working set",
95
- );
166
+ .describe("Memory v3 — section-grain lane retrieval");
96
167
 
97
168
  export type MemoryV3Config = z.infer<typeof MemoryV3ConfigSchema>;
@@ -229,7 +229,7 @@ export function seedInferenceProfiles(
229
229
  // profiles without duplicating; we have to honor those edits across
230
230
  // reseeds or they'd silently revert on every boot. Carry by
231
231
  // key-presence rather than truthiness so an explicit `null` (user
232
- // cleared the label) survives too. Codex P1 finding on PR #30362.
232
+ // cleared the label) survives too.
233
233
  //
234
234
  // BYOK seed defaults (off-platform only):
235
235
  // • label: " (Managed)" suffix disambiguates managed profile labels
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type Dirent,
2
3
  existsSync,
3
4
  lstatSync,
4
5
  readdirSync,
@@ -17,18 +18,16 @@ import {
17
18
 
18
19
  import { z } from "zod";
19
20
 
20
- import {
21
- getPluginContributedSkillDefinition,
22
- getPluginContributedSkillSummaries,
23
- } from "../plugins/plugin-skill-contributions.js";
24
21
  import { parseFrontmatterFields } from "../skills/frontmatter.js";
25
22
  import type { InlineCommandExpansion } from "../skills/inline-command-expansions.js";
26
23
  import { parseInlineCommandExpansions } from "../skills/inline-command-expansions.js";
27
24
  import { parseToolManifestFile } from "../skills/tool-manifest.js";
28
25
  import { computeSkillVersionHash } from "../skills/version-hash.js";
26
+ import type { OwnerInfo } from "../tools/types.js";
29
27
  import { getLogger } from "../util/logger.js";
30
28
  import {
31
29
  getWorkspaceDirDisplay,
30
+ getWorkspacePluginsDir,
32
31
  getWorkspaceSkillsDir,
33
32
  } from "../util/platform.js";
34
33
  import { stripCommentLines } from "../util/strip-comment-lines.js";
@@ -47,6 +46,7 @@ const VellumMetadataSchema = z
47
46
  "feature-flag": z.string().optional(),
48
47
  "activation-hints": z.array(z.string()).optional(),
49
48
  "avoid-when": z.array(z.string()).optional(),
49
+ category: z.string().optional(),
50
50
  })
51
51
  .passthrough();
52
52
 
@@ -66,9 +66,9 @@ const SkillMetadataSchema = z
66
66
  * - `workspace`: user-authored skill living in a conversation's working dir.
67
67
  * - `extra`: third-party directory roots passed via `loadSkillCatalog`'s
68
68
  * `extraDirs` argument (primarily for tests).
69
- * - `plugin`: contributed at runtime by a loaded plugin via
70
- * `PluginSkillRegistration`. Body is held in-memory by
71
- * `plugins/plugin-skill-contributions.ts`, not on disk.
69
+ * - `plugin`: shipped on disk inside an installed plugin at
70
+ * `<workspaceDir>/plugins/<name>/skills/<id>/SKILL.md`, attributed back to
71
+ * the owning plugin via its `owner` descriptor.
72
72
  */
73
73
  export type SkillSource =
74
74
  | "bundled"
@@ -90,6 +90,13 @@ export interface SkillSummary {
90
90
  icon?: string;
91
91
  emoji?: string;
92
92
  source: SkillSource;
93
+ /**
94
+ * Ownership descriptor identifying the extension that ships this skill,
95
+ * reusing the same {@link OwnerInfo} model the tool registry uses. Set only
96
+ * for `source: "plugin"` skills — `{ kind: "plugin", id: <plugin dir name> }`
97
+ * — attributing them to the installed plugin under `<workspaceDir>/plugins/`.
98
+ */
99
+ owner?: OwnerInfo;
93
100
  /** Parsed tool manifest metadata, if the skill has a valid TOOLS.json. */
94
101
  toolManifest?: SkillToolManifestMeta;
95
102
  /** IDs of child skills that this skill includes (metadata-only, not auto-activated). */
@@ -100,6 +107,8 @@ export interface SkillSummary {
100
107
  activationHints?: string[];
101
108
  /** Conditions under which this skill should NOT be loaded. */
102
109
  avoidWhen?: string[];
110
+ /** Category slug declared in frontmatter, used as a fallback when the skill is not in the Vellum catalog. */
111
+ category?: string;
103
112
  /** Parsed inline command expansion descriptors (`!\`command\``) found in the skill body. */
104
113
  inlineCommandExpansions?: InlineCommandExpansion[];
105
114
  }
@@ -217,6 +226,7 @@ interface ParsedFrontmatter {
217
226
  featureFlag?: string;
218
227
  activationHints?: string[];
219
228
  avoidWhen?: string[];
229
+ category?: string;
220
230
  inlineCommandExpansions?: InlineCommandExpansion[];
221
231
  }
222
232
 
@@ -327,6 +337,11 @@ function parseFrontmatter(
327
337
  const activationHints = normalizeStringArray(vellum?.["activation-hints"]);
328
338
  const avoidWhen = normalizeStringArray(vellum?.["avoid-when"]);
329
339
 
340
+ const category =
341
+ typeof vellum?.category === "string" && vellum.category.trim().length > 0
342
+ ? vellum.category.trim()
343
+ : undefined;
344
+
330
345
  const strippedBody = stripCommentLines(body);
331
346
 
332
347
  // Parse inline command expansions from the body (after frontmatter/comment stripping)
@@ -350,6 +365,7 @@ function parseFrontmatter(
350
365
  featureFlag,
351
366
  activationHints,
352
367
  avoidWhen,
368
+ category,
353
369
  inlineCommandExpansions,
354
370
  };
355
371
  }
@@ -506,6 +522,7 @@ function readSkillFromDirectory(
506
522
  featureFlag: parsed.featureFlag,
507
523
  activationHints: parsed.activationHints,
508
524
  avoidWhen: parsed.avoidWhen,
525
+ category: parsed.category,
509
526
  inlineCommandExpansions: parsed.inlineCommandExpansions,
510
527
  };
511
528
  } catch (err) {
@@ -558,6 +575,7 @@ function readBundledSkillFromDirectory(
558
575
  featureFlag: parsed.featureFlag,
559
576
  activationHints: parsed.activationHints,
560
577
  avoidWhen: parsed.avoidWhen,
578
+ category: parsed.category,
561
579
  inlineCommandExpansions: parsed.inlineCommandExpansions,
562
580
  };
563
581
  } catch (err) {
@@ -618,6 +636,7 @@ function loadBundledSkills(): SkillSummary[] {
618
636
  featureFlag: skill.featureFlag,
619
637
  activationHints: skill.activationHints,
620
638
  avoidWhen: skill.avoidWhen,
639
+ category: skill.category,
621
640
  inlineCommandExpansions: skill.inlineCommandExpansions,
622
641
  });
623
642
  }
@@ -646,6 +665,82 @@ function discoverSkillDirectories(skillsDir: string): string[] {
646
665
  return dirs.sort((a, b) => a.localeCompare(b));
647
666
  }
648
667
 
668
+ /**
669
+ * Whether `pluginDir` is a recognized installed plugin: it must carry a
670
+ * parseable `package.json` whose `name` equals the directory name. This
671
+ * mirrors the external plugin loader's recognition gate, which skips any
672
+ * directory whose `manifest.name` does not match its directory name.
673
+ */
674
+ function isRecognizedPluginDir(pluginDir: string, dirName: string): boolean {
675
+ const manifestPath = join(pluginDir, "package.json");
676
+ if (!existsSync(manifestPath)) return false;
677
+ try {
678
+ const parsed: unknown = JSON.parse(readFileSync(manifestPath, "utf-8"));
679
+ return (
680
+ typeof parsed === "object" &&
681
+ parsed !== null &&
682
+ "name" in parsed &&
683
+ (parsed as { name: unknown }).name === dirName
684
+ );
685
+ } catch (err) {
686
+ log.warn(
687
+ { err, manifestPath },
688
+ "Skipping plugin dir with unparseable package.json for resident skills",
689
+ );
690
+ return false;
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Discover skills shipped on disk inside installed plugins. Each installed
696
+ * plugin — a directory under `<workspaceDir>/plugins/` recognized by
697
+ * {@link isRecognizedPluginDir} — may ship skills at `skills/<id>/SKILL.md`.
698
+ * Returned summaries are attributed to the owning plugin via their `owner`
699
+ * descriptor (`{ kind: "plugin", id: <plugin dir name> }`).
700
+ */
701
+ function discoverPluginResidentSkills(): SkillSummary[] {
702
+ const pluginsDir = getWorkspacePluginsDir();
703
+ if (!existsSync(pluginsDir)) return [];
704
+
705
+ let entries: Dirent[];
706
+ try {
707
+ entries = readdirSync(pluginsDir, { withFileTypes: true });
708
+ } catch (err) {
709
+ log.warn(
710
+ { err, pluginsDir },
711
+ "Failed to read plugins directory for resident skills",
712
+ );
713
+ return [];
714
+ }
715
+
716
+ const summaries: SkillSummary[] = [];
717
+ for (const entry of entries) {
718
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
719
+ const pluginDir = join(pluginsDir, entry.name);
720
+ // Mirror the plugin loader's recognition gate: a directory is a real
721
+ // installed plugin only if it carries a parseable `package.json` whose
722
+ // `name` matches the directory. This rejects staging dirs, stray files,
723
+ // and malformed/mismatched clones (e.g. an un-adapted `caveman-installer`)
724
+ // that the loader itself would skip, so the catalog never surfaces skills
725
+ // from a directory the runtime would refuse to load.
726
+ if (!isRecognizedPluginDir(pluginDir, entry.name)) continue;
727
+
728
+ const skillsDir = join(pluginDir, "skills");
729
+ if (!existsSync(skillsDir)) continue;
730
+
731
+ for (const directory of discoverSkillDirectories(skillsDir)) {
732
+ const skill = readSkillFromDirectory(directory, skillsDir, "plugin");
733
+ if (!skill) continue;
734
+ summaries.push({
735
+ ...skillSummaryFromDefinition(skill, "plugin"),
736
+ owner: { kind: "plugin", id: entry.name },
737
+ });
738
+ }
739
+ }
740
+
741
+ return summaries;
742
+ }
743
+
649
744
  // ─── Catalog loading ─────────────────────────────────────────────────────────
650
745
 
651
746
  function skillSummaryFromDefinition(
@@ -668,6 +763,7 @@ function skillSummaryFromDefinition(
668
763
  featureFlag: skill.featureFlag,
669
764
  activationHints: skill.activationHints,
670
765
  avoidWhen: skill.avoidWhen,
766
+ category: skill.category,
671
767
  inlineCommandExpansions: skill.inlineCommandExpansions,
672
768
  };
673
769
  }
@@ -721,6 +817,7 @@ export function loadSkillCatalog(
721
817
  featureFlag: parsed.featureFlag,
722
818
  activationHints: parsed.activationHints,
723
819
  avoidWhen: parsed.avoidWhen,
820
+ category: parsed.category,
724
821
  inlineCommandExpansions: parsed.inlineCommandExpansions,
725
822
  });
726
823
  } catch (err) {
@@ -754,12 +851,11 @@ export function loadSkillCatalog(
754
851
  catalog.push(skill);
755
852
  }
756
853
 
757
- // Load plugin-contributed skills. They sit above bundled/extra but below
758
- // managed and workspace so that user-authored filesystem skills can
759
- // override a plugin-provided skill by declaring the same id under
760
- // `~/.vellum/workspace/skills/`. Registration is owned by the plugin
761
- // bootstrap — this call is a pure read against the in-memory registry.
762
- const pluginSkills = getPluginContributedSkillSummaries();
854
+ // Discover skills shipped on disk inside installed plugins. They sit above
855
+ // bundled/extra but below managed and workspace so a user-authored
856
+ // filesystem skill can override a plugin-provided skill by declaring the
857
+ // same id under `$VELLUM_WORKSPACE_DIR/skills/`.
858
+ const pluginSkills = discoverPluginResidentSkills();
763
859
  for (const skill of pluginSkills) {
764
860
  if (seenIds.has(skill.id)) {
765
861
  const existingIndex = catalog.findIndex((s) => s.id === skill.id);
@@ -769,14 +865,14 @@ export function loadSkillCatalog(
769
865
  catalog[existingIndex].source === "extra")
770
866
  ) {
771
867
  log.info(
772
- { id: skill.id, pluginSkill: true },
868
+ { id: skill.id, pluginName: skill.owner?.id },
773
869
  "Plugin skill overrides bundled/extra skill",
774
870
  );
775
871
  catalog[existingIndex] = skill;
776
872
  continue;
777
873
  }
778
874
  log.warn(
779
- { id: skill.id },
875
+ { id: skill.id, pluginName: skill.owner?.id },
780
876
  "Skipping duplicate plugin skill id (already present in catalog)",
781
877
  );
782
878
  continue;
@@ -857,6 +953,7 @@ export function loadSkillCatalog(
857
953
  featureFlag: parsed.featureFlag,
858
954
  activationHints: parsed.activationHints,
859
955
  avoidWhen: parsed.avoidWhen,
956
+ category: parsed.category,
860
957
  inlineCommandExpansions: parsed.inlineCommandExpansions,
861
958
  };
862
959
 
@@ -992,41 +1089,13 @@ export function listReferenceFiles(directoryPath: string): string | null {
992
1089
  }
993
1090
 
994
1091
  function loadSkillDefinition(skill: SkillSummary): SkillLookupResult {
995
- // Plugin-contributed skills live in-memory, not on disk. The registry
996
- // (see `plugins/plugin-skill-contributions.ts`) stores a pre-built
997
- // SkillDefinition we can return directly — no file read, no frontmatter
998
- // parse. We still run placeholder / feature-gate substitution on the
999
- // body so plugin skills behave the same as filesystem skills from the
1000
- // model's perspective.
1001
- if (skill.source === "plugin") {
1002
- const pluginDef = getPluginContributedSkillDefinition(skill.id);
1003
- if (!pluginDef) {
1004
- // The skill was in the catalog when the selector ran but has since
1005
- // been unregistered (plugin hot-reload, shutdown race). Fail loudly
1006
- // rather than returning a mystery undefined.
1007
- return {
1008
- error: `Plugin-contributed skill "${skill.id}" is no longer registered`,
1009
- };
1010
- }
1011
- // Shallow-clone so mutating the returned `body` below does not touch
1012
- // the registry's stored copy — the registry must stay the source of
1013
- // truth across repeated `skill_load` calls.
1014
- const loaded: SkillDefinition = { ...pluginDef };
1015
- loaded.body = loaded.body.replaceAll("{baseDir}", loaded.directoryPath);
1016
- loaded.body = loaded.body.replaceAll(
1017
- "{workspaceDir}",
1018
- getWorkspaceDirDisplay(),
1019
- );
1020
- loaded.body = applyFeatureGatedSections(loaded.body);
1021
- return { skill: loaded };
1022
- }
1023
-
1024
1092
  let loaded: SkillDefinition | null;
1025
1093
  if (skill.bundled) {
1026
1094
  loaded = readBundledSkillFromDirectory(skill.directoryPath);
1027
- } else if (skill.source === "workspace") {
1028
- // Workspace skills live outside ~/.vellum/workspace/skills, so use their parent
1029
- // directory as the root to avoid the isOutsideSkillsRoot rejection.
1095
+ } else if (skill.source === "workspace" || skill.source === "plugin") {
1096
+ // Workspace and plugin-resident skills live outside
1097
+ // $VELLUM_WORKSPACE_DIR/skills, so use the skill's parent directory as the
1098
+ // root to avoid the isOutsideSkillsRoot rejection.
1030
1099
  loaded = readSkillFromDirectory(
1031
1100
  skill.directoryPath,
1032
1101
  dirname(skill.directoryPath),
@@ -1042,6 +1111,7 @@ function loadSkillDefinition(skill: SkillSummary): SkillLookupResult {
1042
1111
  if (!loaded) {
1043
1112
  return { error: `Failed to load SKILL.md for "${skill.id}"` };
1044
1113
  }
1114
+ loaded.owner = skill.owner;
1045
1115
  // Replace {baseDir} placeholders with the actual skill directory path
1046
1116
  loaded.body = loaded.body.replaceAll("{baseDir}", loaded.directoryPath);
1047
1117
  // Replace {workspaceDir} placeholders with the runtime workspace display path
@@ -743,12 +743,19 @@ export async function runAssistantDrivenCompaction(
743
743
  let response: ProviderResponse;
744
744
  try {
745
745
  response = await args.provider.sendMessage(requestMessages, {
746
+ // Tools are passed so the cached prefix (system prompt + tools +
747
+ // history) matches the agent's main-turn cache key. Force
748
+ // `tool_choice: "none"` so the model can only answer with the
749
+ // `<compaction_result>` text and never burns the turn on a tool call
750
+ // it cannot complete — an empty text response yields no parseable
751
+ // summary, stalls compaction, and exhausts the agent loop budget.
746
752
  tools: args.tools,
747
753
  systemPrompt: args.systemPrompt,
748
754
  signal: args.signal,
749
755
  config: {
750
756
  callSite: COMPACTION_CALL_SITE,
751
757
  usageTracking: "manual",
758
+ tool_choice: { type: "none" },
752
759
  ...(args.overrideProfile
753
760
  ? { overrideProfile: args.overrideProfile }
754
761
  : {}),
@@ -1113,12 +1120,16 @@ export async function runEmergencyCompaction(
1113
1120
  let response: ProviderResponse;
1114
1121
  try {
1115
1122
  response = await args.provider.sendMessage(requestMessages, {
1123
+ // See the assistant-driven path: tools keep the prefix cache warm, but
1124
+ // `tool_choice: "none"` forces a text-only `<compaction_result>` so the
1125
+ // model can't stall compaction by emitting an uncompletable tool call.
1116
1126
  tools: args.tools,
1117
1127
  systemPrompt: args.systemPrompt,
1118
1128
  signal: args.signal,
1119
1129
  config: {
1120
1130
  callSite: COMPACTION_CALL_SITE,
1121
1131
  usageTracking: "manual",
1132
+ tool_choice: { type: "none" },
1122
1133
  ...(args.overrideProfile
1123
1134
  ? { overrideProfile: args.overrideProfile }
1124
1135
  : {}),
@@ -11,6 +11,10 @@
11
11
  * loop (which drives the compaction pipeline) and the compactor can call it
12
12
  * without reaching up into the daemon orchestrator.
13
13
  */
14
+ import {
15
+ MEMORY_SPOTLIGHT_PREFIX,
16
+ MEMORY_SPOTLIGHT_SUFFIX,
17
+ } from "../memory/memory-marker.js";
14
18
  import type { Message } from "../providers/types.js";
15
19
 
16
20
  /**
@@ -56,6 +60,34 @@ export function stripUserTextBlocksByPrefix(
56
60
  );
57
61
  }
58
62
 
63
+ /**
64
+ * Full-wrapper matcher for the memory-v3 ephemeral `<memory_spotlight>` block.
65
+ * Shared by the per-turn scoped strip ({@link stripSpotlightInjections}) and
66
+ * the compaction pipeline below so both recognize exactly the same wrapper.
67
+ */
68
+ const MEMORY_SPOTLIGHT_MATCHER: InjectionMatcher = {
69
+ prefix: MEMORY_SPOTLIGHT_PREFIX,
70
+ suffix: MEMORY_SPOTLIGHT_SUFFIX,
71
+ };
72
+
73
+ /**
74
+ * Remove memory-v3 `<memory_spotlight>` blocks from every user message — and
75
+ * ONLY those blocks. The spotlight is ephemeral by contract: runtime assembly
76
+ * strip-and-replaces it each turn (the previous turn's spotlight is stale),
77
+ * while the frozen `<memory>` card blocks stay byte-identical in history for
78
+ * prompt caching. This is deliberately a scoped, single-id strip — the old
79
+ * whole-layer `stripAllMemoryInjections` replace is gone.
80
+ */
81
+ export function stripSpotlightInjections(messages: Message[]): Message[] {
82
+ return stripUserTextBlocksByPrefix(messages, [MEMORY_SPOTLIGHT_MATCHER]);
83
+ }
84
+
85
+ /** `<NOW.md>` scratchpad prefixes (current tag, pre-line-limit variant, legacy `<now_scratchpad>`) — shared with `stripNowScratchpad` so the two strip paths can't drift. */
86
+ export const NOW_SCRATCHPAD_STRIP_PREFIXES: InjectionMatcher[] = [
87
+ "<NOW.md Always keep this up to date",
88
+ "<now_scratchpad>",
89
+ ];
90
+
59
91
  /** Matchers stripped by the pipeline (order doesn't matter — single pass). */
60
92
  const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
61
93
  "<channel_capabilities>",
@@ -85,6 +117,11 @@ const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
85
117
  // matches the full-wrapper requirement in `countMemoryPrefixBlocks`.
86
118
  { prefix: "<memory>\n", suffix: "\n</memory>" },
87
119
  { prefix: "<info>\n", suffix: "\n</info>" },
120
+ // The memory-v3 ephemeral spotlight block. Normally strip-and-replaced every
121
+ // turn by `stripSpotlightInjections`, but registered here too so compaction
122
+ // and overflow recovery remove a stale spotlight along with the rest of the
123
+ // runtime injections. Full-wrapper shape for the same reason as `<memory>`.
124
+ MEMORY_SPOTLIGHT_MATCHER,
88
125
  "<voice_call_control>",
89
126
  "<workspace_top_level>", // backward-compat: strip legacy workspace blocks
90
127
  // The `<workspace>` top-level block is stripped so each compaction re-injects
@@ -100,10 +137,7 @@ const RUNTIME_INJECTION_PREFIXES: InjectionMatcher[] = [
100
137
  "<active_workspace>",
101
138
  "<active_dynamic_page>",
102
139
  "<non_interactive_context>",
103
- // Shared prefix catches both the current NOW.md tag and any pre-line-limit
104
- // variant that may linger in in-flight histories during a rolling deploy.
105
- "<NOW.md Always keep this up to date",
106
- "<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
140
+ ...NOW_SCRATCHPAD_STRIP_PREFIXES,
107
141
  "<knowledge_base>",
108
142
  "<pkb>", // backward-compat: strip legacy tag from pre-rename history
109
143
  "<system_reminder>",
@@ -28,12 +28,6 @@ export const CES_SECURE_INSTALL_FLAG_KEY = "ces-secure-install" as const;
28
28
  /** Gate for credential grant and audit inspection surfaces. */
29
29
  export const CES_GRANT_AUDIT_FLAG_KEY = "ces-grant-audit" as const;
30
30
 
31
- /** Gate for routing credential reads/writes through the CES process. */
32
- const CES_CREDENTIAL_BACKEND_FLAG_KEY = "ces-credential-backend" as const;
33
-
34
- /** Gate for managed sidecar transport in containerized environments. */
35
- export const CES_MANAGED_SIDECAR_FLAG_KEY = "ces-managed-sidecar" as const;
36
-
37
31
  // ---------------------------------------------------------------------------
38
32
  // Public API — predicate functions
39
33
  // ---------------------------------------------------------------------------
@@ -66,18 +60,3 @@ export function isCesGrantAuditEnabled(config: AssistantConfig): boolean {
66
60
  return isAssistantFeatureFlagEnabled(CES_GRANT_AUDIT_FLAG_KEY, config);
67
61
  }
68
62
 
69
- /**
70
- * Whether credential reads and writes should be routed through the CES process.
71
- */
72
- export function isCesCredentialBackendEnabled(
73
- config: AssistantConfig,
74
- ): boolean {
75
- return isAssistantFeatureFlagEnabled(CES_CREDENTIAL_BACKEND_FLAG_KEY, config);
76
- }
77
-
78
- /**
79
- * Whether managed sidecar transport should be used for CES communication.
80
- */
81
- export function isCesManagedSidecarEnabled(config: AssistantConfig): boolean {
82
- return isAssistantFeatureFlagEnabled(CES_MANAGED_SIDECAR_FLAG_KEY, config);
83
- }
@@ -11,10 +11,8 @@ export async function awaitCesClientWithTimeout(
11
11
  clientPromise: Promise<CesClient | undefined>,
12
12
  options: AwaitCesClientWithTimeoutOptions = {},
13
13
  ): Promise<CesClient | undefined> {
14
- const {
15
- timeoutMs = DEFAULT_CES_STARTUP_TIMEOUT_MS,
16
- onTimeout = () => {},
17
- } = options;
14
+ const { timeoutMs = DEFAULT_CES_STARTUP_TIMEOUT_MS, onTimeout = () => {} } =
15
+ options;
18
16
 
19
17
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
20
18
 
@@ -34,3 +32,33 @@ export async function awaitCesClientWithTimeout(
34
32
  }
35
33
  }
36
34
  }
35
+
36
+ export interface CesClientResolver {
37
+ getCesClient: () => CesClient | undefined;
38
+ setCesClient: (client: CesClient) => void;
39
+ }
40
+
41
+ /**
42
+ * Inject the CES client into the credential resolver once the startup
43
+ * handshake resolves.
44
+ *
45
+ * {@link awaitCesClientWithTimeout} only gates provider initialization: when
46
+ * the handshake loses the race it returns before the client exists, so the
47
+ * caller cannot inject it synchronously. This bridges that gap by injecting
48
+ * the client whenever the handshake eventually completes. The resolver is left
49
+ * untouched if it already holds a client (e.g. one installed by a reconnection
50
+ * while the late handshake was still in flight) so the older startup client
51
+ * never clobbers a newer one, and a failed or aborted handshake is a no-op.
52
+ */
53
+ export function injectCesClientWhenReady(
54
+ clientPromise: Promise<CesClient | undefined>,
55
+ resolver: CesClientResolver,
56
+ ): void {
57
+ void clientPromise
58
+ .then((client) => {
59
+ if (client && resolver.getCesClient() === undefined) {
60
+ resolver.setCesClient(client);
61
+ }
62
+ })
63
+ .catch(() => undefined);
64
+ }