@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -9,6 +9,13 @@ import type {
9
9
  import { APP_VERSION } from "../version.js";
10
10
  import { getDb } from "./db-connection.js";
11
11
  import { rawAll } from "./raw-query.js";
12
+ import {
13
+ buildScheduleAttributionSubquery,
14
+ buildScheduleRunWindowExists,
15
+ normalizeScheduleAttributionFilter,
16
+ type ScheduleAttributionFilter,
17
+ type ScheduleAttributionSqlParam,
18
+ } from "./schedule-attribution-sql.js";
12
19
  import { conversations, llmUsageEvents } from "./schema.js";
13
20
  import {
14
21
  bucketEventsByDay,
@@ -282,6 +289,8 @@ export interface UsageTimeRange {
282
289
  to: number;
283
290
  }
284
291
 
292
+ export type UsageAggregationFilter = ScheduleAttributionFilter;
293
+
285
294
  /** Aggregate totals across a time range. */
286
295
  export interface UsageTotals {
287
296
  /** Direct input tokens only; cache traffic is reported separately below. */
@@ -332,14 +341,15 @@ export interface UsageGroupBreakdown {
332
341
  /**
333
342
  * Stable identifier for the group. Populated with the conversation id when
334
343
  * `groupBy === "conversation"` (and `null` for that mode's "Other" bucket,
335
- * which aggregates events with no conversation id). For all other group-bys
336
- * this is always `null`.
344
+ * which aggregates events with no conversation id) or with the schedule id
345
+ * when `groupBy === "schedule"` (and `null` for "Other"). For all other
346
+ * group-bys this is always `null`.
337
347
  */
338
348
  groupId: string | null;
339
349
  /**
340
350
  * Raw stored grouping value for dimensions whose display label may differ
341
- * from storage (`call_site`, `inference_profile`). Omitted for legacy
342
- * dimensions where `group` is already the raw value.
351
+ * from storage (`call_site`, `inference_profile`, `schedule`). Omitted for
352
+ * legacy dimensions where `group` is already the raw value.
343
353
  */
344
354
  groupKey?: string | null;
345
355
  /** Direct input tokens only; cache traffic is reported separately below. */
@@ -367,6 +377,7 @@ interface TotalsRow {
367
377
  interface GroupRow {
368
378
  group_key: string | null;
369
379
  group_id: string | null;
380
+ group_label: string | null;
370
381
  total_input_tokens: number;
371
382
  total_output_tokens: number;
372
383
  total_cache_creation_tokens: number;
@@ -375,6 +386,39 @@ interface GroupRow {
375
386
  event_count: number;
376
387
  }
377
388
 
389
+ type UsageQueryParam = ScheduleAttributionSqlParam;
390
+
391
+ function normalizeUsageAggregationFilter(
392
+ filter?: UsageAggregationFilter,
393
+ ): UsageAggregationFilter {
394
+ return normalizeScheduleAttributionFilter(filter);
395
+ }
396
+
397
+ function buildUsageAggregationWhere(
398
+ range: UsageTimeRange,
399
+ filter?: UsageAggregationFilter,
400
+ eventAlias?: string,
401
+ now: number = Date.now(),
402
+ ): { sql: string; params: UsageQueryParam[] } {
403
+ const normalized = normalizeUsageAggregationFilter(filter);
404
+ const eventTable = eventAlias ?? "llm_usage_events";
405
+ const createdAt = `${eventTable}.created_at`;
406
+ const clauses = [`${createdAt} >= ? AND ${createdAt} <= ?`];
407
+ const params: UsageQueryParam[] = [range.from, range.to];
408
+
409
+ if (normalized.scheduleId) {
410
+ const exists = buildScheduleRunWindowExists({
411
+ eventAlias: eventTable,
412
+ filter: normalized,
413
+ now,
414
+ });
415
+ clauses.push(exists.sql);
416
+ params.push(...exists.params);
417
+ }
418
+
419
+ return { sql: clauses.join(" AND "), params };
420
+ }
421
+
378
422
  /**
379
423
  * Return aggregate usage for a single conversation (e.g. a subagent).
380
424
  */
@@ -406,10 +450,38 @@ export function getConversationUsageTotals(conversationId: string): {
406
450
  };
407
451
  }
408
452
 
453
+ export function getUsageCostForConversationWindow({
454
+ conversationId,
455
+ from,
456
+ to,
457
+ }: {
458
+ conversationId: string;
459
+ from: number;
460
+ to: number;
461
+ }): number {
462
+ const rows = rawAll<{ total_cost: number | null }>(
463
+ /*sql*/ `
464
+ SELECT COALESCE(SUM(estimated_cost_usd), 0) AS total_cost
465
+ FROM llm_usage_events
466
+ WHERE conversation_id = ?1
467
+ AND created_at >= ?2
468
+ AND created_at <= ?3
469
+ `,
470
+ conversationId,
471
+ from,
472
+ to,
473
+ );
474
+ return rows[0]?.total_cost ?? 0;
475
+ }
476
+
409
477
  /**
410
478
  * Return aggregate totals for all usage events within the given time range.
411
479
  */
412
- export function getUsageTotals(range: UsageTimeRange): UsageTotals {
480
+ export function getUsageTotals(
481
+ range: UsageTimeRange,
482
+ filter?: UsageAggregationFilter,
483
+ ): UsageTotals {
484
+ const where = buildUsageAggregationWhere(range, filter);
413
485
  const rows = rawAll<TotalsRow>(
414
486
  /*sql*/ `
415
487
  SELECT
@@ -422,10 +494,9 @@ export function getUsageTotals(range: UsageTimeRange): UsageTotals {
422
494
  COUNT(CASE WHEN pricing_status = 'priced' THEN 1 END) AS priced_event_count,
423
495
  COUNT(CASE WHEN pricing_status = 'unpriced' THEN 1 END) AS unpriced_event_count
424
496
  FROM llm_usage_events
425
- WHERE created_at >= ?1 AND created_at <= ?2
497
+ WHERE ${where.sql}
426
498
  `,
427
- range.from,
428
- range.to,
499
+ ...where.params,
429
500
  );
430
501
  const row = rows[0];
431
502
  return {
@@ -441,7 +512,11 @@ export function getUsageTotals(range: UsageTimeRange): UsageTotals {
441
512
  }
442
513
 
443
514
  /** Fetch raw events in a time range for in-memory bucketing. */
444
- function fetchRawBucketRows(range: UsageTimeRange): UsageEventBucketRow[] {
515
+ function fetchRawBucketRows(
516
+ range: UsageTimeRange,
517
+ filter?: UsageAggregationFilter,
518
+ ): UsageEventBucketRow[] {
519
+ const where = buildUsageAggregationWhere(range, filter);
445
520
  return rawAll<UsageEventBucketRow>(
446
521
  /*sql*/ `
447
522
  SELECT
@@ -451,11 +526,10 @@ function fetchRawBucketRows(range: UsageTimeRange): UsageEventBucketRow[] {
451
526
  estimated_cost_usd,
452
527
  llm_call_count
453
528
  FROM llm_usage_events
454
- WHERE created_at >= ?1 AND created_at <= ?2
529
+ WHERE ${where.sql}
455
530
  ORDER BY created_at ASC
456
531
  `,
457
- range.from,
458
- range.to,
532
+ ...where.params,
459
533
  );
460
534
  }
461
535
 
@@ -482,8 +556,9 @@ export function getUsageDayBuckets(
482
556
  range: UsageTimeRange,
483
557
  tz: string = "UTC",
484
558
  options: UsageBucketOptions = {},
559
+ filter?: UsageAggregationFilter,
485
560
  ): UsageDayBucket[] {
486
- const rows = fetchRawBucketRows(range);
561
+ const rows = fetchRawBucketRows(range, filter);
487
562
  return bucketEventsByDay(rows, range, tz, options);
488
563
  }
489
564
 
@@ -500,8 +575,9 @@ export function getUsageHourBuckets(
500
575
  range: UsageTimeRange,
501
576
  tz: string = "UTC",
502
577
  options: UsageBucketOptions = {},
578
+ filter?: UsageAggregationFilter,
503
579
  ): UsageDayBucket[] {
504
- const rows = fetchRawBucketRows(range);
580
+ const rows = fetchRawBucketRows(range, filter);
505
581
  return bucketEventsByHour(rows, range, tz, options);
506
582
  }
507
583
 
@@ -512,6 +588,7 @@ export const USAGE_GROUP_BY_DIMENSIONS = [
512
588
  "conversation",
513
589
  "call_site",
514
590
  "inference_profile",
591
+ "schedule",
515
592
  ] as const;
516
593
 
517
594
  export type GroupByDimension = (typeof USAGE_GROUP_BY_DIMENSIONS)[number];
@@ -522,10 +599,11 @@ export const USAGE_SERIES_GROUP_BY_DIMENSIONS = [
522
599
  "model",
523
600
  "call_site",
524
601
  "inference_profile",
602
+ "schedule",
525
603
  ] as const satisfies readonly GroupByDimension[];
526
604
 
527
605
  const GROUP_BY_COLUMNS: Record<
528
- Exclude<GroupByDimension, "conversation">,
606
+ Exclude<GroupByDimension, "conversation" | "schedule">,
529
607
  string
530
608
  > = {
531
609
  actor: "actor",
@@ -550,9 +628,11 @@ function mapGroupRow(
550
628
  groupBy: GroupByDimension,
551
629
  ): UsageGroupBreakdown {
552
630
  const includeGroupKey =
553
- groupBy === "call_site" || groupBy === "inference_profile";
631
+ groupBy === "call_site" ||
632
+ groupBy === "inference_profile" ||
633
+ groupBy === "schedule";
554
634
  return {
555
- group: displayUsageGroup(groupBy, row.group_key),
635
+ group: row.group_label ?? displayUsageGroup(groupBy, row.group_key),
556
636
  groupId: row.group_id,
557
637
  ...(includeGroupKey ? { groupKey: row.group_key } : {}),
558
638
  totalInputTokens: row.total_input_tokens,
@@ -571,12 +651,16 @@ function mapGroupRow(
571
651
  export function getUsageGroupBreakdown(
572
652
  range: UsageTimeRange,
573
653
  groupBy: GroupByDimension,
654
+ filter?: UsageAggregationFilter,
574
655
  ): UsageGroupBreakdown[] {
575
656
  // Runtime allowlist — defense-in-depth against SQL injection via type assertions.
576
657
  assertGroupByDimension(groupBy);
577
658
 
659
+ const normalizedFilter = normalizeUsageAggregationFilter(filter);
660
+
578
661
  // Conversation grouping requires a JOIN with conversations to resolve titles.
579
662
  if (groupBy === "conversation") {
663
+ const where = buildUsageAggregationWhere(range, normalizedFilter, "e");
580
664
  const rows = rawAll<GroupRow>(
581
665
  /*sql*/ `
582
666
  SELECT
@@ -584,6 +668,7 @@ export function getUsageGroupBreakdown(
584
668
  ELSE COALESCE(c.title, 'Untitled')
585
669
  END AS group_key,
586
670
  e.conversation_id AS group_id,
671
+ NULL AS group_label,
587
672
  COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
588
673
  COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
589
674
  COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
@@ -592,36 +677,81 @@ export function getUsageGroupBreakdown(
592
677
  COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
593
678
  FROM llm_usage_events e
594
679
  LEFT JOIN conversations c ON e.conversation_id = c.id
595
- WHERE e.created_at >= ?1 AND e.created_at <= ?2
680
+ WHERE ${where.sql}
596
681
  GROUP BY e.conversation_id
597
682
  ORDER BY total_estimated_cost_usd DESC
598
683
  LIMIT 50
599
684
  `,
600
- range.from,
601
- range.to,
685
+ ...where.params,
686
+ );
687
+ return rows.map((row) => mapGroupRow(row, groupBy));
688
+ }
689
+
690
+ if (groupBy === "schedule") {
691
+ const now = Date.now();
692
+ const where = buildUsageAggregationWhere(range, normalizedFilter, "e", now);
693
+ const groupKeySubquery = buildScheduleAttributionSubquery({
694
+ eventAlias: "e",
695
+ filter: normalizedFilter,
696
+ now,
697
+ selectExpression: "schedule_attr_runs.job_id",
698
+ });
699
+ const rows = rawAll<GroupRow>(
700
+ /*sql*/ `
701
+ WITH schedule_usage AS (
702
+ SELECT
703
+ e.input_tokens,
704
+ e.output_tokens,
705
+ e.cache_creation_input_tokens,
706
+ e.cache_read_input_tokens,
707
+ e.estimated_cost_usd,
708
+ e.llm_call_count,
709
+ ${groupKeySubquery.sql} AS group_key
710
+ FROM llm_usage_events e
711
+ WHERE ${where.sql}
712
+ )
713
+ SELECT
714
+ schedule_usage.group_key AS group_key,
715
+ schedule_usage.group_key AS group_id,
716
+ MAX(schedule_group_jobs.name) AS group_label,
717
+ COALESCE(SUM(schedule_usage.input_tokens), 0) AS total_input_tokens,
718
+ COALESCE(SUM(schedule_usage.output_tokens), 0) AS total_output_tokens,
719
+ COALESCE(SUM(schedule_usage.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
720
+ COALESCE(SUM(schedule_usage.cache_read_input_tokens), 0) AS total_cache_read_tokens,
721
+ COALESCE(SUM(schedule_usage.estimated_cost_usd), 0) AS total_estimated_cost_usd,
722
+ COALESCE(SUM(COALESCE(schedule_usage.llm_call_count, 1)), 0) AS event_count
723
+ FROM schedule_usage
724
+ LEFT JOIN cron_jobs schedule_group_jobs
725
+ ON schedule_group_jobs.id = schedule_usage.group_key
726
+ GROUP BY schedule_usage.group_key
727
+ ORDER BY total_estimated_cost_usd DESC
728
+ `,
729
+ ...groupKeySubquery.params,
730
+ ...where.params,
602
731
  );
603
732
  return rows.map((row) => mapGroupRow(row, groupBy));
604
733
  }
605
734
 
606
735
  const column = GROUP_BY_COLUMNS[groupBy];
736
+ const where = buildUsageAggregationWhere(range, normalizedFilter, "e");
607
737
  const rows = rawAll<GroupRow>(
608
738
  /*sql*/ `
609
739
  SELECT
610
- ${column} AS group_key,
611
- NULL AS group_id,
612
- COALESCE(SUM(input_tokens), 0) AS total_input_tokens,
613
- COALESCE(SUM(output_tokens), 0) AS total_output_tokens,
614
- COALESCE(SUM(cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
615
- COALESCE(SUM(cache_read_input_tokens), 0) AS total_cache_read_tokens,
616
- COALESCE(SUM(estimated_cost_usd), 0) AS total_estimated_cost_usd,
617
- COALESCE(SUM(COALESCE(llm_call_count, 1)), 0) AS event_count
618
- FROM llm_usage_events
619
- WHERE created_at >= ?1 AND created_at <= ?2
620
- GROUP BY ${column}
740
+ e.${column} AS group_key,
741
+ NULL AS group_id,
742
+ NULL AS group_label,
743
+ COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
744
+ COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
745
+ COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
746
+ COALESCE(SUM(e.cache_read_input_tokens), 0) AS total_cache_read_tokens,
747
+ COALESCE(SUM(e.estimated_cost_usd), 0) AS total_estimated_cost_usd,
748
+ COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
749
+ FROM llm_usage_events e
750
+ WHERE ${where.sql}
751
+ GROUP BY e.${column}
621
752
  ORDER BY total_estimated_cost_usd DESC
622
753
  `,
623
- range.from,
624
- range.to,
754
+ ...where.params,
625
755
  );
626
756
  return rows.map((row) => mapGroupRow(row, groupBy));
627
757
  }
@@ -632,29 +762,73 @@ export function getUsageGroupedSeries(
632
762
  granularity: UsageGranularity,
633
763
  tz: string = "UTC",
634
764
  options: UsageBucketOptions = {},
765
+ filter?: UsageAggregationFilter,
635
766
  ): UsageGroupedSeriesBucket[] {
636
767
  assertGroupByDimension(groupBy);
637
768
  if (groupBy === "conversation") {
638
769
  throw new Error("Grouped usage series does not support conversation");
639
770
  }
640
771
 
641
- const column = GROUP_BY_COLUMNS[groupBy];
642
- const rows = rawAll<UsageGroupedBucketRow>(
643
- /*sql*/ `
644
- SELECT
645
- created_at,
646
- input_tokens,
647
- output_tokens,
648
- estimated_cost_usd,
649
- llm_call_count,
650
- ${column} AS group_key
651
- FROM llm_usage_events
652
- WHERE created_at >= ?1 AND created_at <= ?2
653
- ORDER BY created_at ASC
654
- `,
655
- range.from,
656
- range.to,
657
- );
772
+ const normalizedFilter = normalizeUsageAggregationFilter(filter);
773
+ let rows: UsageGroupedBucketRow[];
774
+
775
+ if (groupBy === "schedule") {
776
+ const now = Date.now();
777
+ const where = buildUsageAggregationWhere(range, normalizedFilter, "e", now);
778
+ const groupKeySubquery = buildScheduleAttributionSubquery({
779
+ eventAlias: "e",
780
+ filter: normalizedFilter,
781
+ now,
782
+ selectExpression: "schedule_attr_runs.job_id",
783
+ });
784
+ rows = rawAll<UsageGroupedBucketRow>(
785
+ /*sql*/ `
786
+ WITH schedule_usage AS (
787
+ SELECT
788
+ e.created_at,
789
+ e.input_tokens,
790
+ e.output_tokens,
791
+ e.estimated_cost_usd,
792
+ e.llm_call_count,
793
+ ${groupKeySubquery.sql} AS group_key
794
+ FROM llm_usage_events e
795
+ WHERE ${where.sql}
796
+ )
797
+ SELECT
798
+ schedule_usage.created_at,
799
+ schedule_usage.input_tokens,
800
+ schedule_usage.output_tokens,
801
+ schedule_usage.estimated_cost_usd,
802
+ schedule_usage.llm_call_count,
803
+ schedule_usage.group_key,
804
+ schedule_group_jobs.name AS group_label
805
+ FROM schedule_usage
806
+ LEFT JOIN cron_jobs schedule_group_jobs
807
+ ON schedule_group_jobs.id = schedule_usage.group_key
808
+ ORDER BY schedule_usage.created_at ASC
809
+ `,
810
+ ...groupKeySubquery.params,
811
+ ...where.params,
812
+ );
813
+ } else {
814
+ const column = GROUP_BY_COLUMNS[groupBy];
815
+ const where = buildUsageAggregationWhere(range, normalizedFilter, "e");
816
+ rows = rawAll<UsageGroupedBucketRow>(
817
+ /*sql*/ `
818
+ SELECT
819
+ e.created_at,
820
+ e.input_tokens,
821
+ e.output_tokens,
822
+ e.estimated_cost_usd,
823
+ e.llm_call_count,
824
+ e.${column} AS group_key
825
+ FROM llm_usage_events e
826
+ WHERE ${where.sql}
827
+ ORDER BY e.created_at ASC
828
+ `,
829
+ ...where.params,
830
+ );
831
+ }
658
832
 
659
833
  return bucketGroupedUsageEvents(rows, range, tz, {
660
834
  ...options,
@@ -1,15 +1,16 @@
1
- import { isPlaceholderSentinelText } from "../../providers/anthropic/client.js";
1
+ import { isPlaceholderSentinelText } from "../../providers/placeholder-sentinels.js";
2
2
  import type { DrizzleDb } from "../db-connection.js";
3
3
  import { getSqliteFrom } from "../db-connection.js";
4
4
  import { withCrashRecovery } from "./validate-migration-state.js";
5
5
 
6
6
  /**
7
- * Strip Anthropic provider placeholder sentinel text blocks from persisted
8
- * assistant messages.
7
+ * Strip provider placeholder sentinel text blocks from persisted assistant
8
+ * messages.
9
9
  *
10
10
  * PLACEHOLDER_EMPTY_TURN and PLACEHOLDER_BLOCKS_OMITTED are injected into
11
- * outbound Anthropic request bodies to preserve role alternation when an
12
- * assistant turn would otherwise be empty. They are never supposed to be
11
+ * outbound provider request bodies (Anthropic role alternation, OpenAI-
12
+ * compatible "content or tool_calls" constraint) when an assistant turn would
13
+ * otherwise be empty. They are never supposed to be
13
14
  * persisted, but a leak path caused them to be stored in the messages table
14
15
  * where they render in chat bubbles as bold "PLACEHOLDER[...]" (markdown
15
16
  * interprets the double-underscore surround as bold).
@@ -0,0 +1,13 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateScheduleSourceConversation(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(
8
+ `ALTER TABLE cron_jobs ADD COLUMN created_from_conversation_id TEXT`,
9
+ );
10
+ } catch {
11
+ // Column already exists — nothing to do.
12
+ }
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Create the auth_fallback_events table for tracking legacy-loopback
5
+ * auth-fallback counts forwarded by the gateway. One row per
6
+ * (guard, path, failure_kind) per flush window.
7
+ */
8
+ export function createAuthFallbackEventsTable(database: DrizzleDb): void {
9
+ database.run(/*sql*/ `
10
+ CREATE TABLE IF NOT EXISTS auth_fallback_events (
11
+ id TEXT PRIMARY KEY,
12
+ created_at INTEGER NOT NULL,
13
+ guard TEXT NOT NULL,
14
+ path TEXT NOT NULL,
15
+ failure_kind TEXT NOT NULL,
16
+ count INTEGER NOT NULL,
17
+ window_start INTEGER NOT NULL,
18
+ window_end INTEGER NOT NULL
19
+ )
20
+ `);
21
+ }
@@ -0,0 +1,36 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ const CHECKPOINT_KEY = "migration_acp_session_history_cwd_v1";
6
+
7
+ const TABLE = "acp_session_history";
8
+ const COLUMN = "cwd";
9
+
10
+ /**
11
+ * Add a nullable `cwd TEXT` column to `acp_session_history`.
12
+ *
13
+ * Records the working directory the ACP agent process was spawned with so
14
+ * that a persisted session can later be resumed (`session/load` /
15
+ * `session/resume` both require the original cwd to reconstruct params).
16
+ *
17
+ * `NULL` for rows persisted before this migration ran — legacy sessions
18
+ * simply are not resumable.
19
+ *
20
+ * Idempotent — the PRAGMA guard makes re-running a no-op once the column
21
+ * exists.
22
+ */
23
+ export function migrateAcpSessionHistoryCwd(database: DrizzleDb): void {
24
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
25
+ const raw = getSqliteFrom(database);
26
+
27
+ const columns = raw.query(`PRAGMA table_info(${TABLE})`).all() as Array<{
28
+ name: string;
29
+ }>;
30
+ const columnNames = new Set(columns.map((c) => c.name));
31
+
32
+ if (!columnNames.has(COLUMN)) {
33
+ raw.exec(`ALTER TABLE ${TABLE} ADD COLUMN ${COLUMN} TEXT`);
34
+ }
35
+ });
36
+ }
@@ -257,6 +257,9 @@ export { migrateLlmUsageEventsAddAssistantVersion } from "./267-llm-usage-events
257
257
  export { migrateAddMemoryV3Selections } from "./268-add-memory-v3-selections.js";
258
258
  export { migrateScheduleScriptTimeout } from "./269-schedule-script-timeout.js";
259
259
  export { migrateMessagesRoleCreatedAtIndex } from "./270-messages-role-created-at-index.js";
260
+ export { migrateScheduleSourceConversation } from "./270-schedule-source-conversation.js";
261
+ export { createAuthFallbackEventsTable } from "./271-create-auth-fallback-events.js";
262
+ export { migrateAcpSessionHistoryCwd } from "./272-acp-session-history-cwd.js";
260
263
  export {
261
264
  MIGRATION_REGISTRY,
262
265
  type MigrationRegistryEntry,
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resolution of the PKB auto-inject file list (`pkb/_autoinject.md`).
3
+ *
4
+ * Which PKB files are always loaded into context is a PKB-domain concern
5
+ * owned here next to {@link getPkbRoot}, so both the runtime injector and the
6
+ * reminder-hint tracker resolve the list from a single canonical source
7
+ * rather than threading it through the orchestrator.
8
+ */
9
+
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ import { stripCommentLines } from "../../util/strip-comment-lines.js";
14
+
15
+ const PKB_DEFAULT_FILES = [
16
+ "INDEX.md",
17
+ "essentials.md",
18
+ "threads.md",
19
+ "buffer.md",
20
+ ];
21
+
22
+ const AUTOINJECT_FILENAME = "_autoinject.md";
23
+
24
+ /**
25
+ * Read `_autoinject.md` from the PKB directory and return the list of
26
+ * filenames to inject.
27
+ *
28
+ * - Returns `null` when the file is missing or unreadable — callers
29
+ * should fall back to the hardcoded defaults.
30
+ * - Returns `[]` when the file exists but has no entries (empty or
31
+ * comments only) — an explicit opt-out meaning "inject nothing."
32
+ */
33
+ export function readAutoinjectList(pkbDir: string): string[] | null {
34
+ const filePath = join(pkbDir, AUTOINJECT_FILENAME);
35
+ if (!existsSync(filePath)) return null;
36
+ try {
37
+ const raw = stripCommentLines(readFileSync(filePath, "utf-8"));
38
+ const files = raw
39
+ .split("\n")
40
+ .map((l) => l.trim())
41
+ .filter((l) => l.length > 0);
42
+ return files.length > 0 ? files : [];
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Resolve the effective list of auto-inject filenames for a PKB directory.
50
+ *
51
+ * This is the single source of truth used both by `readPkbContext` (which
52
+ * actually injects the files) and by the PKB reminder-hint injector (which
53
+ * needs to know what's already in context so it doesn't redundantly
54
+ * recommend those files).
55
+ *
56
+ * Returns `PKB_DEFAULT_FILES` when `_autoinject.md` is missing/unreadable,
57
+ * or the parsed list (possibly empty) when it is present.
58
+ */
59
+ export function getPkbAutoInjectList(pkbRoot: string): string[] {
60
+ return readAutoinjectList(pkbRoot) ?? PKB_DEFAULT_FILES;
61
+ }
@@ -0,0 +1,50 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+
4
+ import { getWorkspaceDir } from "../../util/platform.js";
5
+ import { stripCommentLines } from "../../util/strip-comment-lines.js";
6
+ import { getPkbAutoInjectList } from "./autoinject.js";
7
+
8
+ /** Max buffer.md lines injected into prompts — keeps context bounded even when filing is off. */
9
+ const MAX_BUFFER_LINES = 50;
10
+
11
+ /**
12
+ * Read the always-loaded PKB files and append a nudge encouraging the
13
+ * assistant to proactively read topic files and use `remember` aggressively.
14
+ *
15
+ * Which files are loaded is determined by `pkb/_autoinject.md` (one filename
16
+ * per line). Falls back to the built-in defaults when that file is absent.
17
+ *
18
+ * Returns the concatenated content ready for injection, or `null` if all
19
+ * files are missing or empty.
20
+ */
21
+ export function readPkbContext(): string | null {
22
+ const pkbDir = join(getWorkspaceDir(), "pkb");
23
+ if (!existsSync(pkbDir)) return null;
24
+
25
+ const filesToInject = getPkbAutoInjectList(pkbDir);
26
+
27
+ const parts: string[] = [];
28
+ for (const file of filesToInject) {
29
+ // Path traversal guard: reject entries that escape the pkb directory
30
+ const filePath = resolve(pkbDir, file);
31
+ if (!filePath.startsWith(pkbDir + "/")) continue;
32
+
33
+ if (!existsSync(filePath)) continue;
34
+ try {
35
+ let content = stripCommentLines(readFileSync(filePath, "utf-8")).trim();
36
+ if (file === "buffer.md" && content.length > 0) {
37
+ // Cap buffer entries to prevent unbounded growth when filing is disabled
38
+ const lines = content.split("\n");
39
+ if (lines.length > MAX_BUFFER_LINES) {
40
+ content = lines.slice(-MAX_BUFFER_LINES).join("\n");
41
+ }
42
+ }
43
+ if (content.length > 0) parts.push(content);
44
+ } catch {
45
+ // Skip unreadable files
46
+ }
47
+ }
48
+
49
+ return parts.length > 0 ? parts.join("\n\n") : null;
50
+ }
@@ -5,8 +5,22 @@
5
5
  * index writer) land in later PRs.
6
6
  */
7
7
 
8
+ import { join } from "node:path";
9
+
10
+ import { getWorkspaceDir } from "../../util/platform.js";
11
+
8
12
  export const PKB_TARGET_TYPE = "pkb_file" as const;
9
13
 
14
+ /**
15
+ * Absolute path to the workspace's PKB directory (`<workspace>/pkb`). PKB is
16
+ * a workspace-shared resource with a single on-disk location, so this is the
17
+ * canonical way to resolve its root rather than re-joining the workspace dir
18
+ * at each call site.
19
+ */
20
+ export function getPkbRoot(): string {
21
+ return join(getWorkspaceDir(), "pkb");
22
+ }
23
+
10
24
  /**
11
25
  * Sentinel `memory_scope_id` under which ALL PKB points are indexed and
12
26
  * searched. PKB files are a workspace-shared resource: one copy on disk is