@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -0,0 +1,368 @@
1
+ import { mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ mock.module("../../util/logger.js", () => ({
7
+ getLogger: () =>
8
+ new Proxy({} as Record<string, unknown>, {
9
+ get: () => () => {},
10
+ }),
11
+ }));
12
+
13
+ // Capture calls to embedAndUpsert so we can assert on targetType + payload.
14
+ const embedAndUpsertCalls: Array<{
15
+ config: unknown;
16
+ targetType: string;
17
+ targetId: string;
18
+ input: unknown;
19
+ extraPayload: unknown;
20
+ }> = [];
21
+
22
+ mock.module("../job-utils.js", () => ({
23
+ embedAndUpsert: async (
24
+ config: unknown,
25
+ targetType: string,
26
+ targetId: string,
27
+ input: unknown,
28
+ extraPayload: unknown,
29
+ ) => {
30
+ embedAndUpsertCalls.push({
31
+ config,
32
+ targetType,
33
+ targetId,
34
+ input,
35
+ extraPayload,
36
+ });
37
+ },
38
+ }));
39
+
40
+ // Minimal stub for getConfig — indexPkbFile forwards it opaquely to the
41
+ // mocked embedAndUpsert, so any sentinel value works.
42
+ mock.module("../../config/loader.js", () => ({
43
+ getConfig: () => ({ __stub: true }),
44
+ }));
45
+
46
+ // Track Qdrant deletes by capturing the filter the client sends.
47
+ const qdrantDeleteCalls: Array<{
48
+ targetType: string;
49
+ path: string;
50
+ memoryScopeId: string;
51
+ }> = [];
52
+
53
+ // Track per-target deletes (used by write-then-cleanup in indexPkbFile).
54
+ const qdrantDeleteByTargetCalls: Array<{
55
+ targetType: string;
56
+ targetId: string;
57
+ }> = [];
58
+
59
+ // Points the mocked scroll will return on the next call. Tests mutate this
60
+ // to simulate pre-existing PKB chunks on disk.
61
+ let scrollReturnPoints: Array<{
62
+ id: string;
63
+ payload: Record<string, unknown>;
64
+ }> = [];
65
+ const qdrantScrollCalls: Array<{
66
+ targetType: string;
67
+ memoryScopeId?: string;
68
+ path?: string;
69
+ }> = [];
70
+
71
+ mock.module("../qdrant-client.js", () => ({
72
+ getQdrantClient: () => ({
73
+ deleteByTargetTypeAndPath: async (
74
+ targetType: string,
75
+ path: string,
76
+ memoryScopeId: string,
77
+ ) => {
78
+ qdrantDeleteCalls.push({ targetType, path, memoryScopeId });
79
+ },
80
+ deleteByTarget: async (targetType: string, targetId: string) => {
81
+ qdrantDeleteByTargetCalls.push({ targetType, targetId });
82
+ },
83
+ scrollByTargetType: async (
84
+ targetType: string,
85
+ options?: {
86
+ memoryScopeId?: string;
87
+ path?: string;
88
+ batchSize?: number;
89
+ },
90
+ ) => {
91
+ qdrantScrollCalls.push({
92
+ targetType,
93
+ memoryScopeId: options?.memoryScopeId,
94
+ path: options?.path,
95
+ });
96
+ return scrollReturnPoints;
97
+ },
98
+ }),
99
+ }));
100
+
101
+ // The circuit breaker is a thin wrapper; just call the function through.
102
+ mock.module("../qdrant-circuit-breaker.js", () => ({
103
+ withQdrantBreaker: async <T,>(fn: () => Promise<T>) => fn(),
104
+ }));
105
+
106
+ import {
107
+ chunkPkbFile,
108
+ deletePkbFilePoints,
109
+ indexPkbFile,
110
+ scanPkbFiles,
111
+ } from "./pkb-index.js";
112
+
113
+ describe("chunkPkbFile", () => {
114
+ test("returns whole-file for small inputs", () => {
115
+ const small = "a".repeat(500);
116
+ const chunks = chunkPkbFile(small);
117
+ expect(chunks).toEqual([small]);
118
+ });
119
+
120
+ test("splits on ## headings with lossless concatenation", () => {
121
+ const sectionA = "## Section A\n" + "a".repeat(5990) + "\n";
122
+ const sectionB = "## Section B\n" + "b".repeat(6010);
123
+ const content = sectionA + sectionB;
124
+ expect(content.length).toBeGreaterThanOrEqual(12000);
125
+
126
+ const chunks = chunkPkbFile(content);
127
+ expect(chunks).toHaveLength(2);
128
+ expect(chunks.join("")).toBe(content);
129
+ expect(chunks[0].startsWith("## Section A")).toBe(true);
130
+ expect(chunks[1].startsWith("## Section B")).toBe(true);
131
+ });
132
+
133
+ test("falls back to char-window chunks when no ## headings exist", () => {
134
+ const content = "x".repeat(12000);
135
+ const chunks = chunkPkbFile(content);
136
+ // 12000 / 4000 = 3 windows.
137
+ expect(chunks).toHaveLength(3);
138
+ expect(chunks.join("")).toBe(content);
139
+ expect(chunks[0].length).toBe(4000);
140
+ expect(chunks[1].length).toBe(4000);
141
+ expect(chunks[2].length).toBe(4000);
142
+ });
143
+ });
144
+
145
+ describe("scanPkbFiles", () => {
146
+ test("returns entries for each .md file and ignores non-markdown", async () => {
147
+ const root = await mkdtemp(join(tmpdir(), "pkb-scan-"));
148
+ await writeFile(join(root, "a.md"), "# A\nalpha content");
149
+ await writeFile(join(root, "b.md"), "# B\nbeta content");
150
+ await writeFile(join(root, "notes.txt"), "plain text");
151
+
152
+ // Set deterministic mtimes so we can assert them.
153
+ const mtimeA = new Date(1_700_000_000_000);
154
+ const mtimeB = new Date(1_700_000_001_000);
155
+ await utimes(join(root, "a.md"), mtimeA, mtimeA);
156
+ await utimes(join(root, "b.md"), mtimeB, mtimeB);
157
+
158
+ const entries = await scanPkbFiles(root);
159
+ expect(entries).not.toBeNull();
160
+ const byPath = new Map(entries!.map((e) => [e.path, e]));
161
+
162
+ expect(byPath.size).toBe(2);
163
+ expect(byPath.has("a.md")).toBe(true);
164
+ expect(byPath.has("b.md")).toBe(true);
165
+ expect(byPath.has("notes.txt")).toBe(false);
166
+
167
+ const a = byPath.get("a.md")!;
168
+ expect(a.mtimeMs).toBe(mtimeA.getTime());
169
+ expect(a.chunkIndex).toBe(0);
170
+ expect(a.contentHash).toHaveLength(16);
171
+
172
+ // Hash is stable across scans.
173
+ const entriesAgain = await scanPkbFiles(root);
174
+ const aAgain = entriesAgain!.find((e) => e.path === "a.md")!;
175
+ expect(aAgain.contentHash).toBe(a.contentHash);
176
+ });
177
+
178
+ test("walks nested directories", async () => {
179
+ const root = await mkdtemp(join(tmpdir(), "pkb-scan-nested-"));
180
+ const sub = join(root, "sub");
181
+ await mkdir(sub);
182
+ await writeFile(join(sub, "nested.md"), "# nested");
183
+
184
+ const entries = await scanPkbFiles(root);
185
+ expect(entries).toHaveLength(1);
186
+ expect(entries![0].path).toBe(join("sub", "nested.md"));
187
+ });
188
+
189
+ test("returns null when pkbRoot does not exist", async () => {
190
+ const parent = await mkdtemp(join(tmpdir(), "pkb-scan-missing-"));
191
+ const missing = join(parent, "does-not-exist");
192
+ const entries = await scanPkbFiles(missing);
193
+ expect(entries).toBeNull();
194
+ });
195
+
196
+ test("returns null when pkbRoot existed then was removed", async () => {
197
+ const root = await mkdtemp(join(tmpdir(), "pkb-scan-removed-"));
198
+ await writeFile(join(root, "a.md"), "# A");
199
+ await rm(root, { recursive: true, force: true });
200
+
201
+ const entries = await scanPkbFiles(root);
202
+ expect(entries).toBeNull();
203
+ });
204
+
205
+ test("returns [] (not null) when pkbRoot exists but is empty", async () => {
206
+ const root = await mkdtemp(join(tmpdir(), "pkb-scan-empty-"));
207
+ const entries = await scanPkbFiles(root);
208
+ expect(entries).not.toBeNull();
209
+ expect(entries).toEqual([]);
210
+ });
211
+
212
+ test("returns null when pkbRoot points at a file instead of a directory", async () => {
213
+ const parent = await mkdtemp(join(tmpdir(), "pkb-scan-file-"));
214
+ const filePath = join(parent, "not-a-dir");
215
+ await writeFile(filePath, "just a file");
216
+ const entries = await scanPkbFiles(filePath);
217
+ expect(entries).toBeNull();
218
+ });
219
+ });
220
+
221
+ describe("indexPkbFile", () => {
222
+ beforeEach(() => {
223
+ embedAndUpsertCalls.length = 0;
224
+ qdrantDeleteCalls.length = 0;
225
+ qdrantDeleteByTargetCalls.length = 0;
226
+ qdrantScrollCalls.length = 0;
227
+ scrollReturnPoints = [];
228
+ });
229
+
230
+ test("invokes embedAndUpsert once per chunk with pkb_file target_type", async () => {
231
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-"));
232
+ const filePath = join(root, "doc.md");
233
+ await writeFile(filePath, "# hello\nworld");
234
+
235
+ await indexPkbFile(root, filePath, "scope-xyz");
236
+
237
+ expect(embedAndUpsertCalls).toHaveLength(1);
238
+ const call = embedAndUpsertCalls[0];
239
+ expect(call.targetType).toBe("pkb_file");
240
+ expect(call.targetId).toBe("scope-xyz:doc.md#0");
241
+ expect(call.input).toEqual({ type: "text", text: "# hello\nworld" });
242
+ const payload = call.extraPayload as Record<string, unknown>;
243
+ expect(payload.path).toBe("doc.md");
244
+ expect(payload.chunk_index).toBe(0);
245
+ expect(payload.memory_scope_id).toBe("scope-xyz");
246
+ expect(typeof payload.mtime_ms).toBe("number");
247
+ expect(typeof payload.content_hash).toBe("string");
248
+ expect((payload.content_hash as string).length).toBe(16);
249
+ });
250
+
251
+ test("emits one embedAndUpsert call per chunk for a large file", async () => {
252
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-large-"));
253
+ const filePath = join(root, "big.md");
254
+ const content =
255
+ "## Section A\n" +
256
+ "a".repeat(5990) +
257
+ "\n## Section B\n" +
258
+ "b".repeat(5990);
259
+ await writeFile(filePath, content);
260
+
261
+ await indexPkbFile(root, filePath, "scope-1");
262
+
263
+ expect(embedAndUpsertCalls).toHaveLength(2);
264
+ expect(embedAndUpsertCalls[0].targetId).toBe("scope-1:big.md#0");
265
+ expect(embedAndUpsertCalls[1].targetId).toBe("scope-1:big.md#1");
266
+ expect(embedAndUpsertCalls.every((c) => c.targetType === "pkb_file")).toBe(
267
+ true,
268
+ );
269
+ });
270
+
271
+ test("scope-namespaces target ids so two scopes indexing the same path do not collide", async () => {
272
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-scope-"));
273
+ const filePath = join(root, "shared.md");
274
+ await writeFile(filePath, "# shared");
275
+
276
+ await indexPkbFile(root, filePath, "alpha");
277
+ await indexPkbFile(root, filePath, "beta");
278
+
279
+ expect(embedAndUpsertCalls).toHaveLength(2);
280
+ const ids = embedAndUpsertCalls.map((c) => c.targetId);
281
+ expect(ids).toEqual(["alpha:shared.md#0", "beta:shared.md#0"]);
282
+ });
283
+
284
+ test("scrolls existing chunks scoped to (target_type, scope, path) before upserting", async () => {
285
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-scroll-"));
286
+ const filePath = join(root, "noted.md");
287
+ await writeFile(filePath, "# one");
288
+
289
+ await indexPkbFile(root, filePath, "scope-xyz");
290
+
291
+ expect(qdrantScrollCalls).toHaveLength(1);
292
+ expect(qdrantScrollCalls[0]).toEqual({
293
+ targetType: "pkb_file",
294
+ memoryScopeId: "scope-xyz",
295
+ path: "noted.md",
296
+ });
297
+ });
298
+
299
+ test("deletes only stale chunks after upserting (write-then-cleanup)", async () => {
300
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-shrink-"));
301
+ const filePath = join(root, "shrinking.md");
302
+ // New content produces a single chunk (index #0). Pre-existing chunks
303
+ // #0..#2 simulate a prior run over a larger file.
304
+ await writeFile(filePath, "# just one");
305
+ scrollReturnPoints = [
306
+ {
307
+ id: "point-0",
308
+ payload: { target_id: "scope-xyz:shrinking.md#0" },
309
+ },
310
+ {
311
+ id: "point-1",
312
+ payload: { target_id: "scope-xyz:shrinking.md#1" },
313
+ },
314
+ {
315
+ id: "point-2",
316
+ payload: { target_id: "scope-xyz:shrinking.md#2" },
317
+ },
318
+ ];
319
+
320
+ await indexPkbFile(root, filePath, "scope-xyz");
321
+
322
+ // Exactly one upsert for the surviving chunk.
323
+ expect(embedAndUpsertCalls).toHaveLength(1);
324
+ expect(embedAndUpsertCalls[0].targetId).toBe("scope-xyz:shrinking.md#0");
325
+
326
+ // The pre-delete is gone; only the two stale chunks are removed.
327
+ expect(qdrantDeleteCalls).toHaveLength(0);
328
+ const staleTargetIds = qdrantDeleteByTargetCalls.map((c) => c.targetId);
329
+ expect(staleTargetIds.sort()).toEqual([
330
+ "scope-xyz:shrinking.md#1",
331
+ "scope-xyz:shrinking.md#2",
332
+ ]);
333
+ });
334
+
335
+ test("does not delete points whose target_id is regenerated", async () => {
336
+ const root = await mkdtemp(join(tmpdir(), "pkb-index-stable-"));
337
+ const filePath = join(root, "stable.md");
338
+ await writeFile(filePath, "# same");
339
+ scrollReturnPoints = [
340
+ {
341
+ id: "point-0",
342
+ payload: { target_id: "scope-xyz:stable.md#0" },
343
+ },
344
+ ];
345
+
346
+ await indexPkbFile(root, filePath, "scope-xyz");
347
+
348
+ expect(embedAndUpsertCalls).toHaveLength(1);
349
+ expect(qdrantDeleteByTargetCalls).toHaveLength(0);
350
+ });
351
+ });
352
+
353
+ describe("deletePkbFilePoints", () => {
354
+ beforeEach(() => {
355
+ qdrantDeleteCalls.length = 0;
356
+ });
357
+
358
+ test("sends a filter with target_type, path, and memory_scope_id predicates", async () => {
359
+ await deletePkbFilePoints("notes/todo.md", "scope-xyz");
360
+
361
+ expect(qdrantDeleteCalls).toHaveLength(1);
362
+ expect(qdrantDeleteCalls[0]).toEqual({
363
+ targetType: "pkb_file",
364
+ path: "notes/todo.md",
365
+ memoryScopeId: "scope-xyz",
366
+ });
367
+ });
368
+ });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * PKB (Personal Knowledge Base) filesystem indexing primitives.
3
+ *
4
+ * Provides the low-level building blocks used by the PKB job handler and
5
+ * startup reconciliation:
6
+ * - `scanPkbFiles`: recursively walk a PKB root and emit one entry per chunk.
7
+ * - `chunkPkbFile`: split a markdown file into retrieval-friendly chunks.
8
+ * - `indexPkbFile`: embed each chunk and upsert it to Qdrant.
9
+ * - `deletePkbFilePoints`: remove every Qdrant point for a given file.
10
+ *
11
+ * Consumers (job queue wiring, startup scan) land in later PRs.
12
+ */
13
+
14
+ import { createHash } from "node:crypto";
15
+ import { readdir, readFile, stat } from "node:fs/promises";
16
+ import { join, relative } from "node:path";
17
+
18
+ import { getConfig } from "../../config/loader.js";
19
+ import { embedAndUpsert } from "../job-utils.js";
20
+ import { withQdrantBreaker } from "../qdrant-circuit-breaker.js";
21
+ import { getQdrantClient } from "../qdrant-client.js";
22
+ import type { PkbIndexEntry } from "./types.js";
23
+ import { PKB_TARGET_TYPE } from "./types.js";
24
+
25
+ /** Files larger than this are split into chunks for retrieval. */
26
+ const WHOLE_FILE_THRESHOLD = 8000;
27
+
28
+ /** Character-window size when falling back for unstructured content. */
29
+ const CHAR_WINDOW_SIZE = 4000;
30
+
31
+ /**
32
+ * Recursively walk `pkbRoot` and return one `PkbIndexEntry` per chunk of
33
+ * every `*.md` file found. Paths in the returned entries are relative to
34
+ * `pkbRoot`; mtime is read from the filesystem and `contentHash` is the
35
+ * first 16 hex chars of the sha256 of the file's contents.
36
+ *
37
+ * Returns `null` if `pkbRoot` cannot be confirmed as an existing directory
38
+ * (missing, not a directory, or stat failed for any reason — ENOENT, EACCES,
39
+ * EIO, etc.). This is distinct from returning `[]` for a directory that
40
+ * exists but has no `*.md` files — callers that run destructive
41
+ * reconciliation against the result (e.g. `reconcilePkbIndex`) use the
42
+ * sentinel to avoid interpreting an unreadable or transiently missing root
43
+ * as "delete every indexed point".
44
+ */
45
+ export async function scanPkbFiles(
46
+ pkbRoot: string,
47
+ ): Promise<PkbIndexEntry[] | null> {
48
+ const entries: PkbIndexEntry[] = [];
49
+
50
+ // Verify the root exists up front. Any failure to confirm the root is a
51
+ // directory — ENOENT, EACCES, EIO, or a path that exists but isn't a
52
+ // directory — returns the missing sentinel so callers that run destructive
53
+ // reconciliation don't interpret "couldn't read the tree" as "disk is
54
+ // empty, delete everything indexed".
55
+ try {
56
+ const rootStat = await stat(pkbRoot);
57
+ if (!rootStat.isDirectory()) {
58
+ return null;
59
+ }
60
+ } catch {
61
+ return null;
62
+ }
63
+
64
+ async function walk(dir: string): Promise<void> {
65
+ let dirents;
66
+ try {
67
+ dirents = await readdir(dir, { withFileTypes: true });
68
+ } catch {
69
+ return;
70
+ }
71
+
72
+ for (const dirent of dirents) {
73
+ const absPath = join(dir, dirent.name);
74
+ if (dirent.isDirectory()) {
75
+ await walk(absPath);
76
+ continue;
77
+ }
78
+ if (!dirent.isFile()) continue;
79
+ if (!dirent.name.toLowerCase().endsWith(".md")) continue;
80
+
81
+ let content: string;
82
+ let mtimeMs: number;
83
+ try {
84
+ content = await readFile(absPath, "utf8");
85
+ const st = await stat(absPath);
86
+ mtimeMs = st.mtimeMs;
87
+ } catch {
88
+ continue;
89
+ }
90
+
91
+ const contentHash = hashContent(content);
92
+ const relPath = relative(pkbRoot, absPath);
93
+ const chunks = chunkPkbFile(content);
94
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
95
+ entries.push({
96
+ path: relPath,
97
+ mtimeMs,
98
+ contentHash,
99
+ chunkIndex,
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ await walk(pkbRoot);
106
+ return entries;
107
+ }
108
+
109
+ /**
110
+ * Split markdown content into retrieval chunks.
111
+ *
112
+ * Strategy:
113
+ * - If the file is small (< WHOLE_FILE_THRESHOLD chars), return the whole
114
+ * file as a single chunk.
115
+ * - Otherwise split on lines starting with `## `, keeping each heading
116
+ * with the body of its section. Concatenation of the returned chunks is
117
+ * lossless — no content is dropped or duplicated.
118
+ * - If no `## ` headings are present, fall back to fixed-size character
119
+ * windows.
120
+ */
121
+ export function chunkPkbFile(content: string): string[] {
122
+ if (content.length < WHOLE_FILE_THRESHOLD) {
123
+ return [content];
124
+ }
125
+
126
+ const headingIndices: number[] = [];
127
+ let cursor = 0;
128
+ while (cursor < content.length) {
129
+ // Find the next line that starts with "## " (either at the very beginning
130
+ // of the file or immediately after a newline).
131
+ const atStart = cursor === 0 && content.startsWith("## ");
132
+ if (atStart) {
133
+ headingIndices.push(0);
134
+ cursor = 1; // advance past the match so the `indexOf` below keeps moving
135
+ continue;
136
+ }
137
+ const nextNewline = content.indexOf("\n## ", cursor);
138
+ if (nextNewline === -1) break;
139
+ headingIndices.push(nextNewline + 1);
140
+ cursor = nextNewline + 1;
141
+ }
142
+
143
+ if (headingIndices.length === 0) {
144
+ // Fallback: fixed-size char windows.
145
+ const chunks: string[] = [];
146
+ for (let i = 0; i < content.length; i += CHAR_WINDOW_SIZE) {
147
+ chunks.push(content.slice(i, i + CHAR_WINDOW_SIZE));
148
+ }
149
+ return chunks;
150
+ }
151
+
152
+ // Build chunks from heading boundaries. Preserve any preamble before the
153
+ // first heading so concatenation stays lossless.
154
+ const chunks: string[] = [];
155
+ if (headingIndices[0] > 0) {
156
+ chunks.push(content.slice(0, headingIndices[0]));
157
+ }
158
+ for (let i = 0; i < headingIndices.length; i++) {
159
+ const start = headingIndices[i];
160
+ const end = i + 1 < headingIndices.length ? headingIndices[i + 1] : content.length;
161
+ chunks.push(content.slice(start, end));
162
+ }
163
+ return chunks;
164
+ }
165
+
166
+ /**
167
+ * Read a PKB file, chunk it, and upsert each chunk to Qdrant via the shared
168
+ * embedding pipeline. `relPath` in the payload is computed relative to
169
+ * `pkbRoot`.
170
+ *
171
+ * Write-then-cleanup: enumerate the existing chunk target_ids for this
172
+ * (scope, path), upsert every new chunk, then delete only the stale
173
+ * target_ids (those the fresh content no longer produces). Upsert dedupes on
174
+ * (target_type, target_id), so matching chunk indexes are replaced in place
175
+ * and the prior index stays queryable if any embed/upsert call throws. A
176
+ * pre-delete would instead leave the file unsearchable on transient failure
177
+ * until a successful retry.
178
+ */
179
+ export async function indexPkbFile(
180
+ pkbRoot: string,
181
+ absPath: string,
182
+ memoryScopeId: string,
183
+ ): Promise<void> {
184
+ const content = await readFile(absPath, "utf8");
185
+ const st = await stat(absPath);
186
+ const mtimeMs = st.mtimeMs;
187
+ const contentHash = hashContent(content);
188
+ const relPath = relative(pkbRoot, absPath);
189
+ const chunks = chunkPkbFile(content);
190
+
191
+ const config = getConfig();
192
+
193
+ const qdrant = getQdrantClient();
194
+ const existing = await withQdrantBreaker(() =>
195
+ qdrant.scrollByTargetType(PKB_TARGET_TYPE, {
196
+ memoryScopeId,
197
+ path: relPath,
198
+ }),
199
+ );
200
+
201
+ const newTargetIds = new Set<string>();
202
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
203
+ const chunk = chunks[chunkIndex];
204
+ // Scope-namespace the target_id so `qdrant.upsert` — which dedupes on
205
+ // (target_type, target_id) — cannot collapse distinct scopes' chunks of
206
+ // the same relpath into a single point. Without the scope prefix, the
207
+ // second scope to index a shared path would overwrite the first's vectors.
208
+ const targetId = `${memoryScopeId}:${relPath}#${chunkIndex}`;
209
+ newTargetIds.add(targetId);
210
+ await embedAndUpsert(
211
+ config,
212
+ PKB_TARGET_TYPE,
213
+ targetId,
214
+ { type: "text", text: chunk },
215
+ {
216
+ path: relPath,
217
+ mtime_ms: mtimeMs,
218
+ chunk_index: chunkIndex,
219
+ content_hash: contentHash,
220
+ memory_scope_id: memoryScopeId,
221
+ },
222
+ );
223
+ }
224
+
225
+ // All upserts succeeded — safe to remove stale chunks that the new content
226
+ // did not regenerate (e.g. the file shrunk from 4 chunks to 2).
227
+ for (const point of existing) {
228
+ const staleTargetId = point.payload.target_id;
229
+ if (typeof staleTargetId !== "string") continue;
230
+ if (newTargetIds.has(staleTargetId)) continue;
231
+ await withQdrantBreaker(() =>
232
+ qdrant.deleteByTarget(PKB_TARGET_TYPE, staleTargetId),
233
+ );
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Remove every Qdrant point belonging to a given PKB file (all chunks) within
239
+ * a single memory scope. `relPath` must match the `path` payload written by
240
+ * `indexPkbFile`. The `memoryScopeId` filter is required — omitting it would
241
+ * wipe that relpath's chunks across every scope that indexes the same file.
242
+ */
243
+ export async function deletePkbFilePoints(
244
+ relPath: string,
245
+ memoryScopeId: string,
246
+ ): Promise<void> {
247
+ const qdrant = getQdrantClient();
248
+ await withQdrantBreaker(() =>
249
+ qdrant.deleteByTargetTypeAndPath(PKB_TARGET_TYPE, relPath, memoryScopeId),
250
+ );
251
+ }
252
+
253
+ function hashContent(content: string): string {
254
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
255
+ }