@vellumai/assistant 0.8.3 → 0.8.4

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 (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -43,6 +43,7 @@ import {
43
43
  isCliCommandSlug,
44
44
  } from "./cli-command-store.js";
45
45
  import { getEdgeIndex } from "./edge-index.js";
46
+ import { recordInjectionEvents } from "./injection-events.js";
46
47
  import { readPage, renderPageContent } from "./page-store.js";
47
48
  import { runRouter } from "./router.js";
48
49
  import { getSkillCapability, isSkillSlug } from "./skill-store.js";
@@ -552,9 +553,19 @@ async function injectViaRouter(args: {
552
553
  nowText,
553
554
  priorEverInjected,
554
555
  config,
556
+ database,
555
557
  ...(signal ? { signal } : {}),
556
558
  });
557
559
 
560
+ // Record router selections to the EMA event log. The router decided these
561
+ // slugs are relevant THIS turn (regardless of whether they're newly
562
+ // rendered or re-picked from prior context). The helper swallows its own
563
+ // errors — a SQLite write must not abort the turn on top of a successful
564
+ // routing decision the rest of this function depends on.
565
+ if (routerResult.failureReason === null) {
566
+ recordInjectionEvents(database, routerResult.selectedSlugs, Date.now());
567
+ }
568
+
558
569
  if (routerResult.failureReason !== null) {
559
570
  log.warn(
560
571
  { failureReason: routerResult.failureReason },
@@ -603,12 +614,12 @@ async function injectViaRouter(args: {
603
614
 
604
615
  // Build minimal telemetry rows for the union of router-selected slugs and
605
616
  // prior `everInjected` slugs. Router-mode rows zero out every activation
606
- // value (no spreading activation runs). Slugs the router picked this turn
607
- // get `source: "router"`; prior-everInjected slugs the router did NOT
608
- // re-pick get `source: "carry_over"`. The `status` placeholder is
609
- // overwritten by `finalizeInjection`.
610
- const routerPicked = new Set(routerResult.selectedSlugs);
611
- const telemetrySlugs = new Set<string>(routerPicked);
617
+ // value (no spreading activation runs). Slugs the router picked carry
618
+ // their batch's tier tag from `routerResult.sourceBySlug` (e.g. `tier1`,
619
+ // `tier3:2`); prior-everInjected slugs the router did NOT re-pick get
620
+ // `source: "carry_over"`. The `status` placeholder is overwritten by
621
+ // `finalizeInjection`.
622
+ const telemetrySlugs = new Set<string>(routerResult.selectedSlugs);
612
623
  for (const entry of priorEverInjected) telemetrySlugs.add(entry.slug);
613
624
  const telemetryRows: MemoryV2ConceptRowRecord[] = [...telemetrySlugs].map(
614
625
  (slug) => ({
@@ -623,7 +634,7 @@ async function injectViaRouter(args: {
623
634
  simAssistantRerankBoost: 0,
624
635
  inRerankPool: false,
625
636
  spreadContribution: 0,
626
- source: routerPicked.has(slug) ? "router" : "carry_over",
637
+ source: routerResult.sourceBySlug.get(slug) ?? "carry_over",
627
638
  status: "not_injected",
628
639
  }),
629
640
  );
@@ -712,7 +723,7 @@ interface RenderInjectionBlockResult {
712
723
  * the agent into wasted reads.
713
724
  */
714
725
  const INJECTION_HEADER =
715
- "**CRITICAL:** These are page summaries. Read the page file if it looks relevant.";
726
+ 'Use `file_read("memory/concepts/path/to/file.md")` to read the full pages for any of the injected memory summaries you want more information on.';
716
727
 
717
728
  /**
718
729
  * Render the inner content of the `<memory>` block for a list of slugs.
@@ -745,9 +756,9 @@ const INJECTION_HEADER =
745
756
  * fallback for pages predating the summary field). Skills sit after the
746
757
  * concept sections under `### Skills You Can Use`, and CLI subcommands sit
747
758
  * after the skills under `### CLI Commands You Can Use`. The leading
748
- * `**CRITICAL:**` line tells the agent how to read the block.
759
+ * instruction line tells the agent how to read the block.
749
760
  *
750
- * **CRITICAL:** These are page summaries. Read the page file if it looks relevant.
761
+ * Use `file_read("memory/concepts/path/to/file.md")` to read the full pages for any of the injected memory summaries you want more information on.
751
762
  *
752
763
  * # memory/concepts/<concept-slug-1>.md
753
764
  * <summary-1>
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { getLogger } from "../../util/logger.js";
20
- import { listPages, readPage } from "./page-store.js";
20
+ import { getPageMtimeMs, listPages, readPage } from "./page-store.js";
21
21
 
22
22
  // Dynamic import for `./skill-store.js` happens inside `getPageIndex` so that
23
23
  // modules that only need `invalidatePageIndex` (page-store.ts,
@@ -55,6 +55,12 @@ export interface PageIndexEntry {
55
55
  summary: string;
56
56
  /** Numeric IDs of outgoing edges, in sorted order. */
57
57
  edges: number[];
58
+ /**
59
+ * File mtime in epoch ms; 0 for synthetic entries (skills, CLI commands)
60
+ * that have no on-disk source file. Used by `splitTier1` to rank pages
61
+ * by recency.
62
+ */
63
+ modifiedAt: number;
58
64
  }
59
65
 
60
66
  /**
@@ -94,18 +100,25 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
94
100
 
95
101
  const slugs = await listPages(workspaceDir);
96
102
 
97
- // Read pages in parallel; pages whose read rejects are dropped with a warn
98
- // so a single broken page never blocks the rest of the index.
103
+ // Read pages and stat their mtimes in parallel. Pages whose read rejects
104
+ // are dropped with a warn so a single broken page never blocks the rest
105
+ // of the index. mtime is stat'd alongside readPage so tier-1 sorting has
106
+ // recency without a second pass over the filesystem.
99
107
  const settled = await Promise.allSettled(
100
- slugs.map((slug) => readPage(workspaceDir, slug)),
108
+ slugs.map(async (slug) => {
109
+ const [page, mtimeMs] = await Promise.all([
110
+ readPage(workspaceDir, slug),
111
+ getPageMtimeMs(workspaceDir, slug),
112
+ ]);
113
+ return { page, mtimeMs };
114
+ }),
101
115
  );
102
116
 
103
- // Intermediate shape used while we still need the raw outgoing slugs to
104
- // resolve into numeric IDs after sorting.
105
117
  interface DraftEntry {
106
118
  slug: string;
107
119
  summary: string;
108
120
  outgoingSlugs: string[];
121
+ modifiedAt: number;
109
122
  }
110
123
 
111
124
  const [
@@ -143,7 +156,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
143
156
  );
144
157
  continue;
145
158
  }
146
- const page = result.value;
159
+ const { page, mtimeMs } = result.value;
147
160
  if (!page) continue;
148
161
  if (skillSlugs.has(slug)) {
149
162
  log.warn(
@@ -164,6 +177,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
164
177
  slug,
165
178
  summary: normalizeSummary(summarySource),
166
179
  outgoingSlugs: page.frontmatter.edges,
180
+ modifiedAt: mtimeMs,
167
181
  });
168
182
  }
169
183
 
@@ -172,6 +186,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
172
186
  slug: `${SKILL_SLUG_PREFIX}${entry.id}`,
173
187
  summary: normalizeSummary(entry.content),
174
188
  outgoingSlugs: [],
189
+ modifiedAt: 0,
175
190
  });
176
191
  }
177
192
 
@@ -180,6 +195,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
180
195
  slug: `${CLI_COMMAND_SLUG_PREFIX}${entry.id}`,
181
196
  summary: normalizeSummary(entry.description),
182
197
  outgoingSlugs: [],
198
+ modifiedAt: 0,
183
199
  });
184
200
  }
185
201
 
@@ -194,6 +210,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
194
210
  slug: draft.slug,
195
211
  summary: draft.summary,
196
212
  edges: [],
213
+ modifiedAt: draft.modifiedAt,
197
214
  };
198
215
  bySlug.set(entry.slug, entry);
199
216
  byId.set(entry.id, entry);
@@ -244,3 +261,188 @@ function renderIndex(entries: readonly PageIndexEntry[]): string {
244
261
  });
245
262
  return lines.length > 0 ? `${lines.join("\n")}\n` : "";
246
263
  }
264
+
265
+ // FNV-1a 32-bit. Stable across runtimes — never change the constants or
266
+ // future releases will silently reshuffle batches and torch every batch's
267
+ // KV cache simultaneously.
268
+ function fnv1aHash(input: string): number {
269
+ let h = 0x811c9dc5;
270
+ for (let i = 0; i < input.length; i++) {
271
+ h ^= input.charCodeAt(i);
272
+ h = Math.imul(h, 0x01000193);
273
+ }
274
+ return h >>> 0;
275
+ }
276
+
277
+ /**
278
+ * Split a global `PageIndex` into batches of approximately `batchSize`
279
+ * entries for parallel routing. Each batch is a self-contained `PageIndex`
280
+ * with batch-local 1-based IDs and a re-rendered prompt block.
281
+ *
282
+ * `batchSize === null` or `entries.length <= batchSize` short-circuits to
283
+ * `[pageIndex]` (the same object) so single-batch callers send a request
284
+ * bit-identical to the pre-batching code path and reuse v3's KV cache
285
+ * untouched.
286
+ *
287
+ * Assignment uses FNV-1a on the slug: adding or removing one page only
288
+ * invalidates the KV cache of the one batch it lands in, instead of
289
+ * cascading through every batch the way index-modulo chunking would.
290
+ *
291
+ * Edges are re-resolved to batch-local IDs — edges pointing to pages in
292
+ * other batches drop silently (the model can't reference them anyway).
293
+ */
294
+ export function partitionPageIndex(
295
+ pageIndex: PageIndex,
296
+ batchSize: number | null,
297
+ ): PageIndex[] {
298
+ if (batchSize === null || pageIndex.entries.length <= batchSize) {
299
+ return [pageIndex];
300
+ }
301
+ const batchCount = Math.ceil(pageIndex.entries.length / batchSize);
302
+ const buckets: PageIndexEntry[][] = Array.from(
303
+ { length: batchCount },
304
+ () => [],
305
+ );
306
+ for (const entry of pageIndex.entries) {
307
+ buckets[fnv1aHash(entry.slug) % batchCount].push(entry);
308
+ }
309
+ return buckets
310
+ .filter((b) => b.length > 0)
311
+ .map((entries) => buildLocalPageIndex(entries, pageIndex));
312
+ }
313
+
314
+ /**
315
+ * Build a self-contained `PageIndex` from a subset of another index's
316
+ * entries. Local entries get fresh 1-based IDs in input order, edges are
317
+ * remapped through the source index's `byId` to local IDs (cross-batch
318
+ * edges drop silently), and the prompt block is re-rendered.
319
+ */
320
+ function buildLocalPageIndex(
321
+ entries: readonly PageIndexEntry[],
322
+ source: PageIndex,
323
+ ): PageIndex {
324
+ const localBySlug = new Map<string, PageIndexEntry>();
325
+ const localById = new Map<number, PageIndexEntry>();
326
+ const localEntries: PageIndexEntry[] = entries.map((src, i) => {
327
+ const local: PageIndexEntry = {
328
+ id: i + 1,
329
+ slug: src.slug,
330
+ summary: src.summary,
331
+ edges: [],
332
+ modifiedAt: src.modifiedAt,
333
+ };
334
+ localBySlug.set(local.slug, local);
335
+ localById.set(local.id, local);
336
+ return local;
337
+ });
338
+ for (let i = 0; i < localEntries.length; i++) {
339
+ const localEdges: number[] = [];
340
+ for (const globalEdgeId of entries[i].edges) {
341
+ const target = source.byId.get(globalEdgeId);
342
+ if (!target) continue;
343
+ const localTarget = localBySlug.get(target.slug);
344
+ if (localTarget) localEdges.push(localTarget.id);
345
+ }
346
+ localEdges.sort((a, b) => a - b);
347
+ localEntries[i].edges = localEdges;
348
+ }
349
+ return {
350
+ entries: localEntries,
351
+ bySlug: localBySlug,
352
+ byId: localById,
353
+ rendered: renderIndex(localEntries),
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Carve the top-N most recently modified pages into their own batch (tier
359
+ * 1 in the v4 router architecture) and return the leftover as a second
360
+ * `PageIndex` for downstream partitioning.
361
+ *
362
+ * `tier1Size === null` is a no-op — `{ tier1: null, rest: pageIndex }`
363
+ * with the original index reference preserved so the single-batch path
364
+ * stays bit-identical to v3 and the KV cache survives.
365
+ *
366
+ * Tier 1 entries are sorted by `modifiedAt` descending; ties break by
367
+ * slug ASCII so the order is deterministic when several pages share a
368
+ * mtime (e.g. fresh workspaces). Synthetic entries (mtime=0) sort to the
369
+ * bottom and only enter tier 1 when there aren't enough real pages to
370
+ * fill the pool. The rest is sorted by slug ASCII so downstream
371
+ * hash-bucketing produces stable batches across mtime churn.
372
+ */
373
+ export function splitTier1(
374
+ pageIndex: PageIndex,
375
+ tier1Size: number | null,
376
+ ): { tier1: PageIndex | null; rest: PageIndex } {
377
+ if (tier1Size === null || pageIndex.entries.length === 0) {
378
+ return { tier1: null, rest: pageIndex };
379
+ }
380
+ const sortedByRecency = [...pageIndex.entries].sort((a, b) => {
381
+ if (a.modifiedAt !== b.modifiedAt) return b.modifiedAt - a.modifiedAt;
382
+ return a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0;
383
+ });
384
+ const tier1Entries = sortedByRecency.slice(0, tier1Size);
385
+ const tier1Slugs = new Set(tier1Entries.map((e) => e.slug));
386
+ const restEntries = pageIndex.entries.filter((e) => !tier1Slugs.has(e.slug));
387
+
388
+ const tier1 = buildLocalPageIndex(tier1Entries, pageIndex);
389
+ if (restEntries.length === 0) {
390
+ return { tier1, rest: emptyPageIndex() };
391
+ }
392
+ return { tier1, rest: buildLocalPageIndex(restEntries, pageIndex) };
393
+ }
394
+
395
+ /**
396
+ * Carve the top-M highest-EMA pages into their own batch (tier 2 in the
397
+ * v4 router architecture). Caller computes `scores` via
398
+ * `computeInjectionScores`; this function stays pure so unit tests don't
399
+ * need a database.
400
+ *
401
+ * `tier2Size === null` is a no-op. Pages with `score <= 0` (no events in
402
+ * the read window) are ineligible regardless of `tier2Size` — a stale
403
+ * page with zero score belongs in tier 3, not in the "useful" pool.
404
+ * Ordering is score desc, slug-ASCII tiebreak.
405
+ *
406
+ * Expected call shape: orchestrator passes the *post-tier-1* `PageIndex`,
407
+ * so we never re-promote a tier-1 page to tier 2.
408
+ */
409
+ export function splitTier2(
410
+ pageIndex: PageIndex,
411
+ tier2Size: number | null,
412
+ scores: ReadonlyMap<string, number>,
413
+ ): { tier2: PageIndex | null; rest: PageIndex } {
414
+ if (tier2Size === null || pageIndex.entries.length === 0) {
415
+ return { tier2: null, rest: pageIndex };
416
+ }
417
+ const eligible = pageIndex.entries
418
+ .map((entry) => ({ entry, score: scores.get(entry.slug) ?? 0 }))
419
+ .filter((x) => x.score > 0)
420
+ .sort((a, b) => {
421
+ if (a.score !== b.score) return b.score - a.score;
422
+ return a.entry.slug < b.entry.slug
423
+ ? -1
424
+ : a.entry.slug > b.entry.slug
425
+ ? 1
426
+ : 0;
427
+ });
428
+ const tier2Entries = eligible.slice(0, tier2Size).map((x) => x.entry);
429
+ if (tier2Entries.length === 0) {
430
+ return { tier2: null, rest: pageIndex };
431
+ }
432
+ const tier2Slugs = new Set(tier2Entries.map((e) => e.slug));
433
+ const restEntries = pageIndex.entries.filter((e) => !tier2Slugs.has(e.slug));
434
+ const tier2 = buildLocalPageIndex(tier2Entries, pageIndex);
435
+ if (restEntries.length === 0) {
436
+ return { tier2, rest: emptyPageIndex() };
437
+ }
438
+ return { tier2, rest: buildLocalPageIndex(restEntries, pageIndex) };
439
+ }
440
+
441
+ function emptyPageIndex(): PageIndex {
442
+ return {
443
+ entries: [],
444
+ bySlug: new Map(),
445
+ byId: new Map(),
446
+ rendered: "",
447
+ };
448
+ }
@@ -260,6 +260,24 @@ export async function readPage(
260
260
  return { slug, frontmatter, body };
261
261
  }
262
262
 
263
+ /**
264
+ * File mtime for a concept page, in epoch ms. Returns 0 when the file is
265
+ * missing or unreadable — callers treat 0 as "no mtime" so tier-1 sorting
266
+ * can rank synthetic entries (skills, CLI commands) below real pages.
267
+ */
268
+ export async function getPageMtimeMs(
269
+ workspaceDir: string,
270
+ slug: string,
271
+ ): Promise<number> {
272
+ validateSlug(slug);
273
+ try {
274
+ const s = await stat(getPagePath(workspaceDir, slug));
275
+ return s.mtimeMs;
276
+ } catch {
277
+ return 0;
278
+ }
279
+ }
280
+
263
281
  /**
264
282
  * Write a concept page atomically (temp file + rename). A crash between the
265
283
  * temp write and the rename leaves the prior file intact; a crash after the