@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
@@ -1,11 +1,35 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { loadConfig, resolveConfigPath, resolveGatewayPort, resolveStateDir, } from "../config/config.js";
3
- import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
4
3
  import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
5
- import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../utils/message-channel.js";
4
+ import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
6
5
  import { loadGatewayTlsRuntime } from "../infra/tls/gateway.js";
6
+ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../utils/message-channel.js";
7
7
  import { GatewayClient } from "./client.js";
8
+ import { pickPrimaryLanIPv4 } from "./net.js";
8
9
  import { PROTOCOL_VERSION } from "./protocol/index.js";
10
+ export function resolveExplicitGatewayAuth(opts) {
11
+ const token = typeof opts?.token === "string" && opts.token.trim().length > 0 ? opts.token.trim() : undefined;
12
+ const password = typeof opts?.password === "string" && opts.password.trim().length > 0
13
+ ? opts.password.trim()
14
+ : undefined;
15
+ return { token, password };
16
+ }
17
+ export function ensureExplicitGatewayAuth(params) {
18
+ if (!params.urlOverride) {
19
+ return;
20
+ }
21
+ if (params.auth.token || params.auth.password) {
22
+ return;
23
+ }
24
+ const message = [
25
+ "gateway url override requires explicit credentials",
26
+ params.errorHint,
27
+ params.configPath ? `Config: ${params.configPath}` : undefined,
28
+ ]
29
+ .filter(Boolean)
30
+ .join("\n");
31
+ throw new Error(message);
32
+ }
9
33
  export function buildGatewayConnectionDetails(options = {}) {
10
34
  const config = options.config ?? loadConfig();
11
35
  const configPath = options.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env));
@@ -16,10 +40,14 @@ export function buildGatewayConnectionDetails(options = {}) {
16
40
  const tailnetIPv4 = pickPrimaryTailnetIPv4();
17
41
  const bindMode = config.gateway?.bind ?? "loopback";
18
42
  const preferTailnet = bindMode === "tailnet" && !!tailnetIPv4;
43
+ const preferLan = bindMode === "lan";
44
+ const lanIPv4 = preferLan ? pickPrimaryLanIPv4() : undefined;
19
45
  const scheme = tlsEnabled ? "wss" : "ws";
20
46
  const localUrl = preferTailnet && tailnetIPv4
21
47
  ? `${scheme}://${tailnetIPv4}:${localPort}`
22
- : `${scheme}://127.0.0.1:${localPort}`;
48
+ : preferLan && lanIPv4
49
+ ? `${scheme}://${lanIPv4}:${localPort}`
50
+ : `${scheme}://127.0.0.1:${localPort}`;
23
51
  const urlOverride = typeof options.url === "string" && options.url.trim().length > 0
24
52
  ? options.url.trim()
25
53
  : undefined;
@@ -34,7 +62,9 @@ export function buildGatewayConnectionDetails(options = {}) {
34
62
  ? "missing gateway.remote.url (fallback local)"
35
63
  : preferTailnet && tailnetIPv4
36
64
  ? `local tailnet ${tailnetIPv4}`
37
- : "local loopback";
65
+ : preferLan && lanIPv4
66
+ ? `local lan ${lanIPv4}`
67
+ : "local loopback";
38
68
  const remoteFallbackNote = remoteMisconfigured
39
69
  ? "Warn: gateway.mode=remote but gateway.remote.url is missing; set gateway.remote.url or switch gateway.mode=local."
40
70
  : undefined;
@@ -57,11 +87,19 @@ export function buildGatewayConnectionDetails(options = {}) {
57
87
  };
58
88
  }
59
89
  export async function callGateway(opts) {
60
- const timeoutMs = opts.timeoutMs ?? 10_000;
90
+ const timeoutMs = typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : 10_000;
91
+ const safeTimerTimeoutMs = Math.max(1, Math.min(Math.floor(timeoutMs), 2_147_483_647));
61
92
  const config = opts.config ?? loadConfig();
62
93
  const isRemoteMode = config.gateway?.mode === "remote";
63
94
  const remote = isRemoteMode ? config.gateway?.remote : undefined;
64
95
  const urlOverride = typeof opts.url === "string" && opts.url.trim().length > 0 ? opts.url.trim() : undefined;
96
+ const explicitAuth = resolveExplicitGatewayAuth({ token: opts.token, password: opts.password });
97
+ ensureExplicitGatewayAuth({
98
+ urlOverride,
99
+ auth: explicitAuth,
100
+ errorHint: "Fix: pass --token or --password (or gatewayToken in tools).",
101
+ configPath: opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)),
102
+ });
65
103
  const remoteUrl = typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined;
