@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
@@ -12,6 +12,7 @@ import { z } from "zod";
12
12
  import type { AssistantConfig } from "../../config/types.js";
13
13
  import { estimateTextTokens } from "../../context/token-estimator.js";
14
14
  import type { ServerMessage } from "../../daemon/message-protocol.js";
15
+ import { clearConversation as clearV3EverInjected } from "../../plugins/defaults/memory-v3-shadow/ever-injected-store.js";
15
16
  import type {
16
17
  ContentBlock,
17
18
  ImageContent,
@@ -284,6 +285,19 @@ export class ConversationGraphMemory {
284
285
  );
285
286
  }
286
287
 
288
+ // Memory-v3's frozen-card dedup record resets at the same trigger: the
289
+ // cached card blocks those slugs rode were just stripped by compaction, so
290
+ // every slug must become re-injectable. Cleared unconditionally for the
291
+ // same crash-drift reason as v2's `everInjected` above.
292
+ try {
293
+ clearV3EverInjected(this.conversationId);
294
+ } catch (err) {
295
+ log.warn(
296
+ { err: err instanceof Error ? err.message : String(err) },
297
+ "Failed to clear memory-v3 everInjected on compaction (non-fatal)",
298
+ );
299
+ }
300
+
287
301
  this.needsReload = true;
