@poolzin/pool-bot 2026.2.17 → 2026.2.18

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 (469) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/agent-scope.js +4 -0
  3. package/dist/agents/announce-idempotency.js +14 -0
  4. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +23 -0
  5. package/dist/agents/bash-tools.exec-runtime.js +438 -0
  6. package/dist/agents/bash-tools.shared.js +6 -0
  7. package/dist/agents/cli-runner/reliability.js +61 -0
  8. package/dist/agents/cli-watchdog-defaults.js +11 -0
  9. package/dist/agents/command-poll-backoff.js +63 -0
  10. package/dist/agents/current-time.js +16 -0
  11. package/dist/agents/model-alias-lines.js +18 -0
  12. package/dist/agents/model-auth-label.js +61 -0
  13. package/dist/agents/models-config.e2e-harness.js +115 -0
  14. package/dist/agents/ollama-stream.js +11 -3
  15. package/dist/agents/openclaw-tools.js +135 -0
  16. package/dist/agents/pi-auth-json.js +118 -0
  17. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +147 -0
  18. package/dist/agents/pi-embedded-subscribe.e2e-harness.js +90 -0
  19. package/dist/agents/pi-embedded-subscribe.handlers.compaction.js +63 -0
  20. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +30 -0
  21. package/dist/agents/pi-extensions/session-manager-runtime-registry.js +23 -0
  22. package/dist/agents/pi-tools.js +2 -0
  23. package/dist/agents/queued-file-writer.js +22 -0
  24. package/dist/agents/sandbox/docker.js +133 -40
  25. package/dist/agents/sandbox/fs-bridge.js +146 -0
  26. package/dist/agents/sandbox/fs-paths.js +205 -0
  27. package/dist/agents/sandbox/hash.js +4 -0
  28. package/dist/agents/sandbox-paths.js +3 -0
  29. package/dist/agents/session-dirs.js +20 -0
  30. package/dist/agents/skills/filter.js +24 -0
  31. package/dist/agents/skills/tools-dir.js +9 -0
  32. package/dist/agents/skills-install-download.js +290 -0
  33. package/dist/agents/skills-install-output.js +30 -0
  34. package/dist/agents/skills-install.download-test-utils.js +36 -0
  35. package/dist/agents/skills.e2e-test-helpers.js +13 -0
  36. package/dist/agents/subagent-announce-queue.js +59 -15
  37. package/dist/agents/subagent-depth.js +137 -0
  38. package/dist/agents/subagent-registry.js +448 -96
  39. package/dist/agents/subagent-spawn.js +262 -0
  40. package/dist/agents/test-helpers/fast-tool-stubs.js +18 -0
  41. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +74 -0
  42. package/dist/agents/tool-display-common.js +782 -0
  43. package/dist/agents/tools/image-tool.js +1 -1
  44. package/dist/agents/tools/sessions-access.js +178 -0
  45. package/dist/agents/tools/sessions-resolution.js +206 -0
  46. package/dist/agents/tools/subagents-tool.js +616 -0
  47. package/dist/agents/workspace-dir.js +18 -0
  48. package/dist/agents/workspace-dirs.js +14 -0
  49. package/dist/agents/workspace.js +70 -0
  50. package/dist/auto-reply/heartbeat-reply-payload.js +18 -0
  51. package/dist/auto-reply/reply/commands-export-session.js +163 -0
  52. package/dist/auto-reply/reply/commands-mesh.js +245 -0
  53. package/dist/auto-reply/reply/commands-setunset.js +28 -0
  54. package/dist/auto-reply/reply/commands-slash-parse.js +31 -0
  55. package/dist/auto-reply/reply/commands-system-prompt.js +117 -0
  56. package/dist/auto-reply/reply/directive-handling.levels.js +17 -0
  57. package/dist/auto-reply/reply/directive-handling.params.js +1 -0
  58. package/dist/auto-reply/reply/directive-parsing.js +36 -0
  59. package/dist/auto-reply/reply/dispatcher-registry.js +43 -0
  60. package/dist/auto-reply/reply/elevated-unavailable.js +20 -0
  61. package/dist/auto-reply/reply/reply-delivery.js +92 -0
  62. package/dist/auto-reply/reply/session-reset-prompt.js +1 -0
  63. package/dist/auto-reply/reply/session-run-accounting.js +33 -0
  64. package/dist/auto-reply/reply.directive.directive-behavior.e2e-harness.js +115 -0
  65. package/dist/auto-reply/reply.directive.directive-behavior.e2e-mocks.js +12 -0
  66. package/dist/browser/bridge-auth-registry.js +26 -0
  67. package/dist/browser/client-actions-url.js +10 -0
  68. package/dist/browser/control-auth.js +73 -0
  69. package/dist/browser/csrf.js +64 -0
  70. package/dist/browser/http-auth.js +52 -0
  71. package/dist/browser/paths.js +37 -0
  72. package/dist/browser/proxy-files.js +32 -0
  73. package/dist/browser/pw-ai-state.js +7 -0
  74. package/dist/browser/resolved-config-refresh.js +42 -0
  75. package/dist/browser/routes/path-output.js +1 -0
  76. package/dist/browser/server-context.chrome-test-harness.js +20 -0
  77. package/dist/browser/server-middleware.js +31 -0
  78. package/dist/browser/test-port.js +16 -0
  79. package/dist/build-info.json +3 -3
  80. package/dist/canvas-host/file-resolver.js +43 -0
  81. package/dist/channels/account-summary.js +19 -0
  82. package/dist/channels/draft-stream-loop.js +77 -0
  83. package/dist/channels/plugins/account-helpers.js +26 -0
  84. package/dist/channels/telegram/allow-from.js +10 -0
  85. package/dist/cli/browser-cli-resize.js +22 -0
  86. package/dist/cli/browser-cli-shared.js +8 -0
  87. package/dist/cli/clawbot-cli.js +5 -0
  88. package/dist/cli/completion-cli.js +566 -0
  89. package/dist/cli/config-cli.js +63 -5
  90. package/dist/cli/daemon-cli/lifecycle-core.js +256 -0
  91. package/dist/cli/daemon-cli/register-service-commands.js +60 -0
  92. package/dist/cli/daemon-cli-compat.js +80 -0
  93. package/dist/cli/nodes-cli/pairing-render.js +26 -0
  94. package/dist/cli/program/action-reparse.js +17 -0
  95. package/dist/cli/program/command-registry.js +17 -0
  96. package/dist/cli/program/program-context.js +8 -0
  97. package/dist/cli/program/register.subclis.js +7 -0
  98. package/dist/cli/program/routes.js +233 -0
  99. package/dist/cli/qr-cli.js +132 -0
  100. package/dist/cli/requirements-test-fixtures.js +17 -0
  101. package/dist/cli/respawn-policy.js +4 -0
  102. package/dist/cli/shared/parse-port.js +18 -0
  103. package/dist/cli/skills-cli.format.js +241 -0
  104. package/dist/cli/update-cli/progress.js +121 -0
  105. package/dist/cli/update-cli/restart-helper.js +108 -0
  106. package/dist/cli/update-cli/shared.js +196 -0
  107. package/dist/cli/update-cli/status.js +97 -0
  108. package/dist/cli/update-cli/suppress-deprecations.js +17 -0
  109. package/dist/cli/update-cli/update-command.js +506 -0
  110. package/dist/cli/update-cli/wizard.js +130 -0
  111. package/dist/cli/update-cli.js +3 -9
  112. package/dist/cli/windows-argv.js +69 -0
  113. package/dist/commands/auth-choice-legacy.js +20 -0
  114. package/dist/commands/auth-choice.apply-helpers.js +8 -0
  115. package/dist/commands/channel-test-helpers.js +19 -0
  116. package/dist/commands/cleanup-plan.js +10 -0
  117. package/dist/commands/cleanup-utils.js +7 -0
  118. package/dist/commands/config-validation.js +15 -0
  119. package/dist/commands/doctor-completion.js +112 -0
  120. package/dist/commands/doctor-memory-search.js +119 -0
  121. package/dist/commands/doctor-session-locks.js +73 -0
  122. package/dist/commands/doctor.e2e-harness.js +364 -0
  123. package/dist/commands/gateway-presence.js +19 -0
  124. package/dist/commands/model-default.js +35 -0
  125. package/dist/commands/models/fallbacks-shared.js +102 -0
  126. package/dist/commands/models/shared.js +24 -0
  127. package/dist/commands/onboard-auth.config-gateways.js +64 -0
  128. package/dist/commands/onboard-auth.config-litellm.js +45 -0
  129. package/dist/commands/onboard-auth.config-shared.js +116 -0
  130. package/dist/commands/onboard-config.js +16 -0
  131. package/dist/commands/onboard-non-interactive.test-helpers.js +31 -0
  132. package/dist/commands/onboard-provider-auth-flags.js +136 -0
  133. package/dist/commands/openai-codex-oauth.js +40 -0
  134. package/dist/commands/test-runtime-config-helpers.js +21 -0
  135. package/dist/commands/test-wizard-helpers.js +68 -0
  136. package/dist/commands/vllm-setup.js +66 -0
  137. package/dist/compat/legacy-names.js +2 -0
  138. package/dist/config/backup-rotation.js +19 -0
  139. package/dist/config/env-preserve.js +122 -0
  140. package/dist/config/includes-scan.js +78 -0
  141. package/dist/config/plugins-allowlist.js +13 -0
  142. package/dist/config/schema.help.js +256 -0
  143. package/dist/config/schema.hints.js +189 -0
  144. package/dist/config/schema.irc.js +20 -0
  145. package/dist/config/schema.labels.js +317 -0
  146. package/dist/config/sessions/delivery-info.js +40 -0
  147. package/dist/config/types.irc.js +1 -0
  148. package/dist/config/zod-schema.agent-model.js +10 -0
  149. package/dist/config/zod-schema.allowdeny.js +35 -0
  150. package/dist/config/zod-schema.sensitive.js +4 -0
  151. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  152. package/dist/cron/isolated-agent/skills-snapshot.js +26 -0
  153. package/dist/cron/isolated-agent/subagent-followup.js +127 -0
  154. package/dist/cron/isolated-agent.mocks.js +12 -0
  155. package/dist/cron/isolated-agent.test-setup.js +22 -0
  156. package/dist/cron/legacy-delivery.js +43 -0
  157. package/dist/cron/webhook-url.js +22 -0
  158. package/dist/daemon/arg-split.js +40 -0
  159. package/dist/daemon/exec-file.js +23 -0
  160. package/dist/daemon/output.js +6 -0
  161. package/dist/daemon/runtime-format.js +31 -0
  162. package/dist/daemon/schtasks-exec.js +4 -0
  163. package/dist/daemon/service-audit.js +22 -0
  164. package/dist/discord/client.js +41 -0
  165. package/dist/discord/components-registry.js +57 -0
  166. package/dist/discord/components.js +816 -0
  167. package/dist/discord/guilds.js +12 -0
  168. package/dist/discord/monitor/gateway-plugin.js +48 -0
  169. package/dist/discord/monitor/presence.js +30 -0
  170. package/dist/discord/send.components.js +115 -0
  171. package/dist/discord/send.shared.js +4 -0
  172. package/dist/discord/ui.js +26 -0
  173. package/dist/discord/voice-message.js +254 -0
  174. package/dist/gateway/agent-event-assistant-text.js +5 -0
  175. package/dist/gateway/agent-prompt.js +33 -0
  176. package/dist/gateway/auth-rate-limit.js +136 -0
  177. package/dist/gateway/channel-health-monitor.js +114 -0
  178. package/dist/gateway/control-ui-contract.js +1 -0
  179. package/dist/gateway/control-ui-csp.js +15 -0
  180. package/dist/gateway/gateway-config-prompts.shared.js +25 -0
  181. package/dist/gateway/http-auth-helpers.js +18 -0
  182. package/dist/gateway/http-common.js +18 -0
  183. package/dist/gateway/http-endpoint-helpers.js +27 -0
  184. package/dist/gateway/node-invoke-sanitize.js +11 -0
  185. package/dist/gateway/node-invoke-system-run-approval.js +205 -0
  186. package/dist/gateway/probe-auth.js +21 -0
  187. package/dist/gateway/protocol/index.js +7 -2
  188. package/dist/gateway/protocol/schema/mesh.js +54 -0
  189. package/dist/gateway/protocol/schema/protocol-schemas.js +7 -0
  190. package/dist/gateway/protocol/schema.js +1 -0
  191. package/dist/gateway/server/ws-connection/auth-messages.js +54 -0
  192. package/dist/gateway/server-channels.js +11 -0
  193. package/dist/gateway/server-methods/attachment-normalize.js +16 -0
  194. package/dist/gateway/server-methods/base-hash.js +8 -0
  195. package/dist/gateway/server-methods/mesh.js +700 -0
  196. package/dist/gateway/server-methods/nodes.handlers.invoke-result.js +55 -0
  197. package/dist/gateway/server-methods/restart-request.js +13 -0
  198. package/dist/gateway/server-methods/validation.js +8 -0
  199. package/dist/gateway/server.agent.gateway-server-agent.mocks.js +35 -0
  200. package/dist/gateway/server.e2e-registry-helpers.js +1 -0
  201. package/dist/gateway/server.e2e-ws-harness.js +20 -0
  202. package/dist/gateway/test-helpers.js +2 -0
  203. package/dist/gateway/test-helpers.server.js +3 -1
  204. package/dist/gateway/test-http-response.js +12 -0
  205. package/dist/gateway/test-openai-responses-model.js +20 -0
  206. package/dist/gateway/test-temp-config.js +30 -0
  207. package/dist/gateway/test-with-server.js +32 -0
  208. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +46 -0
  209. package/dist/imessage/monitor/abort-handler.js +23 -0
  210. package/dist/imessage/monitor/inbound-processing.js +346 -0
  211. package/dist/imessage/monitor/parse-notification.js +64 -0
  212. package/dist/imessage/target-parsing-helpers.js +92 -0
  213. package/dist/infra/archive.js +244 -20
  214. package/dist/infra/detect-package-manager.js +26 -0
  215. package/dist/infra/exec-approvals-allowlist.js +257 -0
  216. package/dist/infra/exec-approvals-analysis.js +770 -0
  217. package/dist/infra/exec-approvals.js +13 -0
  218. package/dist/infra/file-lock.js +1 -0
  219. package/dist/infra/gemini-auth.js +39 -0
  220. package/dist/infra/heartbeat-active-hours.js +85 -0
  221. package/dist/infra/heartbeat-events-filter.js +50 -0
  222. package/dist/infra/heartbeat-runner.test-utils.js +39 -0
  223. package/dist/infra/http-body.js +265 -0
  224. package/dist/infra/install-package-dir.js +50 -0
  225. package/dist/infra/install-safe-path.js +49 -0
  226. package/dist/infra/json-files.js +49 -0
  227. package/dist/infra/jsonl-socket.js +52 -0
  228. package/dist/infra/map-size.js +14 -0
  229. package/dist/infra/net/hostname.js +7 -0
  230. package/dist/infra/npm-registry-spec.js +39 -0
  231. package/dist/infra/openclaw-root.js +109 -0
  232. package/dist/infra/outbound/delivery-queue.js +214 -0
  233. package/dist/infra/outbound/identity.js +23 -0
  234. package/dist/infra/outbound/message-action-params.js +307 -0
  235. package/dist/infra/outbound/tool-payload.js +21 -0
  236. package/dist/infra/package-json.js +23 -0
  237. package/dist/infra/pairing-files.js +19 -0
  238. package/dist/infra/pairing-token.js +9 -0
  239. package/dist/infra/path-prepend.js +51 -0
  240. package/dist/infra/process-respawn.js +49 -0
  241. package/dist/infra/runtime-status.js +16 -0
  242. package/dist/infra/session-cost-usage.types.js +1 -0
  243. package/dist/infra/session-maintenance-warning.js +89 -0
  244. package/dist/infra/system-run-command.js +78 -0
  245. package/dist/infra/tmp-openclaw-dir.js +81 -0
  246. package/dist/infra/tmp-poolbot-dir.js +2 -0
  247. package/dist/infra/update-channels.js +19 -0
  248. package/dist/line/actions.js +45 -0
  249. package/dist/line/channel-access-token.js +9 -0
  250. package/dist/line/flex-templates/basic-cards.js +332 -0
  251. package/dist/line/flex-templates/common.js +18 -0
  252. package/dist/line/flex-templates/media-control-cards.js +453 -0
  253. package/dist/line/flex-templates/message.js +10 -0
  254. package/dist/line/flex-templates/schedule-cards.js +399 -0
  255. package/dist/line/flex-templates/types.js +1 -0
  256. package/dist/line/webhook-node.js +100 -0
  257. package/dist/line/webhook-utils.js +11 -0
  258. package/dist/logging/timestamps.js +14 -0
  259. package/dist/markdown/whatsapp.js +62 -0
  260. package/dist/media/base64.js +34 -0
  261. package/dist/media/local-roots.js +32 -0
  262. package/dist/media/outbound-attachment.js +10 -0
  263. package/dist/media/read-response-with-limit.js +41 -0
  264. package/dist/media/sniff-mime-from-base64.js +19 -0
  265. package/dist/media-understanding/audio-preflight.js +67 -0
  266. package/dist/media-understanding/fs.js +13 -0
  267. package/dist/media-understanding/output-extract.js +26 -0
  268. package/dist/media-understanding/providers/audio.test-helpers.js +34 -0
  269. package/dist/media-understanding/providers/google/inline-data.js +64 -0
  270. package/dist/media-understanding/providers/shared.js +7 -0
  271. package/dist/media-understanding/runner.entries.js +459 -0
  272. package/dist/memory/batch-error-utils.js +11 -0
  273. package/dist/memory/batch-http.js +27 -0
  274. package/dist/memory/batch-output.js +29 -0
  275. package/dist/memory/batch-runner.js +22 -0
  276. package/dist/memory/batch-upload.js +23 -0
  277. package/dist/memory/batch-utils.js +26 -0
  278. package/dist/memory/embeddings-debug.js +11 -0
  279. package/dist/memory/embeddings-remote-client.js +22 -0
  280. package/dist/memory/embeddings-remote-fetch.js +14 -0
  281. package/dist/memory/manager-embedding-ops.js +616 -0
  282. package/dist/memory/manager-sync-ops.js +953 -0
  283. package/dist/memory/qmd-manager.js +1061 -0
  284. package/dist/memory/qmd-query-parser.js +107 -0
  285. package/dist/memory/qmd-scope.js +93 -0
  286. package/dist/memory/search-manager.js +0 -1
  287. package/dist/memory/sync-index.js +21 -0
  288. package/dist/memory/sync-progress.js +22 -0
  289. package/dist/memory/sync-stale.js +30 -0
  290. package/dist/memory/test-embeddings-mock.js +16 -0
  291. package/dist/memory/test-manager-helpers.js +14 -0
  292. package/dist/memory/test-runtime-mocks.js +11 -0
  293. package/dist/node-host/invoke-browser.js +177 -0
  294. package/dist/node-host/invoke.js +685 -0
  295. package/dist/pairing/setup-code.js +285 -0
  296. package/dist/plugin-sdk/account-id.js +1 -0
  297. package/dist/plugin-sdk/agent-media-payload.js +13 -0
  298. package/dist/plugin-sdk/allow-from.js +47 -0
  299. package/dist/plugin-sdk/command-auth.js +23 -0
  300. package/dist/plugin-sdk/config-paths.js +9 -0
  301. package/dist/plugin-sdk/file-lock.js +116 -0
  302. package/dist/plugin-sdk/json-store.js +31 -0
  303. package/dist/plugin-sdk/onboarding.js +28 -0
  304. package/dist/plugin-sdk/provider-auth-result.js +29 -0
  305. package/dist/plugin-sdk/slack-message-actions.js +133 -0
  306. package/dist/plugin-sdk/status-helpers.js +35 -0
  307. package/dist/plugin-sdk/text-chunking.js +31 -0
  308. package/dist/plugin-sdk/tool-send.js +12 -0
  309. package/dist/plugin-sdk/webhook-path.js +27 -0
  310. package/dist/plugin-sdk/webhook-targets.js +34 -0
  311. package/dist/plugins/hooks.test-helpers.js +21 -0
  312. package/dist/plugins/uninstall.js +171 -0
  313. package/dist/process/supervisor/adapters/child.js +143 -0
  314. package/dist/process/supervisor/adapters/env.js +13 -0
  315. package/dist/process/supervisor/adapters/pty.js +148 -0
  316. package/dist/process/supervisor/index.js +10 -0
  317. package/dist/process/supervisor/registry.js +117 -0
  318. package/dist/process/supervisor/supervisor.js +244 -0
  319. package/dist/process/supervisor/types.js +1 -0
  320. package/dist/providers/google-shared.test-helpers.js +75 -0
  321. package/dist/security/audit-channel.js +419 -0
  322. package/dist/security/audit-tool-policy.js +1 -0
  323. package/dist/security/scan-paths.js +12 -0
  324. package/dist/sessions/input-provenance.js +55 -0
  325. package/dist/sessions/session-key-utils.js +7 -0
  326. package/dist/shared/chat-content.js +31 -0
  327. package/dist/shared/chat-envelope.js +45 -0
  328. package/dist/shared/config-eval.js +117 -0
  329. package/dist/shared/device-auth.js +16 -0
  330. package/dist/shared/entry-metadata.js +9 -0
  331. package/dist/shared/entry-status.js +25 -0
  332. package/dist/shared/frontmatter.js +98 -0
  333. package/dist/shared/model-param-b.js +19 -0
  334. package/dist/shared/net/ipv4.js +17 -0
  335. package/dist/shared/node-match.js +53 -0
  336. package/dist/shared/requirements.js +128 -0
  337. package/dist/shared/subagents-format.js +84 -0
  338. package/dist/shared/usage-aggregates.js +28 -0
  339. package/dist/signal/monitor/mentions.js +45 -0
  340. package/dist/signal/rpc-context.js +19 -0
  341. package/dist/slack/blocks-fallback.js +76 -0
  342. package/dist/slack/blocks-input.js +40 -0
  343. package/dist/slack/draft-stream.js +106 -0
  344. package/dist/slack/message-actions.js +51 -0
  345. package/dist/slack/modal-metadata.js +32 -0
  346. package/dist/slack/monitor/events/interactions.js +462 -0
  347. package/dist/slack/monitor/room-context.js +17 -0
  348. package/dist/slack/stream-mode.js +41 -0
  349. package/dist/telegram/bot-native-command-menu.js +64 -0
  350. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  351. package/dist/telegram/button-types.js +1 -0
  352. package/dist/telegram/group-access.js +65 -0
  353. package/dist/telegram/outbound-params.js +21 -0
  354. package/dist/telegram/poll-vote-cache.js +21 -0
  355. package/dist/terminal/health-style.js +36 -0
  356. package/dist/test-utils/chunk-test-helpers.js +21 -0
  357. package/dist/test-utils/env.js +72 -0
  358. package/dist/test-utils/exec-assertions.js +12 -0
  359. package/dist/test-utils/imessage-test-plugin.js +54 -0
  360. package/dist/test-utils/mock-http-response.js +17 -0
  361. package/dist/test-utils/vitest-mock-fn.js +1 -0
  362. package/dist/tts/tts-core.js +550 -0
  363. package/dist/utils/chunk-items.js +10 -0
  364. package/dist/utils/reaction-level.js +52 -0
  365. package/dist/utils/safe-json.js +22 -0
  366. package/dist/utils/with-timeout.js +14 -0
  367. package/dist/web/media.js +17 -5
  368. package/dist/whatsapp/resolve-outbound-target.js +42 -0
  369. package/dist/wizard/onboarding.completion.js +74 -0
  370. package/extensions/bluebubbles/src/account-resolve.ts +29 -0
  371. package/extensions/bluebubbles/src/monitor-normalize.ts +796 -0
  372. package/extensions/bluebubbles/src/monitor-processing.ts +1007 -0
  373. package/extensions/bluebubbles/src/monitor-reply-cache.ts +185 -0
  374. package/extensions/bluebubbles/src/monitor-shared.ts +51 -0
  375. package/extensions/bluebubbles/src/multipart.ts +32 -0
  376. package/extensions/bluebubbles/src/send-helpers.ts +53 -0
  377. package/extensions/bluebubbles/src/test-harness.ts +50 -0
  378. package/extensions/bluebubbles/src/test-mocks.ts +11 -0
  379. package/extensions/device-pair/index.ts +554 -0
  380. package/extensions/discord/src/channel.js +366 -0
  381. package/extensions/discord/src/runtime.js +10 -0
  382. package/extensions/feishu/index.ts +63 -0
  383. package/extensions/feishu/src/accounts.ts +114 -0
  384. package/extensions/feishu/src/bitable.ts +739 -0
  385. package/extensions/feishu/src/bot.ts +965 -0
  386. package/extensions/feishu/src/channel.ts +351 -0
  387. package/extensions/feishu/src/client.ts +118 -0
  388. package/extensions/feishu/src/config-schema.ts +206 -0
  389. package/extensions/feishu/src/dedup.ts +33 -0
  390. package/extensions/feishu/src/directory.ts +177 -0
  391. package/extensions/feishu/src/doc-schema.ts +47 -0
  392. package/extensions/feishu/src/docx.ts +536 -0
  393. package/extensions/feishu/src/drive-schema.ts +46 -0
  394. package/extensions/feishu/src/drive.ts +227 -0
  395. package/extensions/feishu/src/dynamic-agent.ts +131 -0
  396. package/extensions/feishu/src/media.ts +449 -0
  397. package/extensions/feishu/src/mention.ts +126 -0
  398. package/extensions/feishu/src/monitor.ts +330 -0
  399. package/extensions/feishu/src/onboarding.ts +359 -0
  400. package/extensions/feishu/src/outbound.ts +55 -0
  401. package/extensions/feishu/src/perm-schema.ts +52 -0
  402. package/extensions/feishu/src/perm.ts +173 -0
  403. package/extensions/feishu/src/policy.ts +84 -0
  404. package/extensions/feishu/src/probe.ts +44 -0
  405. package/extensions/feishu/src/reactions.ts +160 -0
  406. package/extensions/feishu/src/reply-dispatcher.ts +239 -0
  407. package/extensions/feishu/src/runtime.ts +14 -0
  408. package/extensions/feishu/src/send-result.ts +29 -0
  409. package/extensions/feishu/src/send.ts +335 -0
  410. package/extensions/feishu/src/streaming-card.ts +223 -0
  411. package/extensions/feishu/src/targets.ts +78 -0
  412. package/extensions/feishu/src/tools-config.ts +21 -0
  413. package/extensions/feishu/src/types.ts +81 -0
  414. package/extensions/feishu/src/typing.ts +80 -0
  415. package/extensions/feishu/src/wiki-schema.ts +55 -0
  416. package/extensions/feishu/src/wiki.ts +232 -0
  417. package/extensions/imessage/src/channel.js +253 -0
  418. package/extensions/imessage/src/runtime.js +10 -0
  419. package/extensions/irc/index.ts +17 -0
  420. package/extensions/irc/src/accounts.ts +268 -0
  421. package/extensions/irc/src/channel.ts +367 -0
  422. package/extensions/irc/src/client.ts +439 -0
  423. package/extensions/irc/src/config-schema.ts +97 -0
  424. package/extensions/irc/src/connect-options.ts +30 -0
  425. package/extensions/irc/src/control-chars.ts +22 -0
  426. package/extensions/irc/src/inbound.ts +334 -0
  427. package/extensions/irc/src/monitor.ts +147 -0
  428. package/extensions/irc/src/normalize.ts +117 -0
  429. package/extensions/irc/src/onboarding.ts +479 -0
  430. package/extensions/irc/src/policy.ts +157 -0
  431. package/extensions/irc/src/probe.ts +53 -0
  432. package/extensions/irc/src/protocol.ts +169 -0
  433. package/extensions/irc/src/runtime.ts +14 -0
  434. package/extensions/irc/src/send.ts +88 -0
  435. package/extensions/irc/src/types.ts +93 -0
  436. package/extensions/matrix/src/matrix/client-bootstrap.ts +39 -0
  437. package/extensions/mattermost/src/mattermost/monitor-onchar.ts +25 -0
  438. package/extensions/mattermost/src/mattermost/monitor-websocket.ts +221 -0
  439. package/extensions/mattermost/src/mattermost/reactions.ts +130 -0
  440. package/extensions/mattermost/src/mattermost/reconnect.ts +103 -0
  441. package/extensions/minimax-portal-auth/index.ts +161 -0
  442. package/extensions/minimax-portal-auth/oauth.ts +247 -0
  443. package/extensions/msteams/src/file-lock.ts +1 -0
  444. package/extensions/msteams/src/graph.ts +92 -0
  445. package/extensions/msteams/src/mentions.ts +114 -0
  446. package/extensions/msteams/src/test-runtime.ts +16 -0
  447. package/extensions/openai-codex-auth/index.ts +177 -0
  448. package/extensions/phone-control/index.ts +421 -0
  449. package/extensions/shared/resolve-target-test-helpers.ts +66 -0
  450. package/extensions/signal/src/channel.js +273 -0
  451. package/extensions/signal/src/runtime.js +10 -0
  452. package/extensions/slack/src/channel.js +489 -0
  453. package/extensions/slack/src/runtime.js +10 -0
  454. package/extensions/talk-voice/index.ts +150 -0
  455. package/extensions/telegram/src/channel.js +424 -0
  456. package/extensions/telegram/src/runtime.js +10 -0
  457. package/extensions/thread-ownership/index.ts +133 -0
  458. package/extensions/tlon/src/account-fields.ts +25 -0
  459. package/extensions/tlon/src/urbit/base-url.ts +57 -0
  460. package/extensions/tlon/src/urbit/channel-client.ts +157 -0
  461. package/extensions/tlon/src/urbit/channel-ops.ts +164 -0
  462. package/extensions/tlon/src/urbit/context.ts +47 -0
  463. package/extensions/tlon/src/urbit/errors.ts +51 -0
  464. package/extensions/tlon/src/urbit/fetch.ts +39 -0
  465. package/extensions/twitch/src/test-fixtures.ts +30 -0
  466. package/extensions/voice-call/src/allowlist.ts +19 -0
  467. package/extensions/whatsapp/src/channel.js +429 -0
  468. package/extensions/whatsapp/src/runtime.js +10 -0
  469. package/package.json +1 -1