66
104
  if (isRemoteMode && !urlOverride && !remoteUrl) {
67
105
  const configPath = opts.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env));
@@ -88,30 +126,30 @@ export async function callGateway(opts) {
88
126
  const tlsFingerprint = overrideTlsFingerprint ||
89
127
  remoteTlsFingerprint ||
90
128
  (tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 : undefined);
91
- const token = (typeof opts.token === "string" && opts.token.trim().length > 0
92
- ? opts.token.trim()
93
- : undefined) ||
94
- (isRemoteMode
95
- ? typeof remote?.token === "string" && remote.token.trim().length > 0
96
- ? remote.token.trim()
97
- : undefined
98
- : process.env.POOLBOT_GATEWAY_TOKEN?.trim() ||
99
- process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
100
- (typeof authToken === "string" && authToken.trim().length > 0
101
- ? authToken.trim()
102
- : undefined));
103
- const password = (typeof opts.password === "string" && opts.password.trim().length > 0
104
- ? opts.password.trim()
105
- : undefined) ||
106
- process.env.POOLBOT_GATEWAY_PASSWORD?.trim() ||
107
- process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
108
- (isRemoteMode
109
- ? typeof remote?.password === "string" && remote.password.trim().length > 0
110
- ? remote.password.trim()
111
- : undefined
112
- : typeof authPassword === "string" && authPassword.trim().length > 0
113
- ? authPassword.trim()
114
- : undefined);
129
+ const token = explicitAuth.token ||
130
+ (!urlOverride
131
+ ? isRemoteMode
132
+ ? typeof remote?.token === "string" && remote.token.trim().length > 0
133
+ ? remote.token.trim()
134
+ : undefined
135
+ : process.env.POOLBOT_GATEWAY_TOKEN?.trim() ||
136
+ process.env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
137
+ (typeof authToken === "string" && authToken.trim().length > 0
138
+ ? authToken.trim()
139
+ : undefined)
140
+ : undefined);
141
+ const password = explicitAuth.password ||
142
+ (!urlOverride
143
+ ? process.env.POOLBOT_GATEWAY_PASSWORD?.trim() ||
144
+ process.env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
145
+ (isRemoteMode
146
+ ? typeof remote?.password === "string" && remote.password.trim().length > 0
147
+ ? remote.password.trim()
148
+ : undefined
149
+ : typeof authPassword === "string" && authPassword.trim().length > 0
150
+ ? authPassword.trim()
151
+ : undefined)
152
+ : undefined);
115
153
  const formatCloseError = (code, reason) => {
116
154
  const reasonText = reason?.trim() || "no close reason";
117
155
  const hint = code === 1006 ? "abnormal closure (no close frame)" : code === 1000 ? "normal closure" : "";
@@ -123,14 +161,17 @@ export async function callGateway(opts) {
123
161
  let settled = false;
124
162
  let ignoreClose = false;
125
163
  const stop = (err, value) => {
126
- if (settled)
164
+ if (settled) {
127
165
  return;
166
+ }
128
167
  settled = true;
129
168
  clearTimeout(timer);
130
- if (err)
169
+ if (err) {
131
170
  reject(err);
132
- else
171
+ }
172
+ else {
133
173
  resolve(value);
174
+ }
134
175
  };
135
176
  const client = new GatewayClient({
136
177
  url,
@@ -164,8 +205,9 @@ export async function callGateway(opts) {
164
205
  }
165
206
  },
166
207
  onClose: (code, reason) => {
167
- if (settled || ignoreClose)
208
+ if (settled || ignoreClose) {
168
209
  return;
210
+ }
169
211
  ignoreClose = true;
170
212
  client.stop();
171
213
  stop(new Error(formatCloseError(code, reason)));
@@ -175,7 +217,7 @@ export async function callGateway(opts) {
175
217
  ignoreClose = true;
176
218
  client.stop();
177
219
  stop(new Error(formatTimeoutError()));
178
- }, timeoutMs);
220
+ }, safeTimerTimeoutMs);
179
221
  client.start();
180
222
  });
