@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -20,12 +20,14 @@ function createMarkdownIt(options) {
20
20
  return md;
21
21
  }
22
22
  function getAttr(token, name) {
23
- if (token.attrGet)
23
+ if (token.attrGet) {
24
24
  return token.attrGet(name);
25
+ }
25
26
  if (token.attrs) {
26
27
  for (const [key, value] of token.attrs) {
27
- if (key === name)
28
+ if (key === name) {
28
29
  return value;
30
+ }
29
31
  }
30
32
  }
31
33
  return null;
@@ -87,8 +89,9 @@ function resolveRenderTarget(state) {
87
89
  return state.table?.currentCell ?? state;
88
90
  }
89
91
  function appendText(state, value) {
90
- if (!value)
92
+ if (!value) {
91
93
  return;
94
+ }
92
95
  const target = resolveRenderTarget(state);
93
96
  target.text += value;
94
97
  }
@@ -111,25 +114,29 @@ function closeStyle(state, style) {
111
114
  }
112
115
  }
113
116
  function appendParagraphSeparator(state) {
114
- if (state.env.listStack.length > 0)
117
+ if (state.env.listStack.length > 0) {
115
118
  return;
116
- if (state.table)
117
- return; // Don't add paragraph separators inside tables
119
+ }
120
+ if (state.table) {
121
+ return;
122
+ } // Don't add paragraph separators inside tables
118
123
  state.text += "\n\n";
119
124
  }
120
125
  function appendListPrefix(state) {
121
126
  const stack = state.env.listStack;
122
127
  const top = stack[stack.length - 1];
123
- if (!top)
128
+ if (!top) {
124
129
  return;
130
+ }
125
131
  top.index += 1;
126
132
  const indent = " ".repeat(Math.max(0, stack.length - 1));
127
133
  const prefix = top.type === "ordered" ? `${top.index}. ` : "• ";
128
134
  state.text += `${indent}${prefix}`;
129
135
  }
