@poolzin/pool-bot 2026.2.17 → 2026.2.19

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 (488) hide show
  1. package/CHANGELOG.md +25 -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/device-pair/poolbot.plugin.json +20 -0
  381. package/extensions/discord/src/channel.js +366 -0
  382. package/extensions/discord/src/runtime.js +10 -0
  383. package/extensions/feishu/index.ts +63 -0
  384. package/extensions/feishu/package.json +37 -0
  385. package/extensions/feishu/poolbot.plugin.json +10 -0
  386. package/extensions/feishu/skills/feishu-doc/SKILL.md +105 -0
  387. package/extensions/feishu/skills/feishu-doc/references/block-types.md +103 -0
  388. package/extensions/feishu/skills/feishu-drive/SKILL.md +97 -0
  389. package/extensions/feishu/skills/feishu-perm/SKILL.md +119 -0
  390. package/extensions/feishu/skills/feishu-wiki/SKILL.md +111 -0
  391. package/extensions/feishu/src/accounts.ts +114 -0
  392. package/extensions/feishu/src/bitable.ts +739 -0
  393. package/extensions/feishu/src/bot.ts +965 -0
  394. package/extensions/feishu/src/channel.ts +351 -0
  395. package/extensions/feishu/src/client.ts +118 -0
  396. package/extensions/feishu/src/config-schema.ts +206 -0
  397. package/extensions/feishu/src/dedup.ts +33 -0
  398. package/extensions/feishu/src/directory.ts +177 -0
  399. package/extensions/feishu/src/doc-schema.ts +47 -0
  400. package/extensions/feishu/src/docx.ts +536 -0
  401. package/extensions/feishu/src/drive-schema.ts +46 -0
  402. package/extensions/feishu/src/drive.ts +227 -0
  403. package/extensions/feishu/src/dynamic-agent.ts +131 -0
  404. package/extensions/feishu/src/media.ts +449 -0
  405. package/extensions/feishu/src/mention.ts +126 -0
  406. package/extensions/feishu/src/monitor.ts +330 -0
  407. package/extensions/feishu/src/onboarding.ts +359 -0
  408. package/extensions/feishu/src/outbound.ts +55 -0
  409. package/extensions/feishu/src/perm-schema.ts +52 -0
  410. package/extensions/feishu/src/perm.ts +173 -0
  411. package/extensions/feishu/src/policy.ts +84 -0
  412. package/extensions/feishu/src/probe.ts +44 -0
  413. package/extensions/feishu/src/reactions.ts +160 -0
  414. package/extensions/feishu/src/reply-dispatcher.ts +239 -0
  415. package/extensions/feishu/src/runtime.ts +14 -0
  416. package/extensions/feishu/src/send-result.ts +29 -0
  417. package/extensions/feishu/src/send.ts +335 -0
  418. package/extensions/feishu/src/streaming-card.ts +223 -0
  419. package/extensions/feishu/src/targets.ts +78 -0
  420. package/extensions/feishu/src/tools-config.ts +21 -0
  421. package/extensions/feishu/src/types.ts +81 -0
  422. package/extensions/feishu/src/typing.ts +80 -0
  423. package/extensions/feishu/src/wiki-schema.ts +55 -0
  424. package/extensions/feishu/src/wiki.ts +232 -0
  425. package/extensions/imessage/src/channel.js +253 -0
  426. package/extensions/imessage/src/runtime.js +10 -0
  427. package/extensions/irc/index.ts +17 -0
  428. package/extensions/irc/package.json +14 -0
  429. package/extensions/irc/poolbot.plugin.json +9 -0
  430. package/extensions/irc/src/accounts.ts +268 -0
  431. package/extensions/irc/src/channel.ts +367 -0
  432. package/extensions/irc/src/client.ts +439 -0
  433. package/extensions/irc/src/config-schema.ts +97 -0
  434. package/extensions/irc/src/connect-options.ts +30 -0
  435. package/extensions/irc/src/control-chars.ts +22 -0
  436. package/extensions/irc/src/inbound.ts +334 -0
  437. package/extensions/irc/src/monitor.ts +147 -0
  438. package/extensions/irc/src/normalize.ts +117 -0
  439. package/extensions/irc/src/onboarding.ts +479 -0
  440. package/extensions/irc/src/policy.ts +157 -0
  441. package/extensions/irc/src/probe.ts +53 -0
  442. package/extensions/irc/src/protocol.ts +169 -0
  443. package/extensions/irc/src/runtime.ts +14 -0
  444. package/extensions/irc/src/send.ts +88 -0
  445. package/extensions/irc/src/types.ts +93 -0
  446. package/extensions/matrix/src/matrix/client-bootstrap.ts +39 -0
  447. package/extensions/mattermost/src/mattermost/monitor-onchar.ts +25 -0
  448. package/extensions/mattermost/src/mattermost/monitor-websocket.ts +221 -0
  449. package/extensions/mattermost/src/mattermost/reactions.ts +130 -0
  450. package/extensions/mattermost/src/mattermost/reconnect.ts +103 -0
  451. package/extensions/minimax-portal-auth/README.md +33 -0
  452. package/extensions/minimax-portal-auth/index.ts +161 -0
  453. package/extensions/minimax-portal-auth/oauth.ts +247 -0
  454. package/extensions/minimax-portal-auth/package.json +15 -0
  455. package/extensions/minimax-portal-auth/poolbot.plugin.json +9 -0
  456. package/extensions/msteams/src/file-lock.ts +1 -0
  457. package/extensions/msteams/src/graph.ts +92 -0
  458. package/extensions/msteams/src/mentions.ts +114 -0
  459. package/extensions/msteams/src/test-runtime.ts +16 -0
  460. package/extensions/openai-codex-auth/README.md +82 -0
  461. package/extensions/openai-codex-auth/index.ts +177 -0
  462. package/extensions/openai-codex-auth/package.json +15 -0
  463. package/extensions/openai-codex-auth/poolbot.plugin.json +9 -0
  464. package/extensions/phone-control/index.ts +421 -0
  465. package/extensions/phone-control/poolbot.plugin.json +10 -0
  466. package/extensions/shared/resolve-target-test-helpers.ts +66 -0
  467. package/extensions/signal/src/channel.js +273 -0
  468. package/extensions/signal/src/runtime.js +10 -0
  469. package/extensions/slack/src/channel.js +489 -0
  470. package/extensions/slack/src/runtime.js +10 -0
  471. package/extensions/talk-voice/index.ts +150 -0
  472. package/extensions/talk-voice/poolbot.plugin.json +10 -0
  473. package/extensions/telegram/src/channel.js +424 -0
  474. package/extensions/telegram/src/runtime.js +10 -0
  475. package/extensions/thread-ownership/index.ts +133 -0
  476. package/extensions/thread-ownership/poolbot.plugin.json +28 -0
  477. package/extensions/tlon/src/account-fields.ts +25 -0
  478. package/extensions/tlon/src/urbit/base-url.ts +57 -0
  479. package/extensions/tlon/src/urbit/channel-client.ts +157 -0
  480. package/extensions/tlon/src/urbit/channel-ops.ts +164 -0
  481. package/extensions/tlon/src/urbit/context.ts +47 -0
  482. package/extensions/tlon/src/urbit/errors.ts +51 -0
  483. package/extensions/tlon/src/urbit/fetch.ts +39 -0
  484. package/extensions/twitch/src/test-fixtures.ts +30 -0
  485. package/extensions/voice-call/src/allowlist.ts +19 -0
  486. package/extensions/whatsapp/src/channel.js +429 -0
  487. package/extensions/whatsapp/src/runtime.js +10 -0
  488. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ export async function resolveCurrentDirectiveLevels(params) {
2
+ const resolvedDefaultThinkLevel = params.sessionEntry?.thinkingLevel ??
3
+ params.agentCfg?.thinkingDefault ??
4
+ (await params.resolveDefaultThinkingLevel());
5
+ const currentThinkLevel = resolvedDefaultThinkLevel;
6
+ const currentVerboseLevel = params.sessionEntry?.verboseLevel ??
7
+ params.agentCfg?.verboseDefault;
8
+ const currentReasoningLevel = params.sessionEntry?.reasoningLevel ?? "off";
9
+ const currentElevatedLevel = params.sessionEntry?.elevatedLevel ??
10
+ params.agentCfg?.elevatedDefault;
11
+ return {
12
+ currentThinkLevel,
13
+ currentVerboseLevel,
14
+ currentReasoningLevel,
15
+ currentElevatedLevel,
16
+ };
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ export function skipDirectiveArgPrefix(raw) {
2
+ let i = 0;
3
+ const len = raw.length;
4
+ while (i < len && /\s/.test(raw[i])) {
5
+ i += 1;
6
+ }
7
+ if (raw[i] === ":") {
8
+ i += 1;
9
+ while (i < len && /\s/.test(raw[i])) {
10
+ i += 1;
11
+ }
12
+ }
13
+ return i;
14
+ }
15
+ export function takeDirectiveToken(raw, startIndex) {
16
+ let i = startIndex;
17
+ const len = raw.length;
18
+ while (i < len && /\s/.test(raw[i])) {
19
+ i += 1;
20
+ }
21
+ if (i >= len) {
22
+ return { token: null, nextIndex: i };
23
+ }
24
+ const start = i;
25
+ while (i < len && !/\s/.test(raw[i])) {
26
+ i += 1;
27
+ }
28
+ if (start === i) {
29
+ return { token: null, nextIndex: i };
30
+ }
31
+ const token = raw.slice(start, i);
32
+ while (i < len && /\s/.test(raw[i])) {
33
+ i += 1;
34
+ }
35
+ return { token, nextIndex: i };
36
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Global registry for tracking active reply dispatchers.
3
+ * Used to ensure gateway restart waits for all replies to complete.
4
+ */
5
+ const activeDispatchers = new Set();
6
+ let nextId = 0;
7
+ /**
8
+ * Register a reply dispatcher for global tracking.
9
+ * Returns an unregister function to call when the dispatcher is no longer needed.
10
+ */
11
+ export function registerDispatcher(dispatcher) {
12
+ const id = `dispatcher-${++nextId}`;
13
+ const tracked = {
14
+ id,
15
+ pending: dispatcher.pending,
16
+ waitForIdle: dispatcher.waitForIdle,
17
+ };
18
+ activeDispatchers.add(tracked);
19
+ const unregister = () => {
20
+ activeDispatchers.delete(tracked);
21
+ };
22
+ return { id, unregister };
23
+ }
24
+ /**
25
+ * Get the total number of pending replies across all dispatchers.
26
+ */
27
+ export function getTotalPendingReplies() {
28
+ let total = 0;
29
+ for (const dispatcher of activeDispatchers) {
30
+ total += dispatcher.pending();
31
+ }
32
+ return total;
33
+ }
34
+ /**
35
+ * Clear all registered dispatchers (for testing).
36
+ * WARNING: Only use this in test cleanup!
37
+ */
38
+ export function clearAllDispatchers() {
39
+ if (!process.env.VITEST && process.env.NODE_ENV !== "test") {
40
+ throw new Error("clearAllDispatchers() is only available in test environments");
41
+ }
42
+ activeDispatchers.clear();
43
+ }
@@ -0,0 +1,20 @@
1
+ import { formatCliCommand } from "../../cli/command-format.js";
2
+ export function formatElevatedUnavailableMessage(params) {
3
+ const lines = [];
4
+ lines.push(`elevated is not available right now (runtime=${params.runtimeSandboxed ? "sandboxed" : "direct"}).`);
5
+ if (params.failures.length > 0) {
6
+ lines.push(`Failing gates: ${params.failures.map((f) => `${f.gate} (${f.key})`).join(", ")}`);
7
+ }
8
+ else {
9
+ lines.push("Failing gates: enabled (tools.elevated.enabled / agents.list[].tools.elevated.enabled), allowFrom (tools.elevated.allowFrom.<provider>).");
10
+ }
11
+ lines.push("Fix-it keys:");
12
+ lines.push("- tools.elevated.enabled");
13
+ lines.push("- tools.elevated.allowFrom.<provider>");
14
+ lines.push("- agents.list[].tools.elevated.enabled");
15
+ lines.push("- agents.list[].tools.elevated.allowFrom.<provider>");
16
+ if (params.sessionKey) {
17
+ lines.push(`See: ${formatCliCommand(`openclaw sandbox explain --session ${params.sessionKey}`)}`);
18
+ }
19
+ return lines.join("\n");
20
+ }
@@ -0,0 +1,92 @@
1
+ import { logVerbose } from "../../globals.js";
2
+ import { SILENT_REPLY_TOKEN } from "../tokens.js";
3
+ import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
4
+ import { parseReplyDirectives } from "./reply-directives.js";
5
+ import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js";
6
+ export function normalizeReplyPayloadDirectives(params) {
7
+ const parseMode = params.parseMode ?? "always";
8
+ const silentToken = params.silentToken ?? SILENT_REPLY_TOKEN;
9
+ const sourceText = params.payload.text ?? "";
10
+ const shouldParse = parseMode === "always" ||
11
+ (parseMode === "auto" &&
12
+ (sourceText.includes("[[") ||
13
+ sourceText.includes("MEDIA:") ||
14
+ sourceText.includes(silentToken)));
15
+ const parsed = shouldParse
16
+ ? parseReplyDirectives(sourceText, {
17
+ currentMessageId: params.currentMessageId,
18
+ silentToken,
19
+ })
20
+ : undefined;
21
+ let text = parsed ? parsed.text || undefined : params.payload.text || undefined;
22
+ if (params.trimLeadingWhitespace && text) {
23
+ text = text.trimStart() || undefined;
24
+ }
25
+ const mediaUrls = params.payload.mediaUrls ?? parsed?.mediaUrls;
26
+ const mediaUrl = params.payload.mediaUrl ?? parsed?.mediaUrl ?? mediaUrls?.[0];
27
+ return {
28
+ payload: {
29
+ ...params.payload,
30
+ text,
31
+ mediaUrls,
32
+ mediaUrl,
33
+ replyToId: params.payload.replyToId ?? parsed?.replyToId,
34
+ replyToTag: params.payload.replyToTag || parsed?.replyToTag,
35
+ replyToCurrent: params.payload.replyToCurrent || parsed?.replyToCurrent,
36
+ audioAsVoice: Boolean(params.payload.audioAsVoice || parsed?.audioAsVoice),
37
+ },
38
+ isSilent: parsed?.isSilent ?? false,
39
+ };
40
+ }
41
+ const hasRenderableMedia = (payload) => Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
42
+ export function createBlockReplyDeliveryHandler(params) {
43
+ return async (payload) => {
44
+ const { text, skip } = params.normalizeStreamingText(payload);
45
+ if (skip && !hasRenderableMedia(payload)) {
46
+ return;
47
+ }
48
+ const taggedPayload = applyReplyTagsToPayload({
49
+ ...payload,
50
+ text,
51
+ mediaUrl: payload.mediaUrl ?? payload.mediaUrls?.[0],
52
+ replyToId: payload.replyToId ??
53
+ (payload.replyToCurrent === false ? undefined : params.currentMessageId),
54
+ }, params.currentMessageId);
55
+ // Let through payloads with audioAsVoice flag even if empty (need to track it).
56
+ if (!isRenderablePayload(taggedPayload) && !payload.audioAsVoice) {
57
+ return;
58
+ }
59
+ const normalized = normalizeReplyPayloadDirectives({
60
+ payload: taggedPayload,
61
+ currentMessageId: params.currentMessageId,
62
+ silentToken: SILENT_REPLY_TOKEN,
63
+ trimLeadingWhitespace: true,
64
+ parseMode: "auto",
65
+ });
66
+ const blockPayload = params.applyReplyToMode(normalized.payload);
67
+ const blockHasMedia = hasRenderableMedia(blockPayload);
68
+ // Skip empty payloads unless they have audioAsVoice flag (need to track it).
69
+ if (!blockPayload.text && !blockHasMedia && !blockPayload.audioAsVoice) {
70
+ return;
71
+ }
72
+ if (normalized.isSilent && !blockHasMedia) {
73
+ return;
74
+ }
75
+ if (blockPayload.text) {
76
+ void params.typingSignals.signalTextDelta(blockPayload.text).catch((err) => {
77
+ logVerbose(`block reply typing signal failed: ${String(err)}`);
78
+ });
79
+ }
80
+ // Use pipeline if available (block streaming enabled), otherwise send directly.
81
+ if (params.blockStreamingEnabled && params.blockReplyPipeline) {
82
+ params.blockReplyPipeline.enqueue(blockPayload);
83
+ }
84
+ else if (params.blockStreamingEnabled) {
85
+ // Send directly when flushing before tool execution (no pipeline but streaming enabled).
86
+ // Track sent key to avoid duplicate in final payloads.
87
+ params.directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
88
+ await params.onBlockReply(blockPayload);
89
+ }
90
+ // When streaming is disabled entirely, blocks are accumulated in final text instead.
91
+ };
92
+ }
@@ -0,0 +1 @@
1
+ export const BARE_SESSION_RESET_PROMPT = "A new session was started via /new or /reset. Greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
@@ -0,0 +1,33 @@
1
+ import { deriveSessionTotalTokens } from "../../agents/usage.js";
2
+ import { incrementCompactionCount } from "./session-updates.js";
3
+ import { persistSessionUsageUpdate } from "./session-usage.js";
4
+ export async function persistRunSessionUsage(params) {
5
+ await persistSessionUsageUpdate({
6
+ storePath: params.storePath,
7
+ sessionKey: params.sessionKey,
8
+ usage: params.usage,
9
+ lastCallUsage: params.lastCallUsage,
10
+ promptTokens: params.promptTokens,
11
+ modelUsed: params.modelUsed,
12
+ providerUsed: params.providerUsed,
13
+ contextTokensUsed: params.contextTokensUsed,
14
+ systemPromptReport: params.systemPromptReport,
15
+ cliSessionId: params.cliSessionId,
16
+ logLabel: params.logLabel,
17
+ });
18
+ }
19
+ export async function incrementRunCompactionCount(params) {
20
+ const tokensAfterCompaction = params.lastCallUsage
21
+ ? deriveSessionTotalTokens({
22
+ usage: params.lastCallUsage,
23
+ contextTokens: params.contextTokensUsed,
24
+ })
25
+ : undefined;
26
+ return incrementCompactionCount({
27
+ sessionEntry: params.sessionEntry,
28
+ sessionStore: params.sessionStore,
29
+ sessionKey: params.sessionKey,
30
+ storePath: params.storePath,
31
+ tokensAfter: tokensAfterCompaction,
32
+ });
33
+ }
@@ -0,0 +1,115 @@
1
+ import path from "node:path";
2
+ import { afterEach, beforeEach, expect, vi } from "vitest";
3
+ import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
4
+ import { loadModelCatalog } from "../agents/model-catalog.js";
5
+ import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
6
+ import { loadSessionStore } from "../config/sessions.js";
7
+ export { loadModelCatalog } from "../agents/model-catalog.js";
8
+ export { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
9
+ export const MAIN_SESSION_KEY = "agent:main:main";
10
+ export const DEFAULT_TEST_MODEL_CATALOG = [
11
+ { id: "claude-opus-4-5", name: "Opus 4.5", provider: "anthropic" },
12
+ { id: "claude-sonnet-4-1", name: "Sonnet 4.1", provider: "anthropic" },
13
+ { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" },
14
+ ];
15
+ export function replyText(res) {
16
+ if (Array.isArray(res)) {
17
+ return typeof res[0]?.text === "string" ? res[0]?.text : undefined;
18
+ }
19
+ return typeof res?.text === "string" ? res.text : undefined;
20
+ }
21
+ export function replyTexts(res) {
22
+ const payloads = Array.isArray(res) ? res : [res];
23
+ return payloads
24
+ .map((entry) => (typeof entry?.text === "string" ? entry.text : undefined))
25
+ .filter((value) => Boolean(value));
26
+ }
27
+ export async function withTempHome(fn) {
28
+ return withTempHomeBase(async (home) => {
29
+ return await fn(home);
30
+ }, {
31
+ env: {
32
+ POOLBOT_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"),
33
+ PI_CODING_AGENT_DIR: (home) => path.join(home, ".openclaw", "agent"),
34
+ },
35
+ prefix: "openclaw-reply-",
36
+ });
37
+ }
38
+ export function sessionStorePath(home) {
39
+ return path.join(home, "sessions.json");
40
+ }
41
+ export function makeWhatsAppDirectiveConfig(home, defaults, extra = {}) {
42
+ return {
43
+ agents: {
44
+ defaults: {
45
+ workspace: path.join(home, "poolbot"),
46
+ ...defaults,
47
+ },
48
+ },
49
+ channels: { whatsapp: { allowFrom: ["*"] } },
50
+ session: { store: sessionStorePath(home) },
51
+ ...extra,
52
+ };
53
+ }
54
+ export const AUTHORIZED_WHATSAPP_COMMAND = {
55
+ From: "+1222",
56
+ To: "+1222",
57
+ Provider: "whatsapp",
58
+ SenderE164: "+1222",
59
+ CommandAuthorized: true,
60
+ };
61
+ export function makeElevatedDirectiveConfig(home) {
62
+ return makeWhatsAppDirectiveConfig(home, {
63
+ model: "anthropic/claude-opus-4-5",
64
+ elevatedDefault: "on",
65
+ }, {
66
+ tools: {
67
+ elevated: {
68
+ allowFrom: { whatsapp: ["+1222"] },
69
+ },
70
+ },
71
+ channels: { whatsapp: { allowFrom: ["+1222"] } },
72
+ session: { store: sessionStorePath(home) },
73
+ });
74
+ }
75
+ export function assertModelSelection(storePath, selection = {}) {
76
+ const store = loadSessionStore(storePath);
77
+ const entry = store[MAIN_SESSION_KEY];
78
+ expect(entry).toBeDefined();
79
+ expect(entry?.modelOverride).toBe(selection.model);
80
+ expect(entry?.providerOverride).toBe(selection.provider);
81
+ }
82
+ export function installDirectiveBehaviorE2EHooks() {
83
+ beforeEach(() => {
84
+ vi.mocked(runEmbeddedPiAgent).mockReset();
85
+ vi.mocked(loadModelCatalog).mockResolvedValue(DEFAULT_TEST_MODEL_CATALOG);
86
+ });
87
+ afterEach(() => {
88
+ vi.restoreAllMocks();
89
+ });
90
+ }
91
+ export function makeRestrictedElevatedDisabledConfig(home) {
92
+ return {
93
+ agents: {
94
+ defaults: {
95
+ model: "anthropic/claude-opus-4-5",
96
+ workspace: path.join(home, "poolbot"),
97
+ },
98
+ list: [
99
+ {
100
+ id: "restricted",
101
+ tools: {
102
+ elevated: { enabled: false },
103
+ },
104
+ },
105
+ ],
106
+ },
107
+ tools: {
108
+ elevated: {
109
+ allowFrom: { whatsapp: ["+1222"] },
110
+ },
111
+ },
112
+ channels: { whatsapp: { allowFrom: ["+1222"] } },
113
+ session: { store: path.join(home, "sessions.json") },
114
+ };
115
+ }
@@ -0,0 +1,12 @@
1
+ import { vi } from "vitest";
2
+ vi.mock("../agents/pi-embedded.js", () => ({
3
+ abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
4
+ runEmbeddedPiAgent: vi.fn(),
5
+ queueEmbeddedPiMessage: vi.fn().mockReturnValue(false),
6
+ resolveEmbeddedSessionLane: (key) => `session:${key.trim() || "main"}`,
7
+ isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
8
+ isEmbeddedPiRunStreaming: vi.fn().mockReturnValue(false),
9
+ }));
10
+ vi.mock("../agents/model-catalog.js", () => ({
11
+ loadModelCatalog: vi.fn(),
12
+ }));
@@ -0,0 +1,26 @@
1
+ // In-process registry for loopback-only bridge servers that require auth, but
2
+ // are addressed via dynamic ephemeral ports (e.g. sandbox browser bridge).
3
+ const authByPort = new Map();
4
+ export function setBridgeAuthForPort(port, auth) {
5
+ if (!Number.isFinite(port) || port <= 0) {
6
+ return;
7
+ }
8
+ const token = typeof auth.token === "string" ? auth.token.trim() : "";
9
+ const password = typeof auth.password === "string" ? auth.password.trim() : "";
10
+ authByPort.set(port, {
11
+ token: token || undefined,
12
+ password: password || undefined,
13
+ });
14
+ }
15
+ export function getBridgeAuthForPort(port) {
16
+ if (!Number.isFinite(port) || port <= 0) {
17
+ return undefined;
18
+ }
19
+ return authByPort.get(port);
20
+ }
21
+ export function deleteBridgeAuthForPort(port) {
22
+ if (!Number.isFinite(port) || port <= 0) {
23
+ return;
24
+ }
25
+ authByPort.delete(port);
26
+ }
@@ -0,0 +1,10 @@
1
+ export function buildProfileQuery(profile) {
2
+ return profile ? `?profile=${encodeURIComponent(profile)}` : "";
3
+ }
4
+ export function withBaseUrl(baseUrl, path) {
5
+ const trimmed = baseUrl?.trim();
6
+ if (!trimmed) {
7
+ return path;
8
+ }
9
+ return `${trimmed.replace(/\/$/, "")}${path}`;
10
+ }
@@ -0,0 +1,73 @@
1
+ import crypto from "node:crypto";
2
+ import { loadConfig, writeConfigFile } from "../config/config.js";
3
+ import { resolveGatewayAuth } from "../gateway/auth.js";
4
+ export function resolveBrowserControlAuth(cfg, env = process.env) {
5
+ const auth = resolveGatewayAuth({
6
+ authConfig: cfg?.gateway?.auth,
7
+ env,
8
+ tailscaleMode: cfg?.gateway?.tailscale?.mode,
9
+ });
10
+ const token = typeof auth.token === "string" ? auth.token.trim() : "";
11
+ const password = typeof auth.password === "string" ? auth.password.trim() : "";
12
+ return {
13
+ token: token || undefined,
14
+ password: password || undefined,
15
+ };
16
+ }
17
+ function shouldAutoGenerateBrowserAuth(env) {
18
+ const nodeEnv = (env.NODE_ENV ?? "").trim().toLowerCase();
19
+ if (nodeEnv === "test") {
20
+ return false;
21
+ }
22
+ const vitest = (env.VITEST ?? "").trim().toLowerCase();
23
+ if (vitest && vitest !== "0" && vitest !== "false" && vitest !== "off") {
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ export async function ensureBrowserControlAuth(params) {
29
+ const env = params.env ?? process.env;
30
+ const auth = resolveBrowserControlAuth(params.cfg, env);
31
+ if (auth.token || auth.password) {
32
+ return { auth };
33
+ }
34
+ if (!shouldAutoGenerateBrowserAuth(env)) {
35
+ return { auth };
36
+ }
37
+ // Respect explicit password mode even if currently unset.
38
+ if (params.cfg.gateway?.auth?.mode === "password") {
39
+ return { auth };
40
+ }
41
+ if (params.cfg.gateway?.auth?.mode === "trusted-proxy") {
42
+ return { auth };
43
+ }
44
+ // Re-read latest config to avoid racing with concurrent config writers.
45
+ const latestCfg = loadConfig();
46
+ const latestAuth = resolveBrowserControlAuth(latestCfg, env);
47
+ if (latestAuth.token || latestAuth.password) {
48
+ return { auth: latestAuth };
49
+ }
50
+ if (latestCfg.gateway?.auth?.mode === "password") {
51
+ return { auth: latestAuth };
52
+ }
53
+ if (latestCfg.gateway?.auth?.mode === "trusted-proxy") {
54
+ return { auth: latestAuth };
55
+ }
56
+ const generatedToken = crypto.randomBytes(24).toString("hex");
57
+ const nextCfg = {
58
+ ...latestCfg,
59
+ gateway: {
60
+ ...latestCfg.gateway,
61
+ auth: {
62
+ ...latestCfg.gateway?.auth,
63
+ mode: "token",
64
+ token: generatedToken,
65
+ },
66
+ },
67
+ };
68
+ await writeConfigFile(nextCfg);
69
+ return {
70
+ auth: { token: generatedToken },
71
+ generatedToken,
72
+ };
73
+ }
@@ -0,0 +1,64 @@
1
+ import { isLoopbackHost } from "../gateway/net.js";
2
+ function firstHeader(value) {
3
+ return Array.isArray(value) ? (value[0] ?? "") : (value ?? "");
4
+ }
5
+ function isMutatingMethod(method) {
6
+ const m = (method || "").trim().toUpperCase();
7
+ return m === "POST" || m === "PUT" || m === "PATCH" || m === "DELETE";
8
+ }
9
+ function isLoopbackUrl(value) {
10
+ const v = value.trim();
11
+ if (!v || v === "null") {
12
+ return false;
13
+ }
14
+ try {
15
+ const parsed = new URL(v);
16
+ return isLoopbackHost(parsed.hostname);
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ export function shouldRejectBrowserMutation(params) {
23
+ if (!isMutatingMethod(params.method)) {
24
+ return false;
25
+ }
26
+ // Strong signal when present: browser says this is cross-site.
27
+ // Avoid being overly clever with "same-site" since localhost vs 127.0.0.1 may differ.
28
+ const secFetchSite = (params.secFetchSite ?? "").trim().toLowerCase();
29
+ if (secFetchSite === "cross-site") {
30
+ return true;
31
+ }
32
+ const origin = (params.origin ?? "").trim();
33
+ if (origin) {
34
+ return !isLoopbackUrl(origin);
35
+ }
36
+ const referer = (params.referer ?? "").trim();
37
+ if (referer) {
38
+ return !isLoopbackUrl(referer);
39
+ }
40
+ // Non-browser clients (curl/undici/Node) typically send no Origin/Referer.
41
+ return false;
42
+ }
43
+ export function browserMutationGuardMiddleware() {
44
+ return (req, res, next) => {
45
+ // OPTIONS is used for CORS preflight. Even if cross-origin, the preflight isn't mutating.
46
+ const method = (req.method || "").trim().toUpperCase();
47
+ if (method === "OPTIONS") {
48
+ return next();
49
+ }
50
+ const origin = firstHeader(req.headers.origin);
51
+ const referer = firstHeader(req.headers.referer);
52
+ const secFetchSite = firstHeader(req.headers["sec-fetch-site"]);
53
+ if (shouldRejectBrowserMutation({
54
+ method,
55
+ origin,
56
+ referer,
57
+ secFetchSite,
58
+ })) {
59
+ res.status(403).send("Forbidden");
60
+ return;
61
+ }
62
+ next();
63
+ };
64
+ }
@@ -0,0 +1,52 @@
1
+ import { safeEqualSecret } from "../security/secret-equal.js";
2
+ function firstHeaderValue(value) {
3
+ return Array.isArray(value) ? (value[0] ?? "") : (value ?? "");
4
+ }
5
+ function parseBearerToken(authorization) {
6
+ if (!authorization || !authorization.toLowerCase().startsWith("bearer ")) {
7
+ return undefined;
8
+ }
9
+ const token = authorization.slice(7).trim();
10
+ return token || undefined;
11
+ }
12
+ function parseBasicPassword(authorization) {
13
+ if (!authorization || !authorization.toLowerCase().startsWith("basic ")) {
14
+ return undefined;
15
+ }
16
+ const encoded = authorization.slice(6).trim();
17
+ if (!encoded) {
18
+ return undefined;
19
+ }
20
+ try {
21
+ const decoded = Buffer.from(encoded, "base64").toString("utf8");
22
+ const sep = decoded.indexOf(":");
23
+ if (sep < 0) {
24
+ return undefined;
25
+ }
26
+ const password = decoded.slice(sep + 1).trim();
27
+ return password || undefined;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ export function isAuthorizedBrowserRequest(req, auth) {
34
+ const authorization = firstHeaderValue(req.headers.authorization).trim();
35
+ if (auth.token) {
36
+ const bearer = parseBearerToken(authorization);
37
+ if (bearer && safeEqualSecret(bearer, auth.token)) {
38
+ return true;
39
+ }
40
+ }
41
+ if (auth.password) {
42
+ const passwordHeader = firstHeaderValue(req.headers["x-openclaw-password"]).trim();
43
+ if (passwordHeader && safeEqualSecret(passwordHeader, auth.password)) {
44
+ return true;
45
+ }
46
+ const basicPassword = parseBasicPassword(authorization);
47
+ if (basicPassword && safeEqualSecret(basicPassword, auth.password)) {
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ }
@@ -0,0 +1,37 @@
1
+ import path from "node:path";
2
+ import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-poolbot-dir.js";
3
+ export const DEFAULT_BROWSER_TMP_DIR = resolvePreferredOpenClawTmpDir();
4
+ export const DEFAULT_TRACE_DIR = DEFAULT_BROWSER_TMP_DIR;
5
+ export const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "downloads");
6
+ export const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads");
7
+ export function resolvePathWithinRoot(params) {
8
+ const root = path.resolve(params.rootDir);
9
+ const raw = params.requestedPath.trim();
10
+ if (!raw) {
11
+ if (!params.defaultFileName) {
12
+ return { ok: false, error: "path is required" };
13
+ }
14
+ return { ok: true, path: path.join(root, params.defaultFileName) };
15
+ }
16
+ const resolved = path.resolve(root, raw);
17
+ const rel = path.relative(root, resolved);
18
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
19
+ return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
20
+ }
21
+ return { ok: true, path: resolved };
22
+ }
23
+ export function resolvePathsWithinRoot(params) {
24
+ const resolvedPaths = [];
25
+ for (const raw of params.requestedPaths) {
26
+ const pathResult = resolvePathWithinRoot({
27
+ rootDir: params.rootDir,
28
+ requestedPath: raw,
29
+ scopeLabel: params.scopeLabel,
30
+ });
31
+ if (!pathResult.ok) {
32
+ return { ok: false, error: pathResult.error };
33
+ }
34
+ resolvedPaths.push(pathResult.path);
35
+ }
36
+ return { ok: true, paths: resolvedPaths };
37
+ }