@@ -0,0 +1,953 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fsSync from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import chokidar from "chokidar";
6
+ import { resolveAgentDir } from "../agents/agent-scope.js";
7
+ import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
8
+ import { createSubsystemLogger } from "../logging/subsystem.js";
9
+ import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
10
+ import { resolveUserPath } from "../utils.js";
11
+ import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
12
+ import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js";
13
+ import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js";
14
+ import { createEmbeddingProvider, } from "./embeddings.js";
15
+ import { buildFileEntry, ensureDir, listMemoryFiles, normalizeExtraMemoryPaths, runWithConcurrency, } from "./internal.js";
16
+ import { ensureMemoryIndexSchema } from "./memory-schema.js";
17
+ import { buildSessionEntry, listSessionFilesForAgent, sessionPathForFile, } from "./session-files.js";
18
+ import { loadSqliteVecExtension } from "./sqlite-vec.js";
19
+ import { requireNodeSqlite } from "./sqlite.js";
20
+ const META_KEY = "memory_index_meta_v1";
21
+ const VECTOR_TABLE = "chunks_vec";
22
+ const FTS_TABLE = "chunks_fts";
23
+ const EMBEDDING_CACHE_TABLE = "embedding_cache";
24
+ const SESSION_DIRTY_DEBOUNCE_MS = 5000;
25
+ const SESSION_DELTA_READ_CHUNK_BYTES = 64 * 1024;
26
+ const VECTOR_LOAD_TIMEOUT_MS = 30_000;
27
+ const IGNORED_MEMORY_WATCH_DIR_NAMES = new Set([
28
+ ".git",
29
+ "node_modules",
30
+ ".pnpm-store",
31
+ ".venv",
32
+ "venv",
33
+ ".tox",
34
+ "__pycache__",
35
+ ]);
36
+ const log = createSubsystemLogger("memory");
37
+ function shouldIgnoreMemoryWatchPath(watchPath) {
38
+ const normalized = path.normalize(watchPath);
39
+ const parts = normalized.split(path.sep).map((segment) => segment.trim().toLowerCase());
40
+ return parts.some((segment) => IGNORED_MEMORY_WATCH_DIR_NAMES.has(segment));
41
+ }
42
+ export class MemoryManagerSyncOps {
43
+ provider = null;
44
+ fallbackFrom;
45
+ openAi;
46
+ gemini;
47
+ voyage;
48
+ sources = new Set();
49
+ providerKey = null;
50
+ fts = { enabled: false, available: false };
51
+ vectorReady = null;
52
+ watcher = null;
53
+ watchTimer = null;
54
+ sessionWatchTimer = null;
55
+ sessionUnsubscribe = null;
56
+ fallbackReason;
57
+ intervalTimer = null;
58
+ closed = false;
59
+ dirty = false;
60
+ sessionsDirty = false;
61
+ sessionsDirtyFiles = new Set();
62
+ sessionPendingFiles = new Set();
63
+ sessionDeltas = new Map();
64
+ async ensureVectorReady(dimensions) {
65
+ if (!this.vector.enabled) {
66
+ return false;
67
+ }
68
+ if (!this.vectorReady) {
69
+ this.vectorReady = this.withTimeout(this.loadVectorExtension(), VECTOR_LOAD_TIMEOUT_MS, `sqlite-vec load timed out after ${Math.round(VECTOR_LOAD_TIMEOUT_MS / 1000)}s`);
70
+ }
71
+ let ready = false;
72
+ try {
73
+ ready = (await this.vectorReady) || false;
74
+ }
75
+ catch (err) {
76
+ const message = err instanceof Error ? err.message : String(err);
77
+ this.vector.available = false;
78
+ this.vector.loadError = message;
79
+ this.vectorReady = null;
80
+ log.warn(`sqlite-vec unavailable: ${message}`);
81
+ return false;
82
+ }
83
+ if (ready && typeof dimensions === "number" && dimensions > 0) {
84
+ this.ensureVectorTable(dimensions);
85
+ }
86
+ return ready;
87
+ }
88
+ async loadVectorExtension() {
89
+ if (this.vector.available !== null) {
90
+ return this.vector.available;
91
+ }
92
+ if (!this.vector.enabled) {
93
+ this.vector.available = false;
94
+ return false;
95
+ }
96
+ try {
97
+ const resolvedPath = this.vector.extensionPath?.trim()
98
+ ? resolveUserPath(this.vector.extensionPath)
99
+ : undefined;
100
+ const loaded = await loadSqliteVecExtension({ db: this.db, extensionPath: resolvedPath });
101
+ if (!loaded.ok) {
102
+ throw new Error(loaded.error ?? "unknown sqlite-vec load error");
103
+ }
104
+ this.vector.extensionPath = loaded.extensionPath;
105
+ this.vector.available = true;
106
+ return true;
107
+ }
108
+ catch (err) {
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ this.vector.available = false;
111
+ this.vector.loadError = message;
112
+ log.warn(`sqlite-vec unavailable: ${message}`);
113
+ return false;
114
+ }
115
+ }
116
+ ensureVectorTable(dimensions) {
117
+ if (this.vector.dims === dimensions) {
118
+ return;
119
+ }
120
+ if (this.vector.dims && this.vector.dims !== dimensions) {
121
+ this.dropVectorTable();
122
+ }
123
+ this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS ${VECTOR_TABLE} USING vec0(\n` +
124
+ ` id TEXT PRIMARY KEY,\n` +
125
+ ` embedding FLOAT[${dimensions}]\n` +
126
+ `)`);
127
+ this.vector.dims = dimensions;
128
+ }
129
+ dropVectorTable() {
130
+ try {
131
+ this.db.exec(`DROP TABLE IF EXISTS ${VECTOR_TABLE}`);
132
+ }
133
+ catch (err) {
134
+ const message = err instanceof Error ? err.message : String(err);
135
+ log.debug(`Failed to drop ${VECTOR_TABLE}: ${message}`);
136
+ }
137
+ }
138
+ buildSourceFilter(alias) {
139
+ const sources = Array.from(this.sources);
140
+ if (sources.length === 0) {
141
+ return { sql: "", params: [] };
142
+ }
143
+ const column = alias ? `${alias}.source` : "source";
144
+ const placeholders = sources.map(() => "?").join(", ");
145
+ return { sql: ` AND ${column} IN (${placeholders})`, params: sources };
146
+ }
147
+ openDatabase() {
148
+ const dbPath = resolveUserPath(this.settings.store.path);
149
+ return this.openDatabaseAtPath(dbPath);
150
+ }
151
+ openDatabaseAtPath(dbPath) {
152
+ const dir = path.dirname(dbPath);
153
+ ensureDir(dir);
154
+ const { DatabaseSync } = requireNodeSqlite();
155
+ return new DatabaseSync(dbPath, { allowExtension: this.settings.store.vector.enabled });
156
+ }
157
+ seedEmbeddingCache(sourceDb) {
158
+ if (!this.cache.enabled) {
159
+ return;
160
+ }
161
+ try {
162
+ const rows = sourceDb
163
+ .prepare(`SELECT provider, model, provider_key, hash, embedding, dims, updated_at FROM ${EMBEDDING_CACHE_TABLE}`)
164
+ .all();
165
+ if (!rows.length) {
166
+ return;
167
+ }
168
+ const insert = this.db.prepare(`INSERT INTO ${EMBEDDING_CACHE_TABLE} (provider, model, provider_key, hash, embedding, dims, updated_at)
169
+ VALUES (?, ?, ?, ?, ?, ?, ?)
170
+ ON CONFLICT(provider, model, provider_key, hash) DO UPDATE SET
171
+ embedding=excluded.embedding,
172
+ dims=excluded.dims,
173
+ updated_at=excluded.updated_at`);
174
+ this.db.exec("BEGIN");
175
+ for (const row of rows) {
176
+ insert.run(row.provider, row.model, row.provider_key, row.hash, row.embedding, row.dims, row.updated_at);
177
+ }
178
+ this.db.exec("COMMIT");
179
+ }
180
+ catch (err) {
181
+ try {
182
+ this.db.exec("ROLLBACK");
183
+ }
184
+ catch { }
185
+ throw err;
186
+ }
187
+ }
188
+ async swapIndexFiles(targetPath, tempPath) {
189
+ const backupPath = `${targetPath}.backup-${randomUUID()}`;
190
+ await this.moveIndexFiles(targetPath, backupPath);
191
+ try {
192
+ await this.moveIndexFiles(tempPath, targetPath);
193
+ }
194
+ catch (err) {
195
+ await this.moveIndexFiles(backupPath, targetPath);
196
+ throw err;
197
+ }
198
+ await this.removeIndexFiles(backupPath);
199
+ }
200
+ async moveIndexFiles(sourceBase, targetBase) {
201
+ const suffixes = ["", "-wal", "-shm"];
202
+ for (const suffix of suffixes) {
203
+ const source = `${sourceBase}${suffix}`;
204
+ const target = `${targetBase}${suffix}`;
205
+ try {
206
+ await fs.rename(source, target);
207
+ }
208
+ catch (err) {
209
+ if (err.code !== "ENOENT") {
210
+ throw err;
211
+ }
212
+ }
213
+ }
214
+ }
215
+ async removeIndexFiles(basePath) {
216
+ const suffixes = ["", "-wal", "-shm"];
217
+ await Promise.all(suffixes.map((suffix) => fs.rm(`${basePath}${suffix}`, { force: true })));
218
+ }
219
+ ensureSchema() {
220
+ const result = ensureMemoryIndexSchema({
221
+ db: this.db,
222
+ embeddingCacheTable: EMBEDDING_CACHE_TABLE,
223
+ ftsTable: FTS_TABLE,
224
+ ftsEnabled: this.fts.enabled,
225
+ });
226
+ this.fts.available = result.ftsAvailable;
227
+ if (result.ftsError) {
228
+ this.fts.loadError = result.ftsError;
229
+ log.warn(`fts unavailable: ${result.ftsError}`);
230
+ }
231
+ }
232
+ ensureWatcher() {
233
+ if (!this.sources.has("memory") || !this.settings.sync.watch || this.watcher) {
234
+ return;
235
+ }
236
+ const watchPaths = new Set([
237
+ path.join(this.workspaceDir, "MEMORY.md"),
238
+ path.join(this.workspaceDir, "memory.md"),
239
+ path.join(this.workspaceDir, "memory", "**", "*.md"),
240
+ ]);
241
+ const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths);
242
+ for (const entry of additionalPaths) {
243
+ try {
244
+ const stat = fsSync.lstatSync(entry);
245
+ if (stat.isSymbolicLink()) {
246
+ continue;
247
+ }
248
+ if (stat.isDirectory()) {
249
+ watchPaths.add(path.join(entry, "**", "*.md"));
250
+ continue;
251
+ }
252
+ if (stat.isFile() && entry.toLowerCase().endsWith(".md")) {
253
+ watchPaths.add(entry);
254
+ }
255
+ }
256
+ catch {
257
+ // Skip missing/unreadable additional paths.
258
+ }
259
+ }
260
+ this.watcher = chokidar.watch(Array.from(watchPaths), {
261
+ ignoreInitial: true,
262
+ ignored: (watchPath) => shouldIgnoreMemoryWatchPath(String(watchPath)),
263
+ awaitWriteFinish: {
264
+ stabilityThreshold: this.settings.sync.watchDebounceMs,
265
+ pollInterval: 100,
266
+ },
267
+ });
268
+ const markDirty = () => {
269
+ this.dirty = true;
270
+ this.scheduleWatchSync();
271
+ };
272
+ this.watcher.on("add", markDirty);
273
+ this.watcher.on("change", markDirty);
274
+ this.watcher.on("unlink", markDirty);
275
+ }
276
+ ensureSessionListener() {
277
+ if (!this.sources.has("sessions") || this.sessionUnsubscribe) {
278
+ return;
279
+ }
280
+ this.sessionUnsubscribe = onSessionTranscriptUpdate((update) => {
281
+ if (this.closed) {
282
+ return;
283
+ }
284
+ const sessionFile = update.sessionFile;
285
+ if (!this.isSessionFileForAgent(sessionFile)) {
286
+ return;
287
+ }
288
+ this.scheduleSessionDirty(sessionFile);
289
+ });
290
+ }
291
+ scheduleSessionDirty(sessionFile) {
292
+ this.sessionPendingFiles.add(sessionFile);
293
+ if (this.sessionWatchTimer) {
294
+ return;
295
+ }
296
+ this.sessionWatchTimer = setTimeout(() => {
297
+ this.sessionWatchTimer = null;
298
+ void this.processSessionDeltaBatch().catch((err) => {
299
+ log.warn(`memory session delta failed: ${String(err)}`);
300
+ });
301
+ }, SESSION_DIRTY_DEBOUNCE_MS);
302
+ }
303
+ async processSessionDeltaBatch() {
304
+ if (this.sessionPendingFiles.size === 0) {
305
+ return;
306
+ }
307
+ const pending = Array.from(this.sessionPendingFiles);
308
+ this.sessionPendingFiles.clear();
309
+ let shouldSync = false;
310
+ for (const sessionFile of pending) {
311
+ const delta = await this.updateSessionDelta(sessionFile);
312
+ if (!delta) {
313
+ continue;
314
+ }
315
+ const bytesThreshold = delta.deltaBytes;
316
+ const messagesThreshold = delta.deltaMessages;
317
+ const bytesHit = bytesThreshold <= 0 ? delta.pendingBytes > 0 : delta.pendingBytes >= bytesThreshold;
318
+ const messagesHit = messagesThreshold <= 0
319
+ ? delta.pendingMessages > 0
320
+ : delta.pendingMessages >= messagesThreshold;
321
+ if (!bytesHit && !messagesHit) {
322
+ continue;
323
+ }
324
+ this.sessionsDirtyFiles.add(sessionFile);
325
+ this.sessionsDirty = true;
326
+ delta.pendingBytes =
327
+ bytesThreshold > 0 ? Math.max(0, delta.pendingBytes - bytesThreshold) : 0;
328
+ delta.pendingMessages =
329
+ messagesThreshold > 0 ? Math.max(0, delta.pendingMessages - messagesThreshold) : 0;
330
+ shouldSync = true;
331
+ }
332
+ if (shouldSync) {
333
+ void this.sync({ reason: "session-delta" }).catch((err) => {
334
+ log.warn(`memory sync failed (session-delta): ${String(err)}`);
335
+ });
336
+ }
337
+ }
338
+ async updateSessionDelta(sessionFile) {
339
+ const thresholds = this.settings.sync.sessions;
340
+ if (!thresholds) {
341
+ return null;
342
+ }
343
+ let stat;
344
+ try {
345
+ stat = await fs.stat(sessionFile);
346
+ }
347
+ catch {
348
+ return null;
349
+ }
350
+ const size = stat.size;
351
+ let state = this.sessionDeltas.get(sessionFile);
352
+ if (!state) {
353
+ state = { lastSize: 0, pendingBytes: 0, pendingMessages: 0 };
354
+ this.sessionDeltas.set(sessionFile, state);
355
+ }
356
+ const deltaBytes = Math.max(0, size - state.lastSize);
357
+ if (deltaBytes === 0 && size === state.lastSize) {
358
+ return {
359
+ deltaBytes: thresholds.deltaBytes,
360
+ deltaMessages: thresholds.deltaMessages,
361
+ pendingBytes: state.pendingBytes,
362
+ pendingMessages: state.pendingMessages,
363
+ };
364
+ }
365
+ if (size < state.lastSize) {
366
+ state.lastSize = size;
367
+ state.pendingBytes += size;
368
+ const shouldCountMessages = thresholds.deltaMessages > 0 &&
369
+ (thresholds.deltaBytes <= 0 || state.pendingBytes < thresholds.deltaBytes);
370
+ if (shouldCountMessages) {
371
+ state.pendingMessages += await this.countNewlines(sessionFile, 0, size);
372
+ }
373
+ }
374
+ else {
375
+ state.pendingBytes += deltaBytes;
376
+ const shouldCountMessages = thresholds.deltaMessages > 0 &&
377
+ (thresholds.deltaBytes <= 0 || state.pendingBytes < thresholds.deltaBytes);
378
+ if (shouldCountMessages) {
379
+ state.pendingMessages += await this.countNewlines(sessionFile, state.lastSize, size);
380
+ }
381
+ state.lastSize = size;
382
+ }
383
+ this.sessionDeltas.set(sessionFile, state);
384
+ return {
385
+ deltaBytes: thresholds.deltaBytes,
386
+ deltaMessages: thresholds.deltaMessages,
387
+ pendingBytes: state.pendingBytes,
388
+ pendingMessages: state.pendingMessages,
389
+ };
390
+ }
391
+ async countNewlines(absPath, start, end) {
392
+ if (end <= start) {
393
+ return 0;
394
+ }
395
+ const handle = await fs.open(absPath, "r");
396
+ try {
397
+ let offset = start;
398
+ let count = 0;
399
+ const buffer = Buffer.alloc(SESSION_DELTA_READ_CHUNK_BYTES);
400
+ while (offset < end) {
401
+ const toRead = Math.min(buffer.length, end - offset);
402
+ const { bytesRead } = await handle.read(buffer, 0, toRead, offset);
403
+ if (bytesRead <= 0) {
404
+ break;
405
+ }
406
+ for (let i = 0; i < bytesRead; i += 1) {
407
+ if (buffer[i] === 10) {
408
+ count += 1;
409
+ }
410
+ }
411
+ offset += bytesRead;
412
+ }
413
+ return count;
414
+ }
415
+ finally {
416
+ await handle.close();
417
+ }
418
+ }
419
+ resetSessionDelta(absPath, size) {
420
+ const state = this.sessionDeltas.get(absPath);
421
+ if (!state) {
422
+ return;
423
+ }
424
+ state.lastSize = size;
425
+ state.pendingBytes = 0;
426
+ state.pendingMessages = 0;
427
+ }
428
+ isSessionFileForAgent(sessionFile) {
429
+ if (!sessionFile) {
430
+ return false;
431
+ }
432
+ const sessionsDir = resolveSessionTranscriptsDirForAgent(this.agentId);
433
+ const resolvedFile = path.resolve(sessionFile);
434
+ const resolvedDir = path.resolve(sessionsDir);
435
+ return resolvedFile.startsWith(`${resolvedDir}${path.sep}`);
436
+ }
437
+ ensureIntervalSync() {
438
+ const minutes = this.settings.sync.intervalMinutes;
439
+ if (!minutes || minutes <= 0 || this.intervalTimer) {
440
+ return;
441
+ }
442
+ const ms = minutes * 60 * 1000;
443
+ this.intervalTimer = setInterval(() => {
444
+ void this.sync({ reason: "interval" }).catch((err) => {
445
+ log.warn(`memory sync failed (interval): ${String(err)}`);
446
+ });
447
+ }, ms);
448
+ }
449
+ scheduleWatchSync() {
450
+ if (!this.sources.has("memory") || !this.settings.sync.watch) {
451
+ return;
452
+ }
453
+ if (this.watchTimer) {
454
+ clearTimeout(this.watchTimer);
455
+ }
456
+ this.watchTimer = setTimeout(() => {
457
+ this.watchTimer = null;
458
+ void this.sync({ reason: "watch" }).catch((err) => {
459
+ log.warn(`memory sync failed (watch): ${String(err)}`);
460
+ });
461
+ }, this.settings.sync.watchDebounceMs);
462
+ }
463
+ shouldSyncSessions(params, needsFullReindex = false) {
464
+ if (!this.sources.has("sessions")) {
465
+ return false;
466
+ }
467
+ if (params?.force) {
468
+ return true;
469
+ }
470
+ const reason = params?.reason;
471
+ if (reason === "session-start" || reason === "watch") {
472
+ return false;
473
+ }
474
+ if (needsFullReindex) {
475
+ return true;
476
+ }
477
+ return this.sessionsDirty && this.sessionsDirtyFiles.size > 0;
478
+ }
479
+ async syncMemoryFiles(params) {
480
+ // FTS-only mode: skip embedding sync (no provider)
481
+ if (!this.provider) {
482
+ log.debug("Skipping memory file sync in FTS-only mode (no embedding provider)");
483
+ return;
484
+ }
485
+ const files = await listMemoryFiles(this.workspaceDir, this.settings.extraPaths);
486
+ const fileEntries = await Promise.all(files.map(async (file) => buildFileEntry(file, this.workspaceDir)));
487
+ log.debug("memory sync: indexing memory files", {
488
+ files: fileEntries.length,
489
+ needsFullReindex: params.needsFullReindex,
490
+ batch: this.batch.enabled,
491
+ concurrency: this.getIndexConcurrency(),
492
+ });
493
+ const activePaths = new Set(fileEntries.map((entry) => entry.path));
494
+ if (params.progress) {
495
+ params.progress.total += fileEntries.length;
496
+ params.progress.report({
497
+ completed: params.progress.completed,
498
+ total: params.progress.total,
499
+ label: this.batch.enabled ? "Indexing memory files (batch)..." : "Indexing memory files…",
500
+ });
501
+ }
502
+ const tasks = fileEntries.map((entry) => async () => {
503
+ const record = this.db
504
+ .prepare(`SELECT hash FROM files WHERE path = ? AND source = ?`)
505
+ .get(entry.path, "memory");
506
+ if (!params.needsFullReindex && record?.hash === entry.hash) {
507
+ if (params.progress) {
508
+ params.progress.completed += 1;
509
+ params.progress.report({
510
+ completed: params.progress.completed,
511
+ total: params.progress.total,
512
+ });
513
+ }
514
+ return;
515
+ }
516
+ await this.indexFile(entry, { source: "memory" });
517
+ if (params.progress) {
518
+ params.progress.completed += 1;
519
+ params.progress.report({
520
+ completed: params.progress.completed,
521
+ total: params.progress.total,
522
+ });
523
+ }
524
+ });
525
+ await runWithConcurrency(tasks, this.getIndexConcurrency());
526
+ const staleRows = this.db
527
+ .prepare(`SELECT path FROM files WHERE source = ?`)
528
+ .all("memory");
529
+ for (const stale of staleRows) {
530
+ if (activePaths.has(stale.path)) {
531
+ continue;
532
+ }
533
+ this.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory");
534
+ try {
535
+ this.db
536
+ .prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`)
537
+ .run(stale.path, "memory");
538
+ }
539
+ catch { }
540
+ this.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "memory");
541
+ if (this.fts.enabled && this.fts.available) {
542
+ try {
543
+ this.db
544
+ .prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`)
545
+ .run(stale.path, "memory", this.provider.model);
546
+ }
547
+ catch { }
548
+ }
549
+ }
550
+ }
551
+ async syncSessionFiles(params) {
552
+ // FTS-only mode: skip embedding sync (no provider)
553
+ if (!this.provider) {
554
+ log.debug("Skipping session file sync in FTS-only mode (no embedding provider)");
555
+ return;
556
+ }
557
+ const files = await listSessionFilesForAgent(this.agentId);
558
+ const activePaths = new Set(files.map((file) => sessionPathForFile(file)));
559
+ const indexAll = params.needsFullReindex || this.sessionsDirtyFiles.size === 0;
560
+ log.debug("memory sync: indexing session files", {
561
+ files: files.length,
562
+ indexAll,
563
+ dirtyFiles: this.sessionsDirtyFiles.size,
564
+ batch: this.batch.enabled,
565
+ concurrency: this.getIndexConcurrency(),
566
+ });
567
+ if (params.progress) {
568
+ params.progress.total += files.length;
569
+ params.progress.report({
570
+ completed: params.progress.completed,
571
+ total: params.progress.total,
572
+ label: this.batch.enabled ? "Indexing session files (batch)..." : "Indexing session files…",
573
+ });
574
+ }
575
+ const tasks = files.map((absPath) => async () => {
576
+ if (!indexAll && !this.sessionsDirtyFiles.has(absPath)) {
577
+ if (params.progress) {
578
+ params.progress.completed += 1;
579
+ params.progress.report({
580
+ completed: params.progress.completed,
581
+ total: params.progress.total,
582
+ });
583
+ }
584
+ return;
585
+ }
586
+ const entry = await buildSessionEntry(absPath);
587
+ if (!entry) {
588
+ if (params.progress) {
589
+ params.progress.completed += 1;
590
+ params.progress.report({
591
+ completed: params.progress.completed,
592
+ total: params.progress.total,
593
+ });
594
+ }
595
+ return;
596
+ }
597
+ const record = this.db
598
+ .prepare(`SELECT hash FROM files WHERE path = ? AND source = ?`)
599
+ .get(entry.path, "sessions");
600
+ if (!params.needsFullReindex && record?.hash === entry.hash) {
601
+ if (params.progress) {
602
+ params.progress.completed += 1;
603
+ params.progress.report({
604
+ completed: params.progress.completed,
605
+ total: params.progress.total,
606
+ });
607
+ }
608
+ this.resetSessionDelta(absPath, entry.size);
609
+ return;
610
+ }
611
+ await this.indexFile(entry, { source: "sessions", content: entry.content });
612
+ this.resetSessionDelta(absPath, entry.size);
613
+ if (params.progress) {
614
+ params.progress.completed += 1;
615
+ params.progress.report({
616
+ completed: params.progress.completed,
617
+ total: params.progress.total,
618
+ });
619
+ }
620
+ });
621
+ await runWithConcurrency(tasks, this.getIndexConcurrency());
622
+ const staleRows = this.db
623
+ .prepare(`SELECT path FROM files WHERE source = ?`)
624
+ .all("sessions");
625
+ for (const stale of staleRows) {
626
+ if (activePaths.has(stale.path)) {
627
+ continue;
628
+ }
629
+ this.db
630
+ .prepare(`DELETE FROM files WHERE path = ? AND source = ?`)
631
+ .run(stale.path, "sessions");
632
+ try {
633
+ this.db
634
+ .prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`)
635
+ .run(stale.path, "sessions");
636
+ }
637
+ catch { }
638
+ this.db
639
+ .prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`)
640
+ .run(stale.path, "sessions");
641
+ if (this.fts.enabled && this.fts.available) {
642
+ try {
643
+ this.db
644
+ .prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`)
645
+ .run(stale.path, "sessions", this.provider.model);
646
+ }
647
+ catch { }
648
+ }
649
+ }
650
+ }
651
+ createSyncProgress(onProgress) {
652
+ const state = {
653
+ completed: 0,
654
+ total: 0,
655
+ label: undefined,
656
+ report: (update) => {
657
+ if (update.label) {
658
+ state.label = update.label;
659
+ }
660
+ const label = update.total > 0 && state.label
661
+ ? `${state.label} ${update.completed}/${update.total}`
662
+ : state.label;
663
+ onProgress({
664
+ completed: update.completed,
665
+ total: update.total,
666
+ label,
667
+ });
668
+ },
669
+ };
670
+ return state;
671
+ }
672
+ async runSync(params) {
673
+ const progress = params?.progress ? this.createSyncProgress(params.progress) : undefined;
674
+ if (progress) {
675
+ progress.report({
676
+ completed: progress.completed,
677
+ total: progress.total,
678
+ label: "Loading vector extension…",
679
+ });
680
+ }
681
+ const vectorReady = await this.ensureVectorReady();
682
+ const meta = this.readMeta();
683
+ const needsFullReindex = params?.force ||
684
+ !meta ||
685
+ (this.provider && meta.model !== this.provider.model) ||
686
+ (this.provider && meta.provider !== this.provider.id) ||
687
+ meta.providerKey !== this.providerKey ||
688
+ meta.chunkTokens !== this.settings.chunking.tokens ||
689
+ meta.chunkOverlap !== this.settings.chunking.overlap ||
690
+ (vectorReady && !meta?.vectorDims);
691
+ try {
692
+ if (needsFullReindex) {
693
+ if (process.env.POOLBOT_TEST_FAST === "1" &&
694
+ process.env.POOLBOT_TEST_MEMORY_UNSAFE_REINDEX === "1") {
695
+ await this.runUnsafeReindex({
696
+ reason: params?.reason,
697
+ force: params?.force,
698
+ progress: progress ?? undefined,
699
+ });
700
+ }
701
+ else {
702
+ await this.runSafeReindex({
703
+ reason: params?.reason,
704
+ force: params?.force,
705
+ progress: progress ?? undefined,
706
+ });
707
+ }
708
+ return;
709
+ }
710
+ const shouldSyncMemory = this.sources.has("memory") && (params?.force || needsFullReindex || this.dirty);
711
+ const shouldSyncSessions = this.shouldSyncSessions(params, needsFullReindex);
712
+ if (shouldSyncMemory) {
713
+ await this.syncMemoryFiles({ needsFullReindex, progress: progress ?? undefined });
714
+ this.dirty = false;
715
+ }
716
+ if (shouldSyncSessions) {
717
+ await this.syncSessionFiles({ needsFullReindex, progress: progress ?? undefined });
718
+ this.sessionsDirty = false;
719
+ this.sessionsDirtyFiles.clear();
720
+ }
721
+ else if (this.sessionsDirtyFiles.size > 0) {
722
+ this.sessionsDirty = true;
723
+ }
724
+ else {
725
+ this.sessionsDirty = false;
726
+ }
727
+ }
728
+ catch (err) {
729
+ const reason = err instanceof Error ? err.message : String(err);
730
+ const activated = this.shouldFallbackOnError(reason) && (await this.activateFallbackProvider(reason));
731
+ if (activated) {
732
+ await this.runSafeReindex({
733
+ reason: params?.reason ?? "fallback",
734
+ force: true,
735
+ progress: progress ?? undefined,
736
+ });
737
+ return;
738
+ }
739
+ throw err;
740
+ }
741
+ }
742
+ shouldFallbackOnError(message) {
743
+ return /embedding|embeddings|batch/i.test(message);
744
+ }
745
+ resolveBatchConfig() {
746
+ const batch = this.settings.remote?.batch;
747
+ const enabled = Boolean(batch?.enabled &&
748
+ this.provider &&
749
+ ((this.openAi && this.provider.id === "openai") ||
750
+ (this.gemini && this.provider.id === "gemini") ||
751
+ (this.voyage && this.provider.id === "voyage")));
752
+ return {
753
+ enabled,
754
+ wait: batch?.wait ?? true,
755
+ concurrency: Math.max(1, batch?.concurrency ?? 2),
756
+ pollIntervalMs: batch?.pollIntervalMs ?? 2000,
757
+ timeoutMs: (batch?.timeoutMinutes ?? 60) * 60 * 1000,
758
+ };
759
+ }
760
+ async activateFallbackProvider(reason) {
761
+ const fallback = this.settings.fallback;
762
+ if (!fallback || fallback === "none" || !this.provider || fallback === this.provider.id) {
763
+ return false;
764
+ }
765
+ if (this.fallbackFrom) {
766
+ return false;
767
+ }
768
+ const fallbackFrom = this.provider.id;
769
+ const fallbackModel = fallback === "gemini"
770
+ ? DEFAULT_GEMINI_EMBEDDING_MODEL
771
+ : fallback === "openai"
772
+ ? DEFAULT_OPENAI_EMBEDDING_MODEL
773
+ : fallback === "voyage"
774
+ ? DEFAULT_VOYAGE_EMBEDDING_MODEL
775
+ : this.settings.model;
776
+ const fallbackResult = await createEmbeddingProvider({
777
+ config: this.cfg,
778
+ agentDir: resolveAgentDir(this.cfg, this.agentId),
779
+ provider: fallback,
780
+ remote: this.settings.remote,
781
+ model: fallbackModel,
782
+ fallback: "none",
783
+ local: this.settings.local,
784
+ });
785
+ this.fallbackFrom = fallbackFrom;
786
+ this.fallbackReason = reason;
787
+ this.provider = fallbackResult.provider;
788
+ this.openAi = fallbackResult.openAi;
789
+ this.gemini = fallbackResult.gemini;
790
+ this.voyage = fallbackResult.voyage;
791
+ this.providerKey = this.computeProviderKey();
792
+ this.batch = this.resolveBatchConfig();
793
+ log.warn(`memory embeddings: switched to fallback provider (${fallback})`, { reason });
794
+ return true;
795
+ }
796
+ async runSafeReindex(params) {
797
+ const dbPath = resolveUserPath(this.settings.store.path);
798
+ const tempDbPath = `${dbPath}.tmp-${randomUUID()}`;
799
+ const tempDb = this.openDatabaseAtPath(tempDbPath);
800
+ const originalDb = this.db;
801
+ let originalDbClosed = false;
802
+ const originalState = {
803
+ ftsAvailable: this.fts.available,
804
+ ftsError: this.fts.loadError,
805
+ vectorAvailable: this.vector.available,
806
+ vectorLoadError: this.vector.loadError,
807
+ vectorDims: this.vector.dims,
808
+ vectorReady: this.vectorReady,
809
+ };
810
+ const restoreOriginalState = () => {
811
+ if (originalDbClosed) {
812
+ this.db = this.openDatabaseAtPath(dbPath);
813
+ }
814
+ else {
815
+ this.db = originalDb;
816
+ }
817
+ this.fts.available = originalState.ftsAvailable;
818
+ this.fts.loadError = originalState.ftsError;
819
+ this.vector.available = originalDbClosed ? null : originalState.vectorAvailable;
820
+ this.vector.loadError = originalState.vectorLoadError;
821
+ this.vector.dims = originalState.vectorDims;
822
+ this.vectorReady = originalDbClosed ? null : originalState.vectorReady;
823
+ };
824
+ this.db = tempDb;
825
+ this.vectorReady = null;
826
+ this.vector.available = null;
827
+ this.vector.loadError = undefined;
828
+ this.vector.dims = undefined;
829
+ this.fts.available = false;
830
+ this.fts.loadError = undefined;
831
+ this.ensureSchema();
832
+ let nextMeta = null;
833
+ try {
834
+ this.seedEmbeddingCache(originalDb);
835
+ const shouldSyncMemory = this.sources.has("memory");
836
+ const shouldSyncSessions = this.shouldSyncSessions({ reason: params.reason, force: params.force }, true);
837
+ if (shouldSyncMemory) {
838
+ await this.syncMemoryFiles({ needsFullReindex: true, progress: params.progress });
839
+ this.dirty = false;
840
+ }
841
+ if (shouldSyncSessions) {
842
+ await this.syncSessionFiles({ needsFullReindex: true, progress: params.progress });
843
+ this.sessionsDirty = false;
844
+ this.sessionsDirtyFiles.clear();
845
+ }
846
+ else if (this.sessionsDirtyFiles.size > 0) {
847
+ this.sessionsDirty = true;
848
+ }
849
+ else {
850
+ this.sessionsDirty = false;
851
+ }
852
+ nextMeta = {
853
+ model: this.provider?.model ?? "fts-only",
854
+ provider: this.provider?.id ?? "none",
855
+ providerKey: this.providerKey,
856
+ chunkTokens: this.settings.chunking.tokens,
857
+ chunkOverlap: this.settings.chunking.overlap,
858
+ };
859
+ if (!nextMeta) {
860
+ throw new Error("Failed to compute memory index metadata for reindexing.");
861
+ }
862
+ if (this.vector.available && this.vector.dims) {
863
+ nextMeta.vectorDims = this.vector.dims;
864
+ }
865
+ this.writeMeta(nextMeta);
866
+ this.pruneEmbeddingCacheIfNeeded?.();
867
+ this.db.close();
868
+ originalDb.close();
869
+ originalDbClosed = true;
870
+ await this.swapIndexFiles(dbPath, tempDbPath);
871
+ this.db = this.openDatabaseAtPath(dbPath);
872
+ this.vectorReady = null;
873
+ this.vector.available = null;
874
+ this.vector.loadError = undefined;
875
+ this.ensureSchema();
876
+ this.vector.dims = nextMeta?.vectorDims;
877
+ }
878
+ catch (err) {
879
+ try {
880
+ this.db.close();
881
+ }
882
+ catch { }
883
+ await this.removeIndexFiles(tempDbPath);
884
+ restoreOriginalState();
885
+ throw err;
886
+ }
887
+ }
888
+ async runUnsafeReindex(params) {
889
+ // Perf: for test runs, skip atomic temp-db swapping. The index is isolated
890
+ // under the per-test HOME anyway, and this cuts substantial fs+sqlite churn.
891
+ this.resetIndex();
892
+ const shouldSyncMemory = this.sources.has("memory");
893
+ const shouldSyncSessions = this.shouldSyncSessions({ reason: params.reason, force: params.force }, true);
894
+ if (shouldSyncMemory) {
895
+ await this.syncMemoryFiles({ needsFullReindex: true, progress: params.progress });
896
+ this.dirty = false;
897
+ }
898
+ if (shouldSyncSessions) {
899
+ await this.syncSessionFiles({ needsFullReindex: true, progress: params.progress });
900
+ this.sessionsDirty = false;
901
+ this.sessionsDirtyFiles.clear();
902
+ }
903
+ else if (this.sessionsDirtyFiles.size > 0) {
904
+ this.sessionsDirty = true;
905
+ }
906
+ else {
907
+ this.sessionsDirty = false;
908
+ }
909
+ const nextMeta = {
910
+ model: this.provider?.model ?? "fts-only",
911
+ provider: this.provider?.id ?? "none",
912
+ providerKey: this.providerKey,
913
+ chunkTokens: this.settings.chunking.tokens,
914
+ chunkOverlap: this.settings.chunking.overlap,
915
+ };
916
+ if (this.vector.available && this.vector.dims) {
917
+ nextMeta.vectorDims = this.vector.dims;
918
+ }
919
+ this.writeMeta(nextMeta);
920
+ this.pruneEmbeddingCacheIfNeeded?.();
921
+ }
922
+ resetIndex() {
923
+ this.db.exec(`DELETE FROM files`);
924
+ this.db.exec(`DELETE FROM chunks`);
925
+ if (this.fts.enabled && this.fts.available) {
926
+ try {
927
+ this.db.exec(`DELETE FROM ${FTS_TABLE}`);
928
+ }
929
+ catch { }
930
+ }
931
+ this.dropVectorTable();
932
+ this.vector.dims = undefined;
933
+ this.sessionsDirtyFiles.clear();
934
+ }
935
+ readMeta() {
936
+ const row = this.db.prepare(`SELECT value FROM meta WHERE key = ?`).get(META_KEY);
937
+ if (!row?.value) {
938
+ return null;
939
+ }
940
+ try {
941
+ return JSON.parse(row.value);
942
+ }
943
+ catch {
944
+ return null;
945
+ }
946
+ }
947
+ writeMeta(meta) {
948
+ const value = JSON.stringify(meta);
949
+ this.db
950
+ .prepare(`INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`)
951
+ .run(META_KEY, value);
952
+ }
953
+ }