181
223
  }
@@ -26,71 +26,78 @@ export function startChannelHealthMonitor(deps) {
26
26
  const restartRecords = new Map();
27
27
  const startedAt = Date.now();
28
28
  let stopped = false;
29
+ let checkInFlight = false;
29
30
  let timer = null;
30
31
  const rKey = (channelId, accountId) => `${channelId}:${accountId}`;
31
32
  function pruneOldRestarts(record, now) {
32
33
  record.restartsThisHour = record.restartsThisHour.filter((r) => now - r.at < ONE_HOUR_MS);
33
34
  }
34
35
  async function runCheck() {
35
- if (stopped) {
36
+ if (stopped || checkInFlight) {
36
37
  return;
37
38
  }
38
- const now = Date.now();
39
- if (now - startedAt < startupGraceMs) {
40
- return;
41
- }
42
- const snapshot = channelManager.getRuntimeSnapshot();
43
- for (const [channelId, accounts] of Object.entries(snapshot.channelAccounts)) {
44
- if (!accounts) {
45
- continue;
39
+ checkInFlight = true;
40
+ try {
41
+ const now = Date.now();
42
+ if (now - startedAt < startupGraceMs) {
43
+ return;
46
44
  }
47
- for (const [accountId, status] of Object.entries(accounts)) {
48
- if (!status) {
49
- continue;
50
- }
51
- if (!isManagedAccount(status)) {
52
- continue;
53
- }
54
- if (channelManager.isManuallyStopped(channelId, accountId)) {
45
+ const snapshot = channelManager.getRuntimeSnapshot();
46
+ for (const [channelId, accounts] of Object.entries(snapshot.channelAccounts)) {
47
+ if (!accounts) {
55
48
  continue;
56
49
  }
57
- if (isChannelHealthy(status)) {
58
- continue;
59
- }
60
- const key = rKey(channelId, accountId);
61
- const record = restartRecords.get(key) ?? {
62
- lastRestartAt: 0,
63
- restartsThisHour: [],
64
- };
65
- if (now - record.lastRestartAt <= cooldownMs) {
66
- continue;
67
- }
68
- pruneOldRestarts(record, now);
69
- if (record.restartsThisHour.length >= maxRestartsPerHour) {
70
- log.warn?.(`[${channelId}:${accountId}] health-monitor: hit ${maxRestartsPerHour} restarts/hour limit, skipping`);
71
- continue;
72
- }
73
- const reason = !status.running
74
- ? status.reconnectAttempts && status.reconnectAttempts >= 10
75
- ? "gave-up"
76
- : "stopped"
77
- : "stuck";
78
- log.info?.(`[${channelId}:${accountId}] health-monitor: restarting (reason: ${reason})`);
79
- try {
80
- if (status.running) {
81
- await channelManager.stopChannel(channelId, accountId);
50
+ for (const [accountId, status] of Object.entries(accounts)) {
51
+ if (!status) {
52
+ continue;
53
+ }
54
+ if (!isManagedAccount(status)) {
55
+ continue;
56
+ }
57
+ if (channelManager.isManuallyStopped(channelId, accountId)) {
58
+ continue;
59
+ }
60
+ if (isChannelHealthy(status)) {
61
+ continue;
62
+ }
63
+ const key = rKey(channelId, accountId);
64
+ const record = restartRecords.get(key) ?? {
65
+ lastRestartAt: 0,
66
+ restartsThisHour: [],
67
+ };
68
+ if (now - record.lastRestartAt <= cooldownMs) {
69
+ continue;
70
+ }
71
+ pruneOldRestarts(record, now);
72
+ if (record.restartsThisHour.length >= maxRestartsPerHour) {
73
+ log.warn?.(`[${channelId}:${accountId}] health-monitor: hit ${maxRestartsPerHour} restarts/hour limit, skipping`);
74
+ continue;
75
+ }
76
+ const reason = !status.running
77
+ ? status.reconnectAttempts && status.reconnectAttempts >= 10
78
+ ? "gave-up"
79
+ : "stopped"
80
+ : "stuck";
81
+ log.info?.(`[${channelId}:${accountId}] health-monitor: restarting (reason: ${reason})`);
82
+ try {
83
+ if (status.running) {
84
+ await channelManager.stopChannel(channelId, accountId);
85
+ }
86
+ channelManager.resetRestartAttempts(channelId, accountId);
87
+ await channelManager.startChannel(channelId, accountId);
88
+ record.lastRestartAt = now;
89
+ record.restartsThisHour.push({ at: now });
90
+ restartRecords.set(key, record);
91
+ }
92
+ catch (err) {
93
+ log.error?.(`[${channelId}:${accountId}] health-monitor: restart failed: ${String(err)}`);
82
94
  }
83
- channelManager.resetRestartAttempts(channelId, accountId);
84
- await channelManager.startChannel(channelId, accountId);
85
- record.lastRestartAt = now;
86
- record.restartsThisHour.push({ at: now });
87
- restartRecords.set(key, record);
88
- }
89
- catch (err) {
90
- log.error?.(`[${channelId}:${accountId}] health-monitor: restart failed: ${String(err)}`);
91
95
  }
92
96
  }
93
97
  }
98
+ finally {
99
+ checkInFlight = false;
100
+ }
94
101
  }
95
102
  function stop() {
96
103
  stopped = true;
@@ -1,10 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { WebSocket } from "ws";
3
+ import { clearDeviceAuthToken, loadDeviceAuthToken, storeDeviceAuthToken, } from "../infra/device-auth-store.js";
4
+ import { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload, } from "../infra/device-identity.js";
3
5
  import { normalizeFingerprint } from "../infra/tls/fingerprint.js";
4
6
  import { rawDataToString } from "../infra/ws.js";
5
7
  import { logDebug, logError } from "../logger.js";
6
- import { loadOrCreateDeviceIdentity, publicKeyRawBase64UrlFromPem, signDevicePayload, } from "../infra/device-identity.js";
7
- import { clearDeviceAuthToken, loadDeviceAuthToken, storeDeviceAuthToken, } from "../infra/device-auth-store.js";
8
8
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, } from "../utils/message-channel.js";
9
9
  import { buildDeviceAuthPayload } from "./device-auth.js";
10
10
  import { PROTOCOL_VERSION, validateEventFrame, validateRequestFrame, validateResponseFrame, } from "./protocol/index.js";
@@ -38,8 +38,9 @@ export class GatewayClient {
38
38
  };
39
39
  }
40
40
  start() {
41
- if (this.closed)
41
+ if (this.closed) {
42
42
  return;
43
+ }
43
44
  const url = this.opts.url ?? "ws://127.0.0.1:18789";
44
45
  if (this.opts.tlsFingerprint && !url.startsWith("wss://")) {
45
46
  this.opts.onConnectError?.(new Error("gateway tls fingerprint requires wss:// gateway url"));
@@ -67,6 +68,7 @@ export class GatewayClient {
67
68
  return new Error("gateway tls fingerprint mismatch");
68
69
  }
69
70
  return undefined;
71
+ // oxlint-disable-next-line typescript/no-explicit-any
70
72
  });
71
73
  }
72
74
  this.ws = new WebSocket(url, wsOptions);
@@ -85,6 +87,19 @@ export class GatewayClient {
85
87
  this.ws.on("close", (code, reason) => {
86
88
  const reasonText = rawDataToString(reason);
87
89
  this.ws = null;
90
+ // If closed due to device token mismatch, clear the stored token so next attempt can get a fresh one
91
+ if (code === 1008 &&
92
+ reasonText.toLowerCase().includes("device token mismatch") &&
93
+ this.opts.deviceIdentity) {
94
+ const role = this.opts.role ?? "operator";
95
+ try {
96
+ clearDeviceAuthToken({ deviceId: this.opts.deviceIdentity.deviceId, role });
97
+ logDebug(`cleared stale device-auth token for device ${this.opts.deviceIdentity.deviceId}`);
98
+ }
99
+ catch (err) {
100
+ logDebug(`failed clearing stale device-auth token for device ${this.opts.deviceIdentity.deviceId}: ${String(err)}`);
101
+ }
102
+ }
88
103
  this.flushPendingErrors(new Error(`gateway closed (${code}): ${reasonText}`));
89
104
  this.scheduleReconnect();
90
105
  this.opts.onClose?.(code, reasonText);
@@ -107,8 +122,9 @@ export class GatewayClient {
107
122
  this.flushPendingErrors(new Error("gateway client stopped"));
108
123
  }
109
124
  sendConnect() {
110
- if (this.connectSent)
125
+ if (this.connectSent) {
111
126
  return;
127
+ }
112
128
  this.connectSent = true;
113
129
  if (this.connectTimer) {
114
130
  clearTimeout(this.connectTimer);
@@ -118,8 +134,9 @@ export class GatewayClient {
118
134
  const storedToken = this.opts.deviceIdentity
119
135
  ? loadDeviceAuthToken({ deviceId: this.opts.deviceIdentity.deviceId, role })?.token
120
136
  : null;
121
- const authToken = storedToken ?? this.opts.token ?? undefined;
122
- const canFallbackToShared = Boolean(storedToken && this.opts.token);
137
+ // Prefer explicitly provided credentials (e.g. CLI `--token`) over any persisted
138
+ // device-auth tokens. Persisted tokens are only used when no token is provided.
139
+ const authToken = this.opts.token ?? storedToken ?? undefined;
123
140
  const auth = authToken || this.opts.password
124
141
  ? {
125
142
  token: authToken,
@@ -130,8 +147,9 @@ export class GatewayClient {
130
147
  const nonce = this.connectNonce ?? undefined;
131
148
  const scopes = this.opts.scopes ?? ["operator.admin"];
132
149
  const device = (() => {
133
- if (!this.opts.deviceIdentity)
150
+ if (!this.opts.deviceIdentity) {
134
151
  return undefined;
152
+ }
135
153
  const payload = buildDeviceAuthPayload({
136
154
  deviceId: this.opts.deviceIdentity.deviceId,
137
155
  clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
@@ -194,18 +212,14 @@ export class GatewayClient {
194
212
  this.opts.onHelloOk?.(helloOk);
195
213
  })
196
214
  .catch((err) => {
197
- if (canFallbackToShared && this.opts.deviceIdentity) {
198
- clearDeviceAuthToken({
199
- deviceId: this.opts.deviceIdentity.deviceId,
200
- role,
201
- });
202
- }
203
215
  this.opts.onConnectError?.(err instanceof Error ? err : new Error(String(err)));
204
216
  const msg = `gateway connect failed: ${String(err)}`;
205
- if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE)
217
+ if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE) {
206
218
  logDebug(msg);
207
- else
219
+ }
220
+ else {
208
221
  logError(msg);
222
+ }
209
223
  this.ws?.close(1008, "connect failed");
210
224
  });
211
225
  }
@@ -238,8 +252,9 @@ export class GatewayClient {
238
252
  }
239
253
  if (validateResponseFrame(parsed)) {
240
254
  const pending = this.pending.get(parsed.id);
241
- if (!pending)
255
+ if (!pending) {
242
256
  return;
257
+ }
243
258
  // If the payload is an ack with status accepted, keep waiting for final.
244
259
  const payload = parsed.payload;
245
260
  const status = payload?.status;
@@ -247,10 +262,12 @@ export class GatewayClient {
247
262
  return;
248
263
  }
249
264
  this.pending.delete(parsed.id);
250
- if (parsed.ok)
265
+ if (parsed.ok) {
251
266
  pending.resolve(parsed.payload);
252
- else
267
+ }
268
+ else {
253
269
  pending.reject(new Error(parsed.error?.message ?? "unknown error"));
270
+ }
254
271
  }
255
272
  }
256
273
  catch (err) {
@@ -260,15 +277,21 @@ export class GatewayClient {
260
277
  queueConnect() {
261
278
  this.connectNonce = null;
262
279
  this.connectSent = false;
263
- if (this.connectTimer)
280
+ const rawConnectDelayMs = this.opts.connectDelayMs;
281
+ const connectDelayMs = typeof rawConnectDelayMs === "number" && Number.isFinite(rawConnectDelayMs)
282
+ ? Math.max(0, Math.min(5_000, rawConnectDelayMs))
283
+ : 750;
284
+ if (this.connectTimer) {
264
285
  clearTimeout(this.connectTimer);
286
+ }
265
287
  this.connectTimer = setTimeout(() => {
266
288
  this.sendConnect();
267
- }, 750);
289
+ }, connectDelayMs);
268
290
  }
269
291
  scheduleReconnect() {
270
- if (this.closed)
292
+ if (this.closed) {
271
293
  return;
294
+ }
272
295
  if (this.tickTimer) {
273
296
  clearInterval(this.tickTimer);
274
297
  this.tickTimer = null;
@@ -284,14 +307,21 @@ export class GatewayClient {
284
307
  this.pending.clear();
285
308
  }
286
309
  startTickWatch() {
287
- if (this.tickTimer)
310
+ if (this.tickTimer) {
288
311
  clearInterval(this.tickTimer);
289
- const interval = Math.max(this.tickIntervalMs, 1000);
312
+ }
313
+ const rawMinInterval = this.opts.tickWatchMinIntervalMs;
314
+ const minInterval = typeof rawMinInterval === "number" && Number.isFinite(rawMinInterval)
315
+ ? Math.max(1, Math.min(30_000, rawMinInterval))
316
+ : 1000;
317
+ const interval = Math.max(this.tickIntervalMs, minInterval);
290
318
  this.tickTimer = setInterval(() => {
291
- if (this.closed)
319
+ if (this.closed) {
292
320
  return;
293
- if (!this.lastTick)
321
+ }
322
+ if (!this.lastTick) {
294
323
  return;
324
+ }
295
325
  const gap = Date.now() - this.lastTick;
296
326
  if (gap > this.tickIntervalMs * 2) {
297
327
  this.ws?.close(4000, "tick timeout");
@@ -299,21 +329,25 @@ export class GatewayClient {
299
329
  }, interval);
300
330
  }
301
331
  validateTlsFingerprint() {
302
- if (!this.opts.tlsFingerprint || !this.ws)
332
+ if (!this.opts.tlsFingerprint || !this.ws) {
303
333
  return null;
334
+ }
304
335
  const expected = normalizeFingerprint(this.opts.tlsFingerprint);
305
- if (!expected)
336
+ if (!expected) {
306
337
  return new Error("gateway tls fingerprint missing");
338
+ }
307
339
  const socket = this.ws._socket;
308
340
  if (!socket || typeof socket.getPeerCertificate !== "function") {
309
341
  return new Error("gateway tls fingerprint unavailable");
310
342
  }
311
343
  const cert = socket.getPeerCertificate();
312
344
  const fingerprint = normalizeFingerprint(cert?.fingerprint256 ?? "");
313
- if (!fingerprint)
345
+ if (!fingerprint) {
314
346
  return new Error("gateway tls fingerprint unavailable");
315
- if (fingerprint !== expected)
347
+ }
348
+ if (fingerprint !== expected) {
316
349
  return new Error("gateway tls fingerprint mismatch");
350
+ }
317
351
  return null;
318
352
  }
319
353
  async request(method, params, opts) {
@@ -1 +1 @@
1
- export const CONTROL_UI_BOOTSTRAP_CONFIG_PATH = "/__openclaw/control-ui-config.json";
1
+ export const CONTROL_UI_BOOTSTRAP_CONFIG_PATH = "/__poolbot/control-ui-config.json";
@@ -20,6 +20,6 @@ export const TAILSCALE_MISSING_BIN_NOTE_LINES = [
20
20
  ];
21
21
  export const TAILSCALE_DOCS_LINES = [
22
22
  "Docs:",
23
- "https://docs.openclaw.ai/gateway/tailscale",
24
- "https://docs.openclaw.ai/web",
23
+ "https://docs.poolbot.dev/gateway/tailscale",
24
+ "https://docs.poolbot.dev/web",
25
25
  ];
@@ -23,6 +23,27 @@ export function pickPrimaryLanIPv4() {
23
23
  }
24
24
  return undefined;
25
25
  }
26
+ export function normalizeHostHeader(hostHeader) {
27
+ return (hostHeader ?? "").trim().toLowerCase();
28
+ }
29
+ export function resolveHostName(hostHeader) {
30
+ const host = normalizeHostHeader(hostHeader);
31
+ if (!host) {
32
+ return "";
33
+ }
34
+ if (host.startsWith("[")) {
35
+ const end = host.indexOf("]");
36
+ if (end !== -1) {
37
+ return host.slice(1, end);
38
+ }
39
+ }
40
+ // Unbracketed IPv6 host (e.g. "::1") has no port and should be returned as-is.
41
+ if (net.isIP(host) === 6) {
42
+ return host;
43
+ }
44
+ const [name] = host.split(":");
45
+ return name ?? "";
46
+ }
26
47
  export function isLoopbackAddress(ip) {
27
48
  if (!ip) {
28
49
  return false;
@@ -41,6 +62,47 @@ export function isLoopbackAddress(ip) {
41
62
  }
42
63
  return false;
43
64
  }
65
+ /**
66
+ * Returns true if the IP belongs to a private or loopback network range.
67
+ * Private ranges: RFC1918, link-local, ULA IPv6, and CGNAT (100.64/10), plus loopback.
68
+ */
69
+ export function isPrivateOrLoopbackAddress(ip) {
70
+ if (!ip) {
71
+ return false;
72
+ }
73
+ if (isLoopbackAddress(ip)) {
74
+ return true;
75
+ }
76
+ const normalized = normalizeIPv4MappedAddress(ip.trim().toLowerCase());
77
+ const family = net.isIP(normalized);
78
+ if (!family) {
79
+ return false;
80
+ }
81
+ if (family === 4) {
82
+ const octets = normalized.split(".").map((value) => Number.parseInt(value, 10));
83
+ if (octets.length !== 4 || octets.some((value) => Number.isNaN(value))) {
84
+ return false;
85
+ }
86
+ const [o1, o2] = octets;
87
+ // RFC1918 IPv4 private ranges.
88
+ if (o1 === 10 || (o1 === 172 && o2 >= 16 && o2 <= 31) || (o1 === 192 && o2 === 168)) {
89
+ return true;
90
+ }
91
+ // IPv4 link-local and CGNAT (commonly used by Tailnet-like networks).
92
+ if ((o1 === 169 && o2 === 254) || (o1 === 100 && o2 >= 64 && o2 <= 127)) {
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ // IPv6 unique-local and link-local ranges.
98
+ if (normalized.startsWith("fc") || normalized.startsWith("fd")) {
99
+ return true;
100
+ }
101
+ if (/^fe[89ab]/.test(normalized)) {
102
+ return true;
103
+ }
104
+ return false;
105
+ }
44
106
  function normalizeIPv4MappedAddress(ip) {
45
107
  if (ip.startsWith("::ffff:")) {
46
108
  return ip.slice("::ffff:".length);
@@ -87,12 +149,58 @@ function parseRealIp(realIp) {
87
149
  }
88
150
  return normalizeIp(stripOptionalPort(raw));
89
151
  }
152
+ /**
153
+ * Check if an IP address matches a CIDR block.
154
+ * Supports IPv4 CIDR notation (e.g., "10.42.0.0/24").
155
+ *
156
+ * @param ip - The IP address to check (e.g., "10.42.0.59")
157
+ * @param cidr - The CIDR block (e.g., "10.42.0.0/24")
158
+ * @returns True if the IP is within the CIDR block
159
+ */
160
+ function ipMatchesCIDR(ip, cidr) {
161
+ // Handle exact IP match (no CIDR notation)
162
+ if (!cidr.includes("/")) {
163
+ return ip === cidr;
164
+ }
165
+ const [subnet, prefixLenStr] = cidr.split("/");
166
+ const prefixLen = parseInt(prefixLenStr, 10);
167
+ // Validate prefix length
168
+ if (Number.isNaN(prefixLen) || prefixLen < 0 || prefixLen > 32) {
169
+ return false;
170
+ }
171
+ // Convert IPs to 32-bit integers
172
+ const ipParts = ip.split(".").map((p) => parseInt(p, 10));
173
+ const subnetParts = subnet.split(".").map((p) => parseInt(p, 10));
174
+ // Validate IP format
175
+ if (ipParts.length !== 4 ||
176
+ subnetParts.length !== 4 ||
177
+ ipParts.some((p) => Number.isNaN(p) || p < 0 || p > 255) ||
178
+ subnetParts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) {
179
+ return false;
180
+ }
181
+ const ipInt = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];
182
+ const subnetInt = (subnetParts[0] << 24) | (subnetParts[1] << 16) | (subnetParts[2] << 8) | subnetParts[3];
183
+ // Create mask and compare
184
+ const mask = prefixLen === 0 ? 0 : (-1 >>> (32 - prefixLen)) << (32 - prefixLen);
185
+ return (ipInt & mask) === (subnetInt & mask);
186
+ }
90
187
  export function isTrustedProxyAddress(ip, trustedProxies) {
91
188
  const normalized = normalizeIp(ip);
92
189
  if (!normalized || !trustedProxies || trustedProxies.length === 0) {
93
190
  return false;
94
191
  }
95
- return trustedProxies.some((proxy) => normalizeIp(proxy) === normalized);
192
+ return trustedProxies.some((proxy) => {
193
+ const candidate = proxy.trim();
194
+ if (!candidate) {
195
+ return false;
196
+ }
197
+ // Handle CIDR notation
198
+ if (candidate.includes("/")) {
199
+ return ipMatchesCIDR(normalized, candidate);
200
+ }
201
+ // Exact IP match
202
+ return normalizeIp(candidate) === normalized;
203
+ });
96
204
  }
97
205
  export function resolveGatewayClientIp(params) {
98
206
  const remote = normalizeIp(params.remoteAddr);