288
302
  log.info(
289
303
  { compactedMessageCount },
@@ -370,6 +384,22 @@ export class ConversationGraphMemory {
370
384
  return this.lastPkbSparseVector;
371
385
  }
372
386
 
387
+ /**
388
+ * The unwrapped text of the dynamic memory block the last retrieval
389
+ * injected (the v2 router block, or the v1 context block on legacy
390
+ * configs), or `null` when the last retrieval injected nothing.
391
+ *
392
+ * Runtime assembly reads this as the IDENTITY of the v2 dynamic `<memory>`
393
+ * block when memory-v3 supersedes the v2 layer: v2's block and v3's frozen
394
+ * card blocks deliberately share the same wrapper AND leading instruction
395
+ * header bytes, so the tail strip must match the exact block this handle
396
+ * prepended (`prepareMemory` → `injectTextBlock`, or a convergence
397
+ * re-injection of the same cached text) rather than any shared prefix.
398
+ */
399
+ get lastInjectedBlockText(): string | null {
400
+ return this.lastInjectedBlock;
401
+ }
402
+
373
403
  /**
374
404
  * Main entry point — called on every turn before the LLM sees the messages.
375
405
  *
@@ -432,7 +462,7 @@ export class ConversationGraphMemory {
432
462
  metrics: null as RetrievalMetrics | null,
433
463
  };
434
464
 
435
- if (!config.memory.enabled) {
465
+ if (config.memory.enabled === false) {
436
466
  // Clear any cached injection so a later overflow-reduction
437
467
  // re-injection via `reinjectCachedMemory()` cannot reintroduce a
438
468
  // stale <memory> block after the user disables memory.
@@ -970,9 +1000,11 @@ export function stripExistingMemoryInjections(messages: Message[]): Message[] {
970
1000
  /**
971
1001
  * Strip the memory-injected prefix from a single user message. Returns the
972
1002
  * same message reference unchanged when it is not a user message or carries no
973
- * injected prefix, so callers can cheaply detect no-ops. Shared by
974
- * `stripExistingMemoryInjections` (last message only) and
975
- * `stripAllMemoryInjections` (every user message).
1003
+ * injected prefix, so callers can cheaply detect no-ops. Used by
1004
+ * `stripExistingMemoryInjections` (last message only) — memory-v3's frozen
1005
+ * card carry means historical `<memory>` blocks are never bulk-stripped
1006
+ * anymore (the old whole-layer `stripAllMemoryInjections` is gone): runtime
1007
+ * assembly strips only the TAIL's fresh v2 prefix when v3 supersedes it.
976
1008
  */
977
1009
  function stripMemoryPrefixFromUserMessage(message: Message): Message {
978
1010
  if (message.role !== "user") return message;
@@ -981,27 +1013,6 @@ function stripMemoryPrefixFromUserMessage(message: Message): Message {
981
1013
  return { ...message, content: message.content.slice(firstNonMemory) };
982
1014
  }
983
1015
 
984
- /**
985
- * Remove memory-injected blocks from EVERY user message, not just the last.
986
- *
987
- * memory-v3 live mode strips the injected `<memory>` block from all historical
988
- * user messages each turn: it keeps the prompt lean and makes history
989
- * byte-stable for prompt caching (v2 strips only the last user message — see
990
- * `stripExistingMemoryInjections`). Reuses the same per-message block
991
- * recognition as the last-message strip. Wired into the runtime-assembly
992
- * suppression step (`conversation-runtime-assembly.ts`), which calls it when
993
- * `memory-v3-live` is on and the v3 injector produced a block this turn.
994
- */
995
- export function stripAllMemoryInjections(messages: Message[]): Message[] {
996
- let changed = false;
997
- const result = messages.map((message) => {
998
- const stripped = stripMemoryPrefixFromUserMessage(message);
999
- if (stripped !== message) changed = true;
1000
- return stripped;
1001
- });
1002
- return changed ? result : messages;
1003
- }
1004
-
1005
1016
  /**
1006
1017
  * Return the memory-injected prefix blocks from the last user message, or
1007
1018
  * an empty array when there is none. Used by runtime assembly to carry the
@@ -40,6 +40,9 @@ export function handleRemember(
40
40
  if (!input.content || input.content.trim().length === 0) {
41
41
  return { success: false, message: "content is required" };
42
42
  }
43
+ if (config.memory.enabled === false) {
44
+ return { success: false, message: "Memory is disabled." };
45
+ }
43
46
 
44
47
  const workspaceDir = getWorkspaceDir();
45
48
  const now = new Date();
@@ -164,7 +164,8 @@ function parseDeletedCount(stdout: string | undefined): number {
164
164
  * conversation_keys, channel_inbound_events, message_runs, call_sessions,
165
165
  * external_conversation_bindings, assistant_inbox_conversation_state) are handled
166
166
  * automatically. Tables without cascade (messages, tool_invocations,
167
- * llm_request_logs) are deleted explicitly before removing the conversation row.
167
+ * llm_request_logs, skill_loaded_events) are deleted explicitly before
168
+ * removing the conversation row.
168
169
  */
169
170
  export function pruneOldConversationsJob(
170
171
  job: MemoryJob,
@@ -206,6 +207,7 @@ export function pruneOldConversationsJob(
206
207
  rawRun(`DELETE FROM llm_request_logs WHERE conversation_id = ?`, id);
207
208
  rawRun(`DELETE FROM tool_invocations WHERE conversation_id = ?`, id);
208
209
  rawRun(`DELETE FROM messages WHERE conversation_id = ?`, id);
210
+ rawRun(`DELETE FROM skill_loaded_events WHERE conversation_id = ?`, id);
209
211
  // Conversation row deletion cascades to remaining dependent tables
210
212
  rawRun(`DELETE FROM conversations WHERE id = ?`, id);
211
213
  pruned++;
@@ -342,34 +342,6 @@ export function hasActiveJobOfType(type: MemoryJobType): boolean {
342
342
  );
343
343
  }
344
344
 
345
- export function isAutomaticConsolidationJob(job: MemoryJob): boolean {
346
- return job.payload.trigger !== MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS.manual;
347
- }
348
-
349
- export function cancelPendingAutomaticConsolidationJobs(): number {
350
- const db = getDb();
351
- db.update(memoryJobs)
352
- .set({
353
- status: "failed",
354
- lastError: "automatic_consolidation_disabled",
355
- updatedAt: Date.now(),
356
- })
357
- .where(
358
- and(
359
- eq(memoryJobs.type, "memory_v2_consolidate"),
360
- eq(memoryJobs.status, "pending"),
361
- or(
362
- sql`json_extract(${memoryJobs.payload}, '$.trigger') = ${MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS.automatic}`,
363
- // Legacy rows predate trigger markers and are indistinguishable from
364
- // automatic enqueues, so disabling the schedule treats them as auto.
365
- sql`json_extract(${memoryJobs.payload}, '$.trigger') IS NULL`,
366
- ),
367
- ),
368
- )
369
- .run();
370
- return rawChanges();
371
- }
372
-
373
345
  export function enqueuePruneOldLlmRequestLogsJob(retentionMs?: number): string {
374
346
  const db = getDb();
375
347
  const existing = db
@@ -68,7 +68,6 @@ import {
68
68
  failMemoryJob,
69
69
  failStalledJobs,
70
70
  hasActiveJobOfType,
71
- isAutomaticConsolidationJob,
72
71
  MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS,
73
72
  type MemoryJob,
74
73
  type MemoryJobType,
@@ -242,7 +241,7 @@ export async function runMemoryJobsOnce(
242
241
  options: { enableScheduledCleanup?: boolean } = {},
243
242
  ): Promise<number> {
244
243
  const config = getConfig();
245
- if (!config.memory.enabled) return 0;
244
+ if (config.memory.enabled === false) return 0;
246
245
  const enableScheduledCleanup = options.enableScheduledCleanup === true;
247
246
 
248
247
  const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
@@ -625,16 +624,6 @@ async function processJob(
625
624
  await memoryV2SweepJob(job, config);
626
625
  return;
627
626
  case "memory_v2_consolidate":
628
- if (
629
- isAutomaticConsolidationJob(job) &&
630
- !config.memory.v2.consolidation_enabled
631
- ) {
632
- log.info(
633
- { jobId: job.id },
634
- "Skipping automatic memory v2 consolidation because scheduled consolidation is disabled",
635
- );
636
- return;
637
- }
638
627
  await memoryV2ConsolidateJob(job, config);
639
628
  return;
640
629
  case "memory_v2_migrate":
@@ -713,8 +702,8 @@ const GRAPH_PATTERN_SCAN_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 day
713
702
  const GRAPH_NARRATIVE_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000; // 1 week
714
703
  // Backstop cadence for v3 self-maintenance. The primary trigger is the
715
704
  // post-consolidation follow-up (see `consolidation-job.ts`); this interval only
716
- // covers the case where that follow-up is missed (enqueue failure, or a v3-on
717
- // install with v2 consolidation disabled). A conservative cadence is fine since
705
+ // covers the case where that follow-up is missed (enqueue failure). A
706
+ // conservative cadence is fine since
718
707
  // the maintenance pass is idempotent and cheap when there's nothing to do.
719
708
  const GRAPH_V3_MAINTAIN_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
720
709
 
@@ -759,9 +748,10 @@ export function maybeEnqueueGraphMaintenanceJobs(
759
748
  config: AssistantConfig,
760
749
  nowMs = Date.now(),
761
750
  ): void {
751
+ const memoryEnabled = config.memory.enabled !== false;
752
+ if (!memoryEnabled) return;
753
+
762
754
  const v2Active = config.memory.v2.enabled;
763
- const v2ConsolidationAutomaticEnabled =
764
- v2Active && config.memory.v2.consolidation_enabled;
765
755
 
766
756
  // The single buffer-drainer entry for the v2-active branch. Referenced again
767
757
  // below by the size-based trigger.
@@ -776,9 +766,7 @@ export function maybeEnqueueGraphMaintenanceJobs(
776
766
  intervalMs: number;
777
767
  jobType: MemoryJobType;
778
768
  }> = v2Active
779
- ? v2ConsolidationAutomaticEnabled
780
- ? [consolidateEntry]
781
- : []
769
+ ? [consolidateEntry]
782
770
  : [
783
771
  {
784
772
  key: GRAPH_MAINTENANCE_CHECKPOINTS.decay,
@@ -807,8 +795,8 @@ export function maybeEnqueueGraphMaintenanceJobs(
807
795
  // runs under either branch. Gated on the same flags that gate the v3 plugin
808
796
  // so it stays inert when v3 is off. The post-consolidation follow-up in
809
797
  // `consolidation-job.ts` remains the primary trigger; this interval only
810
- // self-heals when that follow-up is missed (failed enqueue, or v3-on with v2
811
- // consolidation disabled). The job handler itself no-ops when v3 is off, so
798
+ // self-heals when that follow-up is missed (failed enqueue). The job handler
799
+ // itself no-ops when v3 is off, so
812
800
  // this guard is belt-and-suspenders that also avoids a wasted enqueue.
813
801
  if (
814
802
  isAssistantFeatureFlagEnabled("memory-v3-shadow", config) ||
@@ -859,7 +847,7 @@ export function maybeEnqueueGraphMaintenanceJobs(
859
847
  // buffer stays over threshold, flooding the queue with redundant LLM work.
860
848
  const maxLines = config.memory.v2.consolidation_max_buffer_lines;
861
849
  if (
862
- v2ConsolidationAutomaticEnabled &&
850
+ v2Active &&
863
851
  !enqueuedConsolidate &&
864
852
  maxLines !== null &&
865
853
  !hasActiveJobOfType(consolidateEntry.jobType)
@@ -15,3 +15,32 @@
15
15
  export function wrapMemoryBlock(text: string): string {
16
16
  return `<memory>\n${text}\n</memory>`;
17
17
  }
18
+
19
+ /**
20
+ * Inverse of {@link wrapMemoryBlock}: recover the inner text from a wrapped
21
+ * block. Only unwraps when the full `<memory>\n…\n</memory>` pair is present,
22
+ * so payloads that merely start with the opening tag pass through unchanged —
23
+ * the same defensive guard the metadata rehydration in
24
+ * `daemon/conversation.ts` applies.
25
+ */
26
+ export function unwrapMemoryBlock(block: string): string {
27
+ return block.startsWith("<memory>\n") && block.endsWith("\n</memory>")
28
+ ? block.slice("<memory>\n".length, -"\n</memory>".length)
29
+ : block;
30
+ }
31
+
32
+ /**
33
+ * The memory-v3 ephemeral spotlight wrapper. Unlike `<memory>` card blocks
34
+ * (frozen into history), the spotlight block is re-rendered onto the current
35
+ * user tail every turn: the per-turn scoped strip in
36
+ * `context/strip-injections.ts` (`stripSpotlightInjections`) and the
37
+ * compaction matcher in `RUNTIME_INJECTION_PREFIXES` both key off this exact
38
+ * prefix/suffix pair, so the producer wrapper lives here beside the `<memory>`
39
+ * marker it parallels.
40
+ */
41
+ export const MEMORY_SPOTLIGHT_PREFIX = "<memory_spotlight>\n";
42
+ export const MEMORY_SPOTLIGHT_SUFFIX = "\n</memory_spotlight>";
43
+
44
+ export function wrapMemorySpotlightBlock(text: string): string {
45
+ return `${MEMORY_SPOTLIGHT_PREFIX}${text}${MEMORY_SPOTLIGHT_SUFFIX}`;
46
+ }
@@ -93,7 +93,7 @@ export function sweepOrphanMemoryRetrospectiveConversations(
93
93
  // Compute the most-recent retro per source so we can preserve it.
94
94
  // `findMostRecentRetrospectiveFor` (called by the next retrospective run)
95
95
  // pulls dedup context from this row; sweeping it would re-introduce the
96
- // unbounded-growth bug PR #30331 was created to fix.
96
+ // unbounded retrospective growth this cleanup exists to prevent.
97
97
  const allRetros = db
98
98
  .select({
99
99
  id: conversations.id,
@@ -6,6 +6,12 @@ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
6
6
  * tagged with the lane (`source`) that produced it. `pinned` marks slugs
7
7
  * carried forward across turns rather than re-selected cold.
8
8
  *
9
+ * HISTORICAL NOTE: the working-set carry this table originally backed was
10
+ * removed (selections are current-turn only now; cross-turn persistence lives
11
+ * in the injector's frozen net-new blocks). The table itself is still live —
12
+ * the shadow plugin logs per-turn selections to it and the inspector /
13
+ * `summarizeSelections` read from it.
14
+ *
9
15
  * Idempotent — re-running is a no-op once the table and index exist.
10
16
  */
11
17
  export function migrateAddMemoryV3Selections(database: DrizzleDb): void {
@@ -0,0 +1,36 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+ import { withCrashRecovery } from "./validate-migration-state.js";
3
+
4
+ const CHECKPOINT_KEY = "migration_schedule_description_backfill_v1";
5
+
6
+ export function migrateScheduleDescription(database: DrizzleDb): void {
7
+ const raw = getSqliteFrom(database);
8
+ raw.exec(/*sql*/ `
9
+ CREATE TABLE IF NOT EXISTS memory_checkpoints (
10
+ key TEXT PRIMARY KEY,
11
+ value TEXT NOT NULL,
12
+ updated_at INTEGER NOT NULL
13
+ )
14
+ `);
15
+ try {
16
+ raw.exec(
17
+ `ALTER TABLE cron_jobs ADD COLUMN description TEXT NOT NULL DEFAULT ''`,
18
+ );
19
+ } catch {
20
+ // Column already exists.
21
+ }
22
+
23
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
24
+ raw
25
+ .query(
26
+ `UPDATE cron_jobs
27
+ SET description = name
28
+ WHERE created_by <> 'defer' AND description = ''`,
29
+ )
30
+ .run();
31
+ });
32
+ }
33
+
34
+ export function downScheduleDescription(): void {
35
+ // Append-only schema migration; the column and backfill are intentionally kept.
36
+ }
@@ -0,0 +1,81 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import * as schema from "../schema.js";
7
+ import { migrateToolInvocationsSkillId } from "./275-tool-invocations-add-skill-id.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ // Pre-275 shape: no skill_id column.
12
+ sqlite.exec(/*sql*/ `
13
+ CREATE TABLE tool_invocations (
14
+ id TEXT PRIMARY KEY,
15
+ conversation_id TEXT NOT NULL,
16
+ tool_name TEXT NOT NULL,
17
+ input TEXT NOT NULL,
18
+ result TEXT NOT NULL,
19
+ decision TEXT NOT NULL,
20
+ risk_level TEXT NOT NULL,
21
+ matched_trust_rule_id TEXT,
22
+ duration_ms INTEGER NOT NULL,
23
+ created_at INTEGER NOT NULL
24
+ )
25
+ `);
26
+ return { sqlite, db: drizzle(sqlite, { schema }) };
27
+ }
28
+
29
+ function columnNames(sqlite: Database): string[] {
30
+ return (
31
+ sqlite.query("PRAGMA table_info(tool_invocations)").all() as Array<{
32
+ name: string;
33
+ }>
34
+ ).map((c) => c.name);
35
+ }
36
+
37
+ describe("migration 275: tool_invocations skill_id", () => {
38
+ test("adds a nullable skill_id column", () => {
39
+ const { sqlite, db } = createTestDb();
40
+ expect(columnNames(sqlite)).not.toContain("skill_id");
41
+
42
+ migrateToolInvocationsSkillId(db);
43
+
44
+ const column = (
45
+ sqlite.query("PRAGMA table_info(tool_invocations)").all() as Array<{
46
+ name: string;
47
+ notnull: number;
48
+ }>
49
+ ).find((c) => c.name === "skill_id");
50
+ expect(column).toBeDefined();
51
+ expect(column?.notnull).toBe(0);
52
+ });
53
+
54
+ test("existing rows read back with skill_id null", () => {
55
+ const { sqlite, db } = createTestDb();
56
+ sqlite.exec(/*sql*/ `
57
+ INSERT INTO tool_invocations
58
+ (id, conversation_id, tool_name, input, result, decision, risk_level, duration_ms, created_at)
59
+ VALUES
60
+ ('ti-1', 'conv-1', 'bash', '{}', 'ok', 'allow', 'low', 5, 1000)
61
+ `);
62
+
63
+ migrateToolInvocationsSkillId(db);
64
+
65
+ const row = sqlite
66
+ .query("SELECT skill_id FROM tool_invocations WHERE id = 'ti-1'")
67
+ .get() as { skill_id: string | null };
68
+ expect(row.skill_id).toBeNull();
69
+ });
70
+
71
+ test("is idempotent — re-run is a no-op", () => {
72
+ const { sqlite, db } = createTestDb();
73
+
74
+ migrateToolInvocationsSkillId(db);
75
+ expect(() => migrateToolInvocationsSkillId(db)).not.toThrow();
76
+
77
+ expect(
78
+ columnNames(sqlite).filter((name) => name === "skill_id"),
79
+ ).toHaveLength(1);
80
+ });
81
+ });
@@ -0,0 +1,20 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { tableHasColumn } from "./schema-introspection.js";
3
+
4
+ /**
5
+ * Add a nullable `skill_id` column to `tool_invocations`.
6
+ *
7
+ * Records which skill's `skill_execute` dispatch triggered the tool call so
8
+ * the `tool_execution` telemetry event can attribute skill-routed
9
+ * invocations. `NULL` for direct (non-skill) tool calls and for rows
10
+ * persisted before this migration ran.
11
+ *
12
+ * Idempotent — re-running is a no-op once the column exists. Pure DDL with a
13
+ * PRAGMA guard, no registry entry needed (matches the 273 pattern).
14
+ */
15
+ export function migrateToolInvocationsSkillId(database: DrizzleDb): void {
16
+ if (tableHasColumn(database, "tool_invocations", "skill_id")) {
17
+ return;
18
+ }
19
+ database.run(`ALTER TABLE tool_invocations ADD COLUMN skill_id TEXT`);
20
+ }
@@ -0,0 +1,68 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import * as schema from "../schema.js";
7
+ import { migrateToolInvocationsCreatedAtIdIndex } from "./276-tool-invocations-created-at-id-index.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ // Pre-276 shape: no (created_at, id) index.
12
+ sqlite.exec(/*sql*/ `
13
+ CREATE TABLE tool_invocations (
14
+ id TEXT PRIMARY KEY,
15
+ conversation_id TEXT NOT NULL,
16
+ tool_name TEXT NOT NULL,
17
+ input TEXT NOT NULL,
18
+ result TEXT NOT NULL,
19
+ decision TEXT NOT NULL,
20
+ risk_level TEXT NOT NULL,
21
+ matched_trust_rule_id TEXT,
22
+ skill_id TEXT,
23
+ duration_ms INTEGER NOT NULL,
24
+ created_at INTEGER NOT NULL
25
+ )
26
+ `);
27
+ return { sqlite, db: drizzle(sqlite, { schema }) };
28
+ }
29
+
30
+ function indexNames(sqlite: Database): string[] {
31
+ return (
32
+ sqlite.query("PRAGMA index_list(tool_invocations)").all() as Array<{
33
+ name: string;
34
+ }>
35
+ ).map((i) => i.name);
36
+ }
37
+
38
+ describe("migration 276: tool_invocations (created_at, id) index", () => {
39
+ test("creates the index on (created_at, id)", () => {
40
+ const { sqlite, db } = createTestDb();
41
+ expect(indexNames(sqlite)).not.toContain(
42
+ "idx_tool_invocations_created_at_id",
43
+ );
44
+
45
+ migrateToolInvocationsCreatedAtIdIndex(db);
46
+
47
+ expect(indexNames(sqlite)).toContain("idx_tool_invocations_created_at_id");
48
+ const columns = (
49
+ sqlite
50
+ .query("PRAGMA index_info(idx_tool_invocations_created_at_id)")
51
+ .all() as Array<{ name: string }>
52
+ ).map((c) => c.name);
53
+ expect(columns).toEqual(["created_at", "id"]);
54
+ });
55
+
56
+ test("is idempotent — re-run is a no-op", () => {
57
+ const { sqlite, db } = createTestDb();
58
+
59
+ migrateToolInvocationsCreatedAtIdIndex(db);
60
+ expect(() => migrateToolInvocationsCreatedAtIdIndex(db)).not.toThrow();
61
+
62
+ expect(
63
+ indexNames(sqlite).filter(
64
+ (name) => name === "idx_tool_invocations_created_at_id",
65
+ ),
66
+ ).toHaveLength(1);
67
+ });
68
+ });
@@ -0,0 +1,20 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Indexes `tool_invocations(created_at, id)` so the tool_execution telemetry
5
+ * cursor can seek to the next unreported batch instead of scanning the table.
6
+ *
7
+ * `queryUnreportedToolExecutionEvents` filters and orders by `(created_at, id)`
8
+ * every telemetry flush (~5 min); without this index each flush scans and
9
+ * sorts the whole audit table, which grows unboundedly by default and carries
10
+ * large `input`/`result` text blobs.
11
+ *
12
+ * Idempotent — `CREATE INDEX IF NOT EXISTS` is a no-op once the index exists.
13
+ */
14
+ export function migrateToolInvocationsCreatedAtIdIndex(
15
+ database: DrizzleDb,
16
+ ): void {
17
+ database.run(
18
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_tool_invocations_created_at_id ON tool_invocations (created_at, id)`,
19
+ );
20
+ }
@@ -0,0 +1,29 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+
3
+ /**
4
+ * Adds `memory_v3_ever_injected` for memory-v3's frozen-card carry: one row
5
+ * per (conversation, page-slug) the v3 injector ever attached as a card.
6
+ * `bytes` is the rendered card size used for resident-footprint accounting;
7
+ * `pruned_at` marks rows the prune valve removed from the live context (rows
8
+ * are never deleted — the record stays auditable, and a pruned page that is
9
+ * re-selected re-injects by clearing `pruned_at`).
10
+ *
11
+ * Idempotent — re-running is a no-op once the table and index exist.
12
+ */
13
+ export function migrateAddMemoryV3EverInjected(database: DrizzleDb): void {
14
+ const raw = getSqliteFrom(database);
15
+ raw.exec(/*sql*/ `
16
+ CREATE TABLE IF NOT EXISTS memory_v3_ever_injected (
17
+ conversation_id TEXT NOT NULL,
18
+ slug TEXT NOT NULL,
19
+ injected_at INTEGER NOT NULL,
20
+ bytes INTEGER NOT NULL DEFAULT 0,
21
+ pruned_at INTEGER,
22
+ PRIMARY KEY (conversation_id, slug)
23
+ )
24
+ `);
25
+ raw.exec(/*sql*/ `
26
+ CREATE INDEX IF NOT EXISTS idx_memory_v3_ever_injected_conv
27
+ ON memory_v3_ever_injected (conversation_id)
28
+ `);
29
+ }
@@ -0,0 +1,96 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import * as schema from "../schema.js";
7
+ import { migrateToolInvocationsTelemetryColumns } from "./278-tool-invocations-telemetry-columns.js";
8
+
9
+ const NEW_COLUMNS = [
10
+ "arg_bytes",
11
+ "result_bytes",
12
+ "provider",
13
+ "model",
14
+ "inference_profile",
15
+ "inference_profile_source",
16
+ ];
17
+
18
+ function createTestDb() {
19
+ const sqlite = new Database(":memory:");
20
+ // Pre-278 shape: skill_id exists (275), telemetry columns do not.
21
+ sqlite.exec(/*sql*/ `
22
+ CREATE TABLE tool_invocations (
23
+ id TEXT PRIMARY KEY,
24
+ conversation_id TEXT NOT NULL,
25
+ tool_name TEXT NOT NULL,
26
+ input TEXT NOT NULL,
27
+ result TEXT NOT NULL,
28
+ decision TEXT NOT NULL,
29
+ risk_level TEXT NOT NULL,
30
+ matched_trust_rule_id TEXT,
31
+ duration_ms INTEGER NOT NULL,
32
+ created_at INTEGER NOT NULL,
33
+ skill_id TEXT
34
+ )
35
+ `);
36
+ return { sqlite, db: drizzle(sqlite, { schema }) };
37
+ }
38
+
39
+ function tableInfo(sqlite: Database) {
40
+ return sqlite.query("PRAGMA table_info(tool_invocations)").all() as Array<{
41
+ name: string;
42
+ notnull: number;
43
+ }>;
44
+ }
45
+
46
+ describe("migration 278: tool_invocations telemetry columns", () => {
47
+ test("adds the nullable telemetry columns", () => {
48
+ const { sqlite, db } = createTestDb();
49
+ const before = tableInfo(sqlite).map((c) => c.name);
50
+ for (const name of NEW_COLUMNS) {
51
+ expect(before).not.toContain(name);
52
+ }
53
+
54
+ migrateToolInvocationsTelemetryColumns(db);
55
+
56
+ const after = tableInfo(sqlite);
57
+ for (const name of NEW_COLUMNS) {
58
+ const column = after.find((c) => c.name === name);
59
+ expect(column).toBeDefined();
60
+ expect(column?.notnull).toBe(0);
61
+ }
62
+ });
63
+
64
+ test("existing rows read back with null telemetry columns", () => {
65
+ const { sqlite, db } = createTestDb();
66
+ sqlite.exec(/*sql*/ `
67
+ INSERT INTO tool_invocations
68
+ (id, conversation_id, tool_name, input, result, decision, risk_level, duration_ms, created_at)
69
+ VALUES
70
+ ('ti-1', 'conv-1', 'bash', '{}', 'ok', 'allow', 'low', 5, 1000)
71
+ `);
72
+
73
+ migrateToolInvocationsTelemetryColumns(db);
74
+
75
+ const row = sqlite
76
+ .query(
77
+ `SELECT ${NEW_COLUMNS.join(", ")} FROM tool_invocations WHERE id = 'ti-1'`,
78
+ )
79
+ .get() as Record<string, unknown>;
80
+ for (const name of NEW_COLUMNS) {
81
+ expect(row[name]).toBeNull();
82
+ }
83
+ });
84
+
85
+ test("is idempotent — re-run is a no-op", () => {
86
+ const { sqlite, db } = createTestDb();
87
+
88
+ migrateToolInvocationsTelemetryColumns(db);
89
+ expect(() => migrateToolInvocationsTelemetryColumns(db)).not.toThrow();
90
+
91
+ const names = tableInfo(sqlite).map((c) => c.name);
92
+ for (const name of NEW_COLUMNS) {
93
+ expect(names.filter((n) => n === name)).toHaveLength(1);
94
+ }
95
+ });
96
+ });