130
136
  function renderInlineCode(state, content) {
131
- if (!content)
137
+ if (!content) {
132
138
  return;
139
+ }
133
140
  const target = resolveRenderTarget(state);
134
141
  const start = target.text.length;
135
142
  target.text += content;
@@ -137,8 +144,9 @@ function renderInlineCode(state, content) {
137
144
  }
138
145
  function renderCodeBlock(state, content) {
139
146
  let code = content ?? "";
140
- if (!code.endsWith("\n"))
147
+ if (!code.endsWith("\n")) {
141
148
  code = `${code}\n`;
149
+ }
142
150
  const target = resolveRenderTarget(state);
143
151
  const start = target.text.length;
144
152
  target.text += code;
@@ -150,11 +158,13 @@ function renderCodeBlock(state, content) {
150
158
  function handleLinkClose(state) {
151
159
  const target = resolveRenderTarget(state);
152
160
  const link = target.linkStack.pop();
153
- if (!link?.href)
161
+ if (!link?.href) {
154
162
  return;
163
+ }
155
164
  const href = link.href.trim();
156
- if (!href)
165
+ if (!href) {
157
166
  return;
167
+ }
158
168
  const start = link.labelStart;
159
169
  const end = target.text.length;
160
170
  if (end <= start) {
@@ -184,12 +194,15 @@ function trimCell(cell) {
184
194
  const text = cell.text;
185
195
  let start = 0;
186
196
  let end = text.length;
187
- while (start < end && /\s/.test(text[start] ?? ""))
197
+ while (start < end && /\s/.test(text[start] ?? "")) {
188
198
  start += 1;
189
- while (end > start && /\s/.test(text[end - 1] ?? ""))
199
+ }
200
+ while (end > start && /\s/.test(text[end - 1] ?? "")) {
190
201
  end -= 1;
191
- if (start === 0 && end === text.length)
202
+ }
203
+ if (start === 0 && end === text.length) {
192
204
  return cell;
205
+ }
193
206
  const trimmedText = text.slice(start, end);
194
207
  const trimmedLength = trimmedText.length;
195
208
  const trimmedStyles = [];
@@ -211,8 +224,9 @@ function trimCell(cell) {
211
224
  return { text: trimmedText, styles: trimmedStyles, links: trimmedLinks };
212
225
  }
213
226
  function appendCell(state, cell) {
214
- if (!cell.text)
227
+ if (!cell.text) {
215
228
  return;
229
+ }
216
230
  const start = state.text.length;
217
231
  state.text += cell.text;
218
232
  for (const span of cell.styles) {
@@ -230,22 +244,32 @@ function appendCell(state, cell) {
230
244
  });
231
245
  }
232
246
  }
247
+ function appendCellTextOnly(state, cell) {
248
+ if (!cell.text) {
249
+ return;
250
+ }
251
+ state.text += cell.text;
252
+ // Do not append styles - this is used for code blocks where inner styles would overlap
253
+ }
233
254
  function renderTableAsBullets(state) {
234
- if (!state.table)
255
+ if (!state.table) {
235
256
  return;
257
+ }
236
258
  const headers = state.table.headers.map(trimCell);
237
259
  const rows = state.table.rows.map((row) => row.map(trimCell));
238
260
  // If no headers or rows, skip
239
- if (headers.length === 0 && rows.length === 0)
261
+ if (headers.length === 0 && rows.length === 0) {
240
262
  return;
263
+ }
241
264
  // Determine if first column should be used as row labels
242
265
  // (common pattern: first column is category/feature name)
243
266
  const useFirstColAsLabel = headers.length > 1 && rows.length > 0;
244
267
  if (useFirstColAsLabel) {
245
268
  // Format: each row becomes a section with header as row[0], then key:value pairs
246
269
  for (const row of rows) {
247
- if (row.length === 0)
270
+ if (row.length === 0) {
248
271
  continue;
272
+ }
249
273
  const rowLabel = row[0];
250
274
  if (rowLabel?.text) {
251
275
  const labelStart = state.text.length;
@@ -260,8 +284,9 @@ function renderTableAsBullets(state) {
260
284
  for (let i = 1; i < row.length; i++) {
261
285
  const header = headers[i];
262
286
  const value = row[i];
263
- if (!value?.text)
287
+ if (!value?.text) {
264
288
  continue;
289
+ }
265
290
  state.text += "• ";
266
291
  if (header?.text) {
267
292
  appendCell(state, header);
@@ -282,8 +307,9 @@ function renderTableAsBullets(state) {
282
307
  for (let i = 0; i < row.length; i++) {
283
308
  const header = headers[i];
284
309
  const value = row[i];
285
- if (!value?.text)
310
+ if (!value?.text) {
286
311
  continue;
312
+ }
287
313
  state.text += "• ";
288
314
  if (header?.text) {
289
315
  appendCell(state, header);
@@ -297,36 +323,43 @@ function renderTableAsBullets(state) {
297
323
  }
298
324
  }
299
325
  function renderTableAsCode(state) {
300
- if (!state.table)
326
+ if (!state.table) {
301
327
  return;
328
+ }
302
329
  const headers = state.table.headers.map(trimCell);
303
330
  const rows = state.table.rows.map((row) => row.map(trimCell));
304
331
  const columnCount = Math.max(headers.length, ...rows.map((row) => row.length));
305
- if (columnCount === 0)
332
+ if (columnCount === 0) {
306
333
  return;
334
+ }
307
335
  const widths = Array.from({ length: columnCount }, () => 0);
308
336
  const updateWidths = (cells) => {
309
337
  for (let i = 0; i < columnCount; i += 1) {
310
338
  const cell = cells[i];
311
339
  const width = cell?.text.length ?? 0;
312
- if (widths[i] < width)
340
+ if (widths[i] < width) {
313
341
  widths[i] = width;
342
+ }
314
343
  }
315
344
  };
316
345
  updateWidths(headers);
317
- for (const row of rows)
346
+ for (const row of rows) {
318
347
  updateWidths(row);
348
+ }
319
349
  const codeStart = state.text.length;
320
350
  const appendRow = (cells) => {
321
351
  state.text += "|";
322
352
  for (let i = 0; i < columnCount; i += 1) {
323
353
  state.text += " ";
324
354
  const cell = cells[i];
325
- if (cell)
326
- appendCell(state, cell);
355
+ if (cell) {
356
+ // Use text-only append to avoid overlapping styles with code_block
357
+ appendCellTextOnly(state, cell);
358
+ }
327
359
  const pad = widths[i] - (cell?.text.length ?? 0);
328
- if (pad > 0)
360
+ if (pad > 0) {
329
361
  state.text += " ".repeat(pad);
362
+ }
330
363
  state.text += " |";
331
364
  }
332
365
  state.text += "\n";
@@ -356,8 +389,9 @@ function renderTokens(tokens, state) {
356
389
  for (const token of tokens) {
357
390
  switch (token.type) {
358
391
  case "inline":
359
- if (token.children)
392
+ if (token.children) {
360
393
  renderTokens(token.children, state);
394
+ }
361
395
  break;
362
396
  case "text":
363
397
  appendText(state, token.content ?? "");
@@ -384,12 +418,14 @@ function renderTokens(tokens, state) {
384
418
  renderInlineCode(state, token.content ?? "");
385
419
  break;
386
420
  case "spoiler_open":
387
- if (state.enableSpoilers)
421
+ if (state.enableSpoilers) {
388
422
  openStyle(state, "spoiler");
423
+ }
389
424
  break;
390
425
  case "spoiler_close":
391
- if (state.enableSpoilers)
426
+ if (state.enableSpoilers) {
392
427
  closeStyle(state, "spoiler");
428
+ }
393
429
  break;
394
430
  case "link_open": {
395
431
  const href = getAttr(token, "href") ?? "";
@@ -411,40 +447,61 @@ function renderTokens(tokens, state) {
411
447
  appendParagraphSeparator(state);
412
448
  break;
413
449
  case "heading_open":
414
- if (state.headingStyle === "bold")
450
+ if (state.headingStyle === "bold") {
415
451
  openStyle(state, "bold");
452
+ }
416
453
  break;
417
454
  case "heading_close":
418
- if (state.headingStyle === "bold")
455
+ if (state.headingStyle === "bold") {
419
456
  closeStyle(state, "bold");
457
+ }
420
458
  appendParagraphSeparator(state);
421
459
  break;
422
460
  case "blockquote_open":
423
- if (state.blockquotePrefix)
461
+ if (state.blockquotePrefix) {
424
462
  state.text += state.blockquotePrefix;
463
+ }
464
+ openStyle(state, "blockquote");
425
465
  break;
426
466
  case "blockquote_close":
427
- state.text += "\n";
467
+ closeStyle(state, "blockquote");
428
468
  break;
429
469
  case "bullet_list_open":
470
+ // Add newline before nested list starts (so nested items appear on new line)
471
+ if (state.env.listStack.length > 0) {
472
+ state.text += "\n";
473
+ }
430
474
  state.env.listStack.push({ type: "bullet", index: 0 });
431
475
  break;
432
476
  case "bullet_list_close":
433
477
  state.env.listStack.pop();
478
+ if (state.env.listStack.length === 0) {
479
+ state.text += "\n";
480
+ }
434
481
  break;
435
482
  case "ordered_list_open": {
483
+ // Add newline before nested list starts (so nested items appear on new line)
484
+ if (state.env.listStack.length > 0) {
485
+ state.text += "\n";
486
+ }
436
487
  const start = Number(getAttr(token, "start") ?? "1");
437
488
  state.env.listStack.push({ type: "ordered", index: start - 1 });
438
489
  break;
439
490
  }
440
491
  case "ordered_list_close":
441
492
  state.env.listStack.pop();
493
+ if (state.env.listStack.length === 0) {
494
+ state.text += "\n";
495
+ }
442
496
  break;
443
497
  case "list_item_open":
444
498
  appendListPrefix(state);
445
499
  break;
446
500
  case "list_item_close":
447
- state.text += "\n";
501
+ // Avoid double newlines (nested list's last item already added newline)
502
+ if (!state.text.endsWith("\n")) {
503
+ state.text += "\n";
504
+ }
448
505
  break;
449
506
  case "code_block":
450
507
  case "fence":
@@ -515,11 +572,13 @@ function renderTokens(tokens, state) {
515
572
  }
516
573
  break;
517
574
  case "hr":
518
- state.text += "\n";
575
+ // Render as a visual separator
576
+ state.text += "───\n\n";
519
577
  break;
520
578
  default:
521
- if (token.children)
579
+ if (token.children) {
522
580
  renderTokens(token.children, state);
581
+ }
523
582
  break;
524
583
  }
525
584
  }
@@ -543,8 +602,9 @@ function clampStyleSpans(spans, maxLength) {
543
602
  for (const span of spans) {
544
603
  const start = Math.max(0, Math.min(span.start, maxLength));
545
604
  const end = Math.max(start, Math.min(span.end, maxLength));
546
- if (end > start)
605
+ if (end > start) {
547
606
  clamped.push({ start, end, style: span.style });
607
+ }
548
608
  }
549
609
  return clamped;
550
610
  }
@@ -553,23 +613,30 @@ function clampLinkSpans(spans, maxLength) {
553
613
  for (const span of spans) {
554
614
  const start = Math.max(0, Math.min(span.start, maxLength));
555
615
  const end = Math.max(start, Math.min(span.end, maxLength));
556
- if (end > start)
616
+ if (end > start) {
557
617
  clamped.push({ start, end, href: span.href });
618
+ }
558
619
  }
559
620
  return clamped;
560
621
  }
561
622
  function mergeStyleSpans(spans) {
562
- const sorted = [...spans].sort((a, b) => {
563
- if (a.start !== b.start)
623
+ const sorted = [...spans].toSorted((a, b) => {
624
+ if (a.start !== b.start) {
564
625
  return a.start - b.start;
565
- if (a.end !== b.end)
626
+ }
627
+ if (a.end !== b.end) {
566
628
  return a.end - b.end;
629
+ }
567
630
  return a.style.localeCompare(b.style);
568
631
  });
569
632
  const merged = [];
570
633
  for (const span of sorted) {
571
634
  const prev = merged[merged.length - 1];
572
- if (prev && prev.style === span.style && span.start <= prev.end) {
635
+ if (prev &&
636
+ prev.style === span.style &&
637
+ // Blockquotes are container blocks. Adjacent blockquote spans should not merge or
638
+ // consecutive blockquotes can "style bleed" across the paragraph boundary.
639
+ (span.start < prev.end || (span.start === prev.end && span.style !== "blockquote"))) {
573
640
  prev.end = Math.max(prev.end, span.end);
574
641
  continue;
575
642
  }
@@ -578,8 +645,9 @@ function mergeStyleSpans(spans) {
578
645
  return merged;
579
646
  }
580
647
  function sliceStyleSpans(spans, start, end) {
581
- if (spans.length === 0)
648
+ if (spans.length === 0) {
582
649
  return [];
650
+ }
583
651
  const sliced = [];
584
652
  for (const span of spans) {
585
653
  const sliceStart = Math.max(span.start, start);
@@ -595,8 +663,9 @@ function sliceStyleSpans(spans, start, end) {
595
663
  return mergeStyleSpans(sliced);
596
664
  }
597
665
  function sliceLinkSpans(spans, start, end) {
598
- if (spans.length === 0)
666
+ if (spans.length === 0) {
599
667
  return [];
668
+ }
600
669
  const sliced = [];
601
670
  for (const span of spans) {
602
671
  const sliceStart = Math.max(span.start, start);
@@ -642,10 +711,12 @@ export function markdownToIRWithMeta(markdown, options = {}) {
642
711
  const trimmedLength = trimmedText.length;
643
712
  let codeBlockEnd = 0;
644
713
  for (const span of state.styles) {
645
- if (span.style !== "code_block")
714
+ if (span.style !== "code_block") {
646
715
  continue;
647
- if (span.end > codeBlockEnd)
716
+ }
717
+ if (span.end > codeBlockEnd) {
648
718
  codeBlockEnd = span.end;
719
+ }
649
720
  }
650
721
  const finalLength = Math.max(trimmedLength, codeBlockEnd);
651
722
  const finalText = finalLength === state.text.length ? state.text : state.text.slice(0, finalLength);
@@ -659,16 +730,19 @@ export function markdownToIRWithMeta(markdown, options = {}) {
659
730
  };
660
731
  }
661
732
  export function chunkMarkdownIR(ir, limit) {
662
- if (!ir.text)
733
+ if (!ir.text) {
663
734
  return [];
664
- if (limit <= 0 || ir.text.length <= limit)
735
+ }
736
+ if (limit <= 0 || ir.text.length <= limit) {
665
737
  return [ir];
738
+ }
666
739
  const chunks = chunkText(ir.text, limit);
667
740
  const results = [];
668
741
  let cursor = 0;
669
742
  chunks.forEach((chunk, index) => {
670
- if (!chunk)
743
+ if (!chunk) {
671
744
  return;
745
+ }
672
746
  if (index > 0) {
673
747
  while (cursor < ir.text.length && /\s/.test(ir.text[cursor] ?? "")) {
674
748
  cursor += 1;
@@ -19,7 +19,7 @@ const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
19
19
  * Prevents LFI (Local File Inclusion) attacks by restricting
20
20
  * local file extraction to known-safe directories.
21
21
  *
22
- * Backported from: openclaw/openclaw@2026.1.30 (PR #4880)
22
+ * Backported from: poolbot/poolbot@2026.1.30 (PR #4880)
23
23
  * Security: CRITICAL
24
24
  */
25
25
  const SAFE_PATHS = [
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
+ import { collectProviderApiKeysForExecution, executeWithApiKeyRotation, } from "../agents/api-key-rotation.js";
4
5
  import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js";
5
6
  import { applyTemplate } from "../auto-reply/templating.js";
6
7
  import { logVerbose, shouldLogVerbose } from "../globals.js";
@@ -307,6 +308,7 @@ export async function runProviderEntry(params) {
307
308
  if (!provider.transcribeAudio) {
308
309
  throw new Error(`Audio transcription provider "${providerId}" not available.`);
309
310
  }
311
+ const transcribeAudio = provider.transcribeAudio;
310
312
  const media = await params.cache.getBuffer({
311
313
  attachmentIndex: params.attachmentIndex,
312
314
  maxBytes,
@@ -319,7 +321,10 @@ export async function runProviderEntry(params) {
319
321
  preferredProfile: entry.preferredProfile,
320
322
  agentDir: params.agentDir,
321
323
  });
322
- const apiKey = requireApiKey(auth, providerId);
324
+ const apiKeys = collectProviderApiKeysForExecution({
325
+ provider: providerId,
326
+ primaryApiKey: requireApiKey(auth, providerId),
327
+ });
323
328
  const providerConfig = cfg.models?.providers?.[providerId];
324
329
  const baseUrl = entry.baseUrl ?? params.config?.baseUrl ?? providerConfig?.baseUrl;
325
330
  const mergedHeaders = {
@@ -334,18 +339,22 @@ export async function runProviderEntry(params) {
334
339
  entry,
335
340
  });
336
341
  const model = entry.model?.trim() || DEFAULT_AUDIO_MODELS[providerId] || entry.model;
337
- const result = await provider.transcribeAudio({
338
- buffer: media.buffer,
339
- fileName: media.fileName,
340
- mime: media.mime,
341
- apiKey,
342
- baseUrl,
343
- headers,
344
- model,
345
- language: entry.language ?? params.config?.language ?? cfg.tools?.media?.audio?.language,
346
- prompt,
347
- query: providerQuery,
348
- timeoutMs,
342
+ const result = await executeWithApiKeyRotation({
343
+ provider: providerId,
344
+ apiKeys,
345
+ execute: async (apiKey) => transcribeAudio({
346
+ buffer: media.buffer,
347
+ fileName: media.fileName,
348
+ mime: media.mime,
349
+ apiKey,
350
+ baseUrl,
351
+ headers,
352
+ model,
353
+ language: entry.language ?? params.config?.language ?? cfg.tools?.media?.audio?.language,
354
+ prompt,
355
+ query: providerQuery,
356
+ timeoutMs,
357
+ }),
349
358
  });
350
359
  return {
351
360
  kind: "audio.transcription",
@@ -358,6 +367,7 @@ export async function runProviderEntry(params) {
358
367
  if (!provider.describeVideo) {
359
368
  throw new Error(`Video understanding provider "${providerId}" not available.`);
360
369
  }
370
+ const describeVideo = provider.describeVideo;
361
371
  const media = await params.cache.getBuffer({
362
372
  attachmentIndex: params.attachmentIndex,
363
373
  maxBytes,
@@ -375,18 +385,25 @@ export async function runProviderEntry(params) {
375
385
  preferredProfile: entry.preferredProfile,
376
386
  agentDir: params.agentDir,
377
387
  });
378
- const apiKey = requireApiKey(auth, providerId);
388
+ const apiKeys = collectProviderApiKeysForExecution({
389
+ provider: providerId,
390
+ primaryApiKey: requireApiKey(auth, providerId),
391
+ });
379
392
  const providerConfig = cfg.models?.providers?.[providerId];
380
- const result = await provider.describeVideo({
381
- buffer: media.buffer,
382
- fileName: media.fileName,
383
- mime: media.mime,
384
- apiKey,
385
- baseUrl: providerConfig?.baseUrl,
386
- headers: providerConfig?.headers,
387
- model: entry.model,
388
- prompt,
389
- timeoutMs,
393
+ const result = await executeWithApiKeyRotation({
394
+ provider: providerId,
395
+ apiKeys,
396
+ execute: (apiKey) => describeVideo({
397
+ buffer: media.buffer,
398
+ fileName: media.fileName,
399
+ mime: media.mime,
400
+ apiKey,
401
+ baseUrl: providerConfig?.baseUrl,
402
+ headers: providerConfig?.headers,
403
+ model: entry.model,
404
+ prompt,
405
+ timeoutMs,
406
+ }),
390
407
  });
391
408
  return {
392
409
  kind: "video.description",
@@ -414,7 +431,7 @@ export async function runCliEntry(params) {
414
431
  maxBytes,
415
432
  timeoutMs,
416
433
  });
417
- const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-media-cli-"));
434
+ const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-media-cli-"));
418
435
  const mediaPath = pathResult.path;
419
436
  const outputBase = path.join(outputDir, path.parse(mediaPath).name);
420
437
  const templCtx = {