@poolzin/pool-bot 2026.3.25 → 2026.3.26

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 (271) hide show
  1. package/dist/agents/model-fallback.js +5 -4
  2. package/dist/agents/tools/common.js +16 -201
  3. package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
  4. package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
  5. package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
  6. package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
  7. package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
  8. package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
  9. package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
  10. package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
  11. package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
  12. package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
  13. package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
  14. package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
  15. package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
  16. package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
  17. package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
  18. package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
  19. package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
  20. package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
  21. package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
  22. package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
  23. package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
  24. package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
  25. package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
  26. package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
  27. package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
  28. package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
  29. package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
  30. package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
  31. package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
  32. package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
  33. package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
  34. package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
  35. package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
  36. package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
  37. package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
  38. package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
  39. package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
  40. package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
  41. package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
  42. package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
  43. package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
  44. package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
  45. package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
  46. package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
  47. package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
  48. package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
  49. package/dist/build-info.json +3 -3
  50. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  51. package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
  52. package/dist/canvas-host/a2ui/index.html +1 -307
  53. package/dist/channels/channels/directory-config.js +185 -0
  54. package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
  55. package/dist/channels/channels/discord/handle-action.js +165 -0
  56. package/dist/channels/channels/discord.js +413 -0
  57. package/dist/channels/channels/dock.js +436 -0
  58. package/dist/channels/channels/index.js +51 -0
  59. package/dist/channels/channels/plugins/outbound/discord.js +101 -0
  60. package/dist/channels/channels/whatsapp.js +17 -0
  61. package/dist/channels/plugins/types.js +1 -1
  62. package/dist/channels/run-state-machine.js +7 -0
  63. package/dist/commands/models/auth.js +47 -1
  64. package/dist/commands-subagents/action-agents.js +44 -0
  65. package/dist/commands-subagents/action-focus.js +64 -0
  66. package/dist/commands-subagents/action-help.js +4 -0
  67. package/dist/commands-subagents/action-info.js +45 -0
  68. package/dist/commands-subagents/action-kill.js +60 -0
  69. package/dist/commands-subagents/action-list.js +44 -0
  70. package/dist/commands-subagents/action-log.js +29 -0
  71. package/dist/commands-subagents/action-send.js +119 -0
  72. package/dist/commands-subagents/action-spawn.js +52 -0
  73. package/dist/commands-subagents/action-unfocus.js +30 -0
  74. package/dist/commands-subagents/shared.js +303 -0
  75. package/dist/config/config.js +1 -8
  76. package/dist/config/types.secrets.js +61 -0
  77. package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
  78. package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
  79. package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
  80. package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
  81. package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
  82. package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
  83. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
  84. package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
  85. package/dist/control-ui/index.html +1 -1
  86. package/dist/gateway/method-scopes.js +9 -1
  87. package/dist/gateway/node-pending-work.js +142 -0
  88. package/dist/gateway/protocol/index.js +5 -1
  89. package/dist/gateway/protocol/schema/nodes.js +18 -0
  90. package/dist/gateway/server-methods/nodes-pending.js +96 -0
  91. package/dist/gateway/server-methods-list.js +4 -0
  92. package/dist/gateway/server-methods.js +2 -0
  93. package/dist/imessage/channel.js +253 -0
  94. package/dist/imessage/monitor/echo-cache.js +70 -0
  95. package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
  96. package/dist/imessage/monitor/reflection-guard.js +50 -0
  97. package/dist/imessage/monitor/sanitize-outbound.js +25 -0
  98. package/dist/imessage/monitor/self-chat-cache.js +75 -0
  99. package/dist/imessage/runtime.js +3 -0
  100. package/dist/infra/exec-approval-reply.js +7 -0
  101. package/dist/infra/tmp-openclaw-dir.js +84 -0
  102. package/dist/pairing/pairing-challenge.js +15 -0
  103. package/dist/plugin-sdk/account-id.d.ts +1 -0
  104. package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
  105. package/dist/plugin-sdk/allow-from.d.ts +27 -0
  106. package/dist/plugin-sdk/command-auth.d.ts +25 -0
  107. package/dist/plugin-sdk/command-auth.js +3 -1
  108. package/dist/plugin-sdk/config-paths.d.ts +6 -0
  109. package/dist/plugin-sdk/file-lock.d.ts +16 -0
  110. package/dist/plugin-sdk/index.d.ts +428 -0
  111. package/dist/plugin-sdk/index.js +237 -103
  112. package/dist/plugin-sdk/json-store.d.ts +5 -0
  113. package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
  114. package/dist/plugin-sdk/onboarding.d.ts +11 -0
  115. package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
  116. package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
  117. package/dist/plugin-sdk/status-helpers.d.ts +25 -0
  118. package/dist/plugin-sdk/temp-path.d.ts +12 -0
  119. package/dist/plugin-sdk/text-chunking.d.ts +1 -0
  120. package/dist/plugin-sdk/tool-send.d.ts +4 -0
  121. package/dist/plugin-sdk/webhook-path.d.ts +6 -0
  122. package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
  123. package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
  124. package/dist/plugin-sdk-internal/accounts.js +6 -0
  125. package/dist/plugin-sdk-internal/discord.js +23 -0
  126. package/dist/plugin-sdk-internal/imessage.js +13 -0
  127. package/dist/plugin-sdk-internal/setup.js +9 -0
  128. package/dist/plugin-sdk-internal/signal.js +13 -0
  129. package/dist/plugin-sdk-internal/slack.js +22 -0
  130. package/dist/plugin-sdk-internal/telegram.js +32 -0
  131. package/dist/plugin-sdk-internal/whatsapp.js +29 -0
  132. package/dist/routing/session-key.js +4 -185
  133. package/dist/shared/pid-alive.js +2 -61
  134. package/dist/shared/process-scoped-map.js +5 -7
  135. package/dist/signal/channel.js +264 -0
  136. package/dist/signal/monitor/access-policy.js +60 -0
  137. package/dist/signal/runtime.js +3 -0
  138. package/dist/slack/account-inspect.js +135 -0
  139. package/dist/slack/blocks-input.js +7 -38
  140. package/dist/slack/channel.js +394 -0
  141. package/dist/slack/interactive-replies.js +28 -0
  142. package/dist/slack/monitor/channel-type.js +31 -0
  143. package/dist/slack/monitor/dm-auth.js +49 -0
  144. package/dist/slack/monitor/events/interactions.modal.js +137 -0
  145. package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
  146. package/dist/slack/monitor/events/system-event-context.js +29 -0
  147. package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
  148. package/dist/slack/monitor/external-arg-menu-store.js +46 -0
  149. package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
  150. package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
  151. package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
  152. package/dist/slack/monitor/reconnect-policy.js +78 -0
  153. package/dist/slack/monitor/slash-commands.runtime.js +1 -0
  154. package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
  155. package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
  156. package/dist/slack/resolve-allowlist-common.js +36 -0
  157. package/dist/slack/runtime.js +3 -0
  158. package/dist/slack/sent-thread-cache.js +61 -0
  159. package/dist/slack/truncate.js +10 -0
  160. package/dist/telegram/account-inspect.js +175 -0
  161. package/dist/telegram/allow-from.js +10 -0
  162. package/dist/telegram/api-fetch.js +18 -0
  163. package/dist/telegram/approval-buttons.js +30 -0
  164. package/dist/telegram/audit-membership-runtime.js +61 -0
  165. package/dist/telegram/bot/delivery.replies.js +508 -0
  166. package/dist/telegram/bot/delivery.resolve-media.js +227 -0
  167. package/dist/telegram/bot/delivery.send.js +132 -0
  168. package/dist/telegram/bot/reply-threading.js +46 -0
  169. package/dist/telegram/bot-message-context.body.js +186 -0
  170. package/dist/telegram/bot-message-context.session.js +207 -0
  171. package/dist/telegram/bot-message-context.types.js +1 -0
  172. package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
  173. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  174. package/dist/telegram/bot.media.test-utils.js +81 -0
  175. package/dist/telegram/channel-actions.js +225 -0
  176. package/dist/telegram/channel.js +515 -0
  177. package/dist/telegram/conversation-route.js +107 -0
  178. package/dist/telegram/delivery.js +2 -0
  179. package/dist/telegram/delivery.replies.js +508 -0
  180. package/dist/telegram/dm-access.js +86 -0
  181. package/dist/telegram/draft-stream.test-helpers.js +62 -0
  182. package/dist/telegram/exec-approvals-handler.js +281 -0
  183. package/dist/telegram/exec-approvals.js +62 -0
  184. package/dist/telegram/forum-service-message.js +22 -0
  185. package/dist/telegram/group-config-helpers.js +10 -0
  186. package/dist/telegram/lane-delivery-state.js +19 -0
  187. package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
  188. package/dist/telegram/lane-delivery.js +2 -0
  189. package/dist/telegram/normalize.js +37 -0
  190. package/dist/telegram/onboarding.js +192 -0
  191. package/dist/telegram/outbound-adapter.js +100 -0
  192. package/dist/telegram/polling-session.js +275 -0
  193. package/dist/telegram/runtime.js +3 -0
  194. package/dist/telegram/sendchataction-401-backoff.js +71 -0
  195. package/dist/telegram/sequential-key.js +46 -0
  196. package/dist/telegram/status-issues.js +105 -0
  197. package/dist/telegram/target-writeback.js +165 -0
  198. package/dist/telegram/thread-bindings.js +560 -0
  199. package/dist/utils.js +10 -276
  200. package/dist/wizard/prompts.js +5 -5
  201. package/extensions/feishu/src/policy.ts +1 -1
  202. package/extensions/firecrawl/index.test.ts +82 -0
  203. package/extensions/firecrawl/index.ts +20 -0
  204. package/extensions/firecrawl/openclaw.plugin.json +8 -0
  205. package/extensions/firecrawl/package.json +12 -0
  206. package/extensions/firecrawl/src/config.ts +159 -0
  207. package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
  208. package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
  209. package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
  210. package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
  211. package/package.json +1 -1
  212. package/dist/.buildstamp +0 -1
  213. package/dist/acp/bindings-store.js +0 -209
  214. package/dist/acp/control-plane/runtime-cache.js +0 -54
  215. package/dist/acp/control-plane/runtime-options.js +0 -215
  216. package/dist/acp/control-plane/session-actor-queue.js +0 -36
  217. package/dist/acp/index.js +0 -2
  218. package/dist/acp/runtime/errors.js +0 -47
  219. package/dist/acp/runtime/registry.js +0 -86
  220. package/dist/acp/secret-file.js +0 -22
  221. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
  222. package/dist/agents/bash-process-registry.test-helpers.js +0 -29
  223. package/dist/agents/bash-tools.exec-approval-request.js +0 -20
  224. package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
  225. package/dist/agents/bash-tools.exec-host-node.js +0 -235
  226. package/dist/agents/checkpoint-manager.js +0 -290
  227. package/dist/agents/claude-cli-runner.js +0 -3
  228. package/dist/agents/error-classifier.js +0 -251
  229. package/dist/agents/live-model-filter.js +0 -84
  230. package/dist/agents/nvidia-models.js +0 -228
  231. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
  232. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
  233. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
  234. package/dist/agents/provider/config-loader.js +0 -76
  235. package/dist/agents/provider/index.js +0 -15
  236. package/dist/agents/provider/models-dev.js +0 -129
  237. package/dist/agents/provider/session-binding.js +0 -376
  238. package/dist/agents/queued-file-writer.js +0 -22
  239. package/dist/agents/skills/bundled-context.js +0 -23
  240. package/dist/agents/skills/security.js +0 -211
  241. package/dist/agents/skills/tools-dir.js +0 -9
  242. package/dist/agents/skills-install-download.js +0 -290
  243. package/dist/agents/skills-install-output.js +0 -30
  244. package/dist/agents/skills-install.download-test-utils.js +0 -36
  245. package/dist/agents/skills.test-helpers.js +0 -13
  246. package/dist/agents/subagent-announce-reliability.js +0 -160
  247. package/dist/agents/subagent-registry.mocks.shared.js +0 -12
  248. package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
  249. package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
  250. package/dist/agents/test-helpers/fast-core-tools.js +0 -8
  251. package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
  252. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
  253. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
  254. package/dist/agents/tool-display-common.js +0 -915
  255. package/dist/agents/tool-policy-shared.js +0 -108
  256. package/dist/agents/tool-policy.conformance.js +0 -14
  257. package/dist/agents/tool-result-truncation.js +0 -299
  258. package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
  259. package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
  260. package/dist/agents/tools/discord-actions-presence.js +0 -78
  261. package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
  262. package/dist/discord/discord-improvements.js +0 -167
  263. package/dist/discord/index.js +0 -2
  264. package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
  265. package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
  266. package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
  267. package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
  268. package/dist/whatsapp/normalize.js +0 -66
  269. package/dist/whatsapp/resolve-outbound-target.js +0 -42
  270. /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
  271. /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
@@ -0,0 +1,446 @@
1
+ import { markdownToText, truncateText } from "../../../src/agents/tools/web-fetch-utils.js";
2
+ import { withTrustedWebToolsEndpoint } from "../../../src/agents/tools/web-guarded-fetch.js";
3
+ import {
4
+ DEFAULT_CACHE_TTL_MINUTES,
5
+ normalizeCacheKey,
6
+ readCache,
7
+ readResponseText,
8
+ resolveCacheTtlMs,
9
+ writeCache,
10
+ } from "../../../src/agents/tools/web-shared.js";
11
+ import type { OpenClawConfig } from "../../../src/config/config.js";
12
+ import { wrapExternalContent, wrapWebContent } from "../../../src/security/external-content.js";
13
+ import {
14
+ resolveFirecrawlApiKey,
15
+ resolveFirecrawlBaseUrl,
16
+ resolveFirecrawlMaxAgeMs,
17
+ resolveFirecrawlOnlyMainContent,
18
+ resolveFirecrawlScrapeTimeoutSeconds,
19
+ resolveFirecrawlSearchTimeoutSeconds,
20
+ } from "./config.js";
21
+
22
+ const SEARCH_CACHE = new Map<
23
+ string,
24
+ { value: Record<string, unknown>; expiresAt: number; insertedAt: number }
25
+ >();
26
+ const SCRAPE_CACHE = new Map<
27
+ string,
28
+ { value: Record<string, unknown>; expiresAt: number; insertedAt: number }
29
+ >();
30
+ const DEFAULT_SEARCH_COUNT = 5;
31
+ const DEFAULT_SCRAPE_MAX_CHARS = 50_000;
32
+ const DEFAULT_ERROR_MAX_BYTES = 64_000;
33
+
34
+ type FirecrawlSearchItem = {
35
+ title: string;
36
+ url: string;
37
+ description?: string;
38
+ content?: string;
39
+ published?: string;
40
+ siteName?: string;
41
+ };
42
+
43
+ export type FirecrawlSearchParams = {
44
+ cfg?: OpenClawConfig;
45
+ query: string;
46
+ count?: number;
47
+ timeoutSeconds?: number;
48
+ sources?: string[];
49
+ categories?: string[];
50
+ scrapeResults?: boolean;
51
+ };
52
+
53
+ export type FirecrawlScrapeParams = {
54
+ cfg?: OpenClawConfig;
55
+ url: string;
56
+ extractMode: "markdown" | "text";
57
+ maxChars?: number;
58
+ onlyMainContent?: boolean;
59
+ maxAgeMs?: number;
60
+ proxy?: "auto" | "basic" | "stealth";
61
+ storeInCache?: boolean;
62
+ timeoutSeconds?: number;
63
+ };
64
+
65
+ function resolveEndpoint(baseUrl: string, pathname: "/v2/search" | "/v2/scrape"): string {
66
+ const trimmed = baseUrl.trim();
67
+ if (!trimmed) {
68
+ return new URL(pathname, "https://api.firecrawl.dev").toString();
69
+ }
70
+ try {
71
+ const url = new URL(trimmed);
72
+ if (url.pathname && url.pathname !== "/") {
73
+ return url.toString();
74
+ }
75
+ url.pathname = pathname;
76
+ return url.toString();
77
+ } catch {
78
+ return new URL(pathname, "https://api.firecrawl.dev").toString();
79
+ }
80
+ }
81
+
82
+ function resolveSiteName(urlRaw: string): string | undefined {
83
+ try {
84
+ const host = new URL(urlRaw).hostname.replace(/^www\./, "");
85
+ return host || undefined;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ async function postFirecrawlJson(params: {
92
+ baseUrl: string;
93
+ pathname: "/v2/search" | "/v2/scrape";
94
+ apiKey: string;
95
+ body: Record<string, unknown>;
96
+ timeoutSeconds: number;
97
+ errorLabel: string;
98
+ }): Promise<Record<string, unknown>> {
99
+ const endpoint = resolveEndpoint(params.baseUrl, params.pathname);
100
+ return await withTrustedWebToolsEndpoint(
101
+ {
102
+ url: endpoint,
103
+ timeoutSeconds: params.timeoutSeconds,
104
+ init: {
105
+ method: "POST",
106
+ headers: {
107
+ Accept: "application/json",
108
+ Authorization: `Bearer ${params.apiKey}`,
109
+ "Content-Type": "application/json",
110
+ },
111
+ body: JSON.stringify(params.body),
112
+ },
113
+ },
114
+ async ({ response }) => {
115
+ if (!response.ok) {
116
+ const detail = await readResponseText(response, { maxBytes: DEFAULT_ERROR_MAX_BYTES });
117
+ throw new Error(
118
+ `${params.errorLabel} API error (${response.status}): ${detail.text || response.statusText}`,
119
+ );
120
+ }
121
+ const payload = (await response.json()) as Record<string, unknown>;
122
+ if (payload.success === false) {
123
+ const error =
124
+ typeof payload.error === "string"
125
+ ? payload.error
126
+ : typeof payload.message === "string"
127
+ ? payload.message
128
+ : "unknown error";
129
+ throw new Error(`${params.errorLabel} API error: ${error}`);
130
+ }
131
+ return payload;
132
+ },
133
+ );
134
+ }
135
+
136
+ function resolveSearchItems(payload: Record<string, unknown>): FirecrawlSearchItem[] {
137
+ const candidates = [
138
+ payload.data,
139
+ payload.results,
140
+ (payload.data as { results?: unknown } | undefined)?.results,
141
+ (payload.data as { data?: unknown } | undefined)?.data,
142
+ (payload.data as { web?: unknown } | undefined)?.web,
143
+ (payload.web as { results?: unknown } | undefined)?.results,
144
+ ];
145
+ const rawItems = candidates.find((candidate) => Array.isArray(candidate));
146
+ if (!Array.isArray(rawItems)) {
147
+ return [];
148
+ }
149
+ const items: FirecrawlSearchItem[] = [];
150
+ for (const entry of rawItems) {
151
+ if (!entry || typeof entry !== "object") {
152
+ continue;
153
+ }
154
+ const record = entry as Record<string, unknown>;
155
+ const metadata =
156
+ record.metadata && typeof record.metadata === "object"
157
+ ? (record.metadata as Record<string, unknown>)
158
+ : undefined;
159
+ const url =
160
+ (typeof record.url === "string" && record.url) ||
161
+ (typeof record.sourceURL === "string" && record.sourceURL) ||
162
+ (typeof record.sourceUrl === "string" && record.sourceUrl) ||
163
+ (typeof metadata?.sourceURL === "string" && metadata.sourceURL) ||
164
+ "";
165
+ if (!url) {
166
+ continue;
167
+ }
168
+ const title =
169
+ (typeof record.title === "string" && record.title) ||
170
+ (typeof metadata?.title === "string" && metadata.title) ||
171
+ "";
172
+ const description =
173
+ (typeof record.description === "string" && record.description) ||
174
+ (typeof record.snippet === "string" && record.snippet) ||
175
+ (typeof record.summary === "string" && record.summary) ||
176
+ undefined;
177
+ const content =
178
+ (typeof record.markdown === "string" && record.markdown) ||
179
+ (typeof record.content === "string" && record.content) ||
180
+ (typeof record.text === "string" && record.text) ||
181
+ undefined;
182
+ const published =
183
+ (typeof record.publishedDate === "string" && record.publishedDate) ||
184
+ (typeof record.published === "string" && record.published) ||
185
+ (typeof metadata?.publishedTime === "string" && metadata.publishedTime) ||
186
+ (typeof metadata?.publishedDate === "string" && metadata.publishedDate) ||
187
+ undefined;
188
+ items.push({
189
+ title,
190
+ url,
191
+ description,
192
+ content,
193
+ published,
194
+ siteName: resolveSiteName(url),
195
+ });
196
+ }
197
+ return items;
198
+ }
199
+
200
+ function buildSearchPayload(params: {
201
+ query: string;
202
+ provider: "firecrawl";
203
+ items: FirecrawlSearchItem[];
204
+ tookMs: number;
205
+ scrapeResults: boolean;
206
+ }): Record<string, unknown> {
207
+ return {
208
+ query: params.query,
209
+ provider: params.provider,
210
+ count: params.items.length,
211
+ tookMs: params.tookMs,
212
+ externalContent: {
213
+ untrusted: true,
214
+ source: "web_search",
215
+ provider: params.provider,
216
+ wrapped: true,
217
+ },
218
+ results: params.items.map((entry) => ({
219
+ title: entry.title ? wrapWebContent(entry.title, "web_search") : "",
220
+ url: entry.url,
221
+ description: entry.description ? wrapWebContent(entry.description, "web_search") : "",
222
+ ...(entry.published ? { published: entry.published } : {}),
223
+ ...(entry.siteName ? { siteName: entry.siteName } : {}),
224
+ ...(params.scrapeResults && entry.content
225
+ ? { content: wrapWebContent(entry.content, "web_search") }
226
+ : {}),
227
+ })),
228
+ };
229
+ }
230
+
231
+ export async function runFirecrawlSearch(
232
+ params: FirecrawlSearchParams,
233
+ ): Promise<Record<string, unknown>> {
234
+ const apiKey = resolveFirecrawlApiKey(params.cfg);
235
+ if (!apiKey) {
236
+ throw new Error(
237
+ "web_search (firecrawl) needs a Firecrawl API key. Set FIRECRAWL_API_KEY in the Gateway environment, or configure tools.web.search.firecrawl.apiKey.",
238
+ );
239
+ }
240
+ const count =
241
+ typeof params.count === "number" && Number.isFinite(params.count)
242
+ ? Math.max(1, Math.min(10, Math.floor(params.count)))
243
+ : DEFAULT_SEARCH_COUNT;
244
+ const timeoutSeconds = resolveFirecrawlSearchTimeoutSeconds(params.timeoutSeconds);
245
+ const scrapeResults = params.scrapeResults === true;
246
+ const sources = Array.isArray(params.sources) ? params.sources.filter(Boolean) : [];
247
+ const categories = Array.isArray(params.categories) ? params.categories.filter(Boolean) : [];
248
+ const baseUrl = resolveFirecrawlBaseUrl(params.cfg);
249
+ const cacheKey = normalizeCacheKey(
250
+ JSON.stringify({
251
+ type: "firecrawl-search",
252
+ q: params.query,
253
+ count,
254
+ baseUrl,
255
+ sources,
256
+ categories,
257
+ scrapeResults,
258
+ }),
259
+ );
260
+ const cached = readCache(SEARCH_CACHE, cacheKey);
261
+ if (cached) {
262
+ return { ...cached.value, cached: true };
263
+ }
264
+
265
+ const body: Record<string, unknown> = {
266
+ query: params.query,
267
+ limit: count,
268
+ };
269
+ if (sources.length > 0) {
270
+ body.sources = sources;
271
+ }
272
+ if (categories.length > 0) {
273
+ body.categories = categories;
274
+ }
275
+ if (scrapeResults) {
276
+ body.scrapeOptions = {
277
+ formats: ["markdown"],
278
+ };
279
+ }
280
+
281
+ const start = Date.now();
282
+ const payload = await postFirecrawlJson({
283
+ baseUrl,
284
+ pathname: "/v2/search",
285
+ apiKey,
286
+ body,
287
+ timeoutSeconds,
288
+ errorLabel: "Firecrawl Search",
289
+ });
290
+ const result = buildSearchPayload({
291
+ query: params.query,
292
+ provider: "firecrawl",
293
+ items: resolveSearchItems(payload),
294
+ tookMs: Date.now() - start,
295
+ scrapeResults,
296
+ });
297
+ writeCache(
298
+ SEARCH_CACHE,
299
+ cacheKey,
300
+ result,
301
+ resolveCacheTtlMs(undefined, DEFAULT_CACHE_TTL_MINUTES),
302
+ );
303
+ return result;
304
+ }
305
+
306
+ function resolveScrapeData(payload: Record<string, unknown>): Record<string, unknown> {
307
+ const data = payload.data;
308
+ if (data && typeof data === "object") {
309
+ return data as Record<string, unknown>;
310
+ }
311
+ return {};
312
+ }
313
+
314
+ export function parseFirecrawlScrapePayload(params: {
315
+ payload: Record<string, unknown>;
316
+ url: string;
317
+ extractMode: "markdown" | "text";
318
+ maxChars: number;
319
+ }): Record<string, unknown> {
320
+ const data = resolveScrapeData(params.payload);
321
+ const metadata =
322
+ data.metadata && typeof data.metadata === "object"
323
+ ? (data.metadata as Record<string, unknown>)
324
+ : undefined;
325
+ const markdown =
326
+ (typeof data.markdown === "string" && data.markdown) ||
327
+ (typeof data.content === "string" && data.content) ||
328
+ "";
329
+ if (!markdown) {
330
+ throw new Error("Firecrawl scrape returned no content.");
331
+ }
332
+ const rawText = params.extractMode === "text" ? markdownToText(markdown) : markdown;
333
+ const truncated = truncateText(rawText, params.maxChars);
334
+ return {
335
+ url: params.url,
336
+ finalUrl:
337
+ (typeof metadata?.sourceURL === "string" && metadata.sourceURL) ||
338
+ (typeof data.url === "string" && data.url) ||
339
+ params.url,
340
+ status:
341
+ (typeof metadata?.statusCode === "number" && metadata.statusCode) ||
342
+ (typeof data.statusCode === "number" && data.statusCode) ||
343
+ undefined,
344
+ title:
345
+ typeof metadata?.title === "string" && metadata.title
346
+ ? wrapExternalContent(metadata.title, { source: "web_fetch", includeWarning: false })
347
+ : undefined,
348
+ extractor: "firecrawl",
349
+ extractMode: params.extractMode,
350
+ externalContent: {
351
+ untrusted: true,
352
+ source: "web_fetch",
353
+ wrapped: true,
354
+ },
355
+ truncated: truncated.truncated,
356
+ rawLength: rawText.length,
357
+ wrappedLength: wrapExternalContent(truncated.text, {
358
+ source: "web_fetch",
359
+ includeWarning: false,
360
+ }).length,
361
+ text: wrapExternalContent(truncated.text, {
362
+ source: "web_fetch",
363
+ includeWarning: false,
364
+ }),
365
+ warning:
366
+ typeof params.payload.warning === "string" && params.payload.warning
367
+ ? wrapExternalContent(params.payload.warning, {
368
+ source: "web_fetch",
369
+ includeWarning: false,
370
+ })
371
+ : undefined,
372
+ };
373
+ }
374
+
375
+ export async function runFirecrawlScrape(
376
+ params: FirecrawlScrapeParams,
377
+ ): Promise<Record<string, unknown>> {
378
+ const apiKey = resolveFirecrawlApiKey(params.cfg);
379
+ if (!apiKey) {
380
+ throw new Error(
381
+ "firecrawl_scrape needs a Firecrawl API key. Set FIRECRAWL_API_KEY in the Gateway environment, or configure tools.web.fetch.firecrawl.apiKey.",
382
+ );
383
+ }
384
+ const baseUrl = resolveFirecrawlBaseUrl(params.cfg);
385
+ const timeoutSeconds = resolveFirecrawlScrapeTimeoutSeconds(params.cfg, params.timeoutSeconds);
386
+ const onlyMainContent = resolveFirecrawlOnlyMainContent(params.cfg, params.onlyMainContent);
387
+ const maxAgeMs = resolveFirecrawlMaxAgeMs(params.cfg, params.maxAgeMs);
388
+ const proxy = params.proxy ?? "auto";
389
+ const storeInCache = params.storeInCache ?? true;
390
+ const maxChars =
391
+ typeof params.maxChars === "number" && Number.isFinite(params.maxChars) && params.maxChars > 0
392
+ ? Math.floor(params.maxChars)
393
+ : DEFAULT_SCRAPE_MAX_CHARS;
394
+ const cacheKey = normalizeCacheKey(
395
+ JSON.stringify({
396
+ type: "firecrawl-scrape",
397
+ url: params.url,
398
+ extractMode: params.extractMode,
399
+ baseUrl,
400
+ onlyMainContent,
401
+ maxAgeMs,
402
+ proxy,
403
+ storeInCache,
404
+ maxChars,
405
+ }),
406
+ );
407
+ const cached = readCache(SCRAPE_CACHE, cacheKey);
408
+ if (cached) {
409
+ return { ...cached.value, cached: true };
410
+ }
411
+
412
+ const payload = await postFirecrawlJson({
413
+ baseUrl,
414
+ pathname: "/v2/scrape",
415
+ apiKey,
416
+ timeoutSeconds,
417
+ errorLabel: "Firecrawl",
418
+ body: {
419
+ url: params.url,
420
+ formats: ["markdown"],
421
+ onlyMainContent,
422
+ timeout: timeoutSeconds * 1000,
423
+ maxAge: maxAgeMs,
424
+ proxy,
425
+ storeInCache,
426
+ },
427
+ });
428
+ const result = parseFirecrawlScrapePayload({
429
+ payload,
430
+ url: params.url,
431
+ extractMode: params.extractMode,
432
+ maxChars,
433
+ });
434
+ writeCache(
435
+ SCRAPE_CACHE,
436
+ cacheKey,
437
+ result,
438
+ resolveCacheTtlMs(undefined, DEFAULT_CACHE_TTL_MINUTES),
439
+ );
440
+ return result;
441
+ }
442
+
443
+ export const __testing = {
444
+ parseFirecrawlScrapePayload,
445
+ resolveSearchItems,
446
+ };
@@ -0,0 +1,89 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { optionalStringEnum } from "../../../src/agents/schema/typebox.js";
3
+ import { jsonResult, readNumberParam, readStringParam } from "../../../src/agents/tools/common.js";
4
+ import type { OpenClawPluginApi } from "../../../src/plugins/types.js";
5
+ import { runFirecrawlScrape } from "./firecrawl-client.js";
6
+
7
+ const FirecrawlScrapeToolSchema = Type.Object(
8
+ {
9
+ url: Type.String({ description: "HTTP or HTTPS URL to scrape via Firecrawl." }),
10
+ extractMode: optionalStringEnum(["markdown", "text"] as const, {
11
+ description: 'Extraction mode ("markdown" or "text"). Default: markdown.',
12
+ }),
13
+ maxChars: Type.Optional(
14
+ Type.Number({
15
+ description: "Maximum characters to return.",
16
+ minimum: 100,
17
+ }),
18
+ ),
19
+ onlyMainContent: Type.Optional(
20
+ Type.Boolean({
21
+ description: "Keep only main content when Firecrawl supports it.",
22
+ }),
23
+ ),
24
+ maxAgeMs: Type.Optional(
25
+ Type.Number({
26
+ description: "Maximum Firecrawl cache age in milliseconds.",
27
+ minimum: 0,
28
+ }),
29
+ ),
30
+ proxy: optionalStringEnum(["auto", "basic", "stealth"] as const, {
31
+ description: 'Firecrawl proxy mode ("auto", "basic", or "stealth").',
32
+ }),
33
+ storeInCache: Type.Optional(
34
+ Type.Boolean({
35
+ description: "Whether Firecrawl should store the scrape in its cache.",
36
+ }),
37
+ ),
38
+ timeoutSeconds: Type.Optional(
39
+ Type.Number({
40
+ description: "Timeout in seconds for the Firecrawl scrape request.",
41
+ minimum: 1,
42
+ }),
43
+ ),
44
+ },
45
+ { additionalProperties: false },
46
+ );
47
+
48
+ export function createFirecrawlScrapeTool(api: OpenClawPluginApi) {
49
+ return {
50
+ name: "firecrawl_scrape",
51
+ label: "Firecrawl Scrape",
52
+ description:
53
+ "Scrape a page using Firecrawl v2/scrape. Useful for JS-heavy or bot-protected pages where plain web_fetch is weak.",
54
+ parameters: FirecrawlScrapeToolSchema,
55
+ execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
56
+ const url = readStringParam(rawParams, "url", { required: true });
57
+ const extractMode =
58
+ readStringParam(rawParams, "extractMode") === "text" ? "text" : "markdown";
59
+ const maxChars = readNumberParam(rawParams, "maxChars", { integer: true });
60
+ const maxAgeMs = readNumberParam(rawParams, "maxAgeMs", { integer: true });
61
+ const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
62
+ integer: true,
63
+ });
64
+ const proxyRaw = readStringParam(rawParams, "proxy");
65
+ const proxy =
66
+ proxyRaw === "basic" || proxyRaw === "stealth" || proxyRaw === "auto"
67
+ ? proxyRaw
68
+ : undefined;
69
+ const onlyMainContent =
70
+ typeof rawParams.onlyMainContent === "boolean" ? rawParams.onlyMainContent : undefined;
71
+ const storeInCache =
72
+ typeof rawParams.storeInCache === "boolean" ? rawParams.storeInCache : undefined;
73
+
74
+ return jsonResult(
75
+ await runFirecrawlScrape({
76
+ cfg: api.config,
77
+ url,
78
+ extractMode,
79
+ maxChars,
80
+ onlyMainContent,
81
+ maxAgeMs,
82
+ proxy,
83
+ storeInCache,
84
+ timeoutSeconds,
85
+ }),
86
+ );
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,63 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { WebSearchProviderPlugin } from "../../../src/plugins/types.js";
3
+ import { runFirecrawlSearch } from "./firecrawl-client.js";
4
+
5
+ const GenericFirecrawlSearchSchema = Type.Object(
6
+ {
7
+ query: Type.String({ description: "Search query string." }),
8
+ count: Type.Optional(
9
+ Type.Number({
10
+ description: "Number of results to return (1-10).",
11
+ minimum: 1,
12
+ maximum: 10,
13
+ }),
14
+ ),
15
+ },
16
+ { additionalProperties: false },
17
+ );
18
+
19
+ function getScopedCredentialValue(searchConfig?: Record<string, unknown>): unknown {
20
+ const scoped = searchConfig?.firecrawl;
21
+ if (!scoped || typeof scoped !== "object" || Array.isArray(scoped)) {
22
+ return undefined;
23
+ }
24
+ return (scoped as Record<string, unknown>).apiKey;
25
+ }
26
+
27
+ function setScopedCredentialValue(
28
+ searchConfigTarget: Record<string, unknown>,
29
+ value: unknown,
30
+ ): void {
31
+ const scoped = searchConfigTarget.firecrawl;
32
+ if (!scoped || typeof scoped !== "object" || Array.isArray(scoped)) {
33
+ searchConfigTarget.firecrawl = { apiKey: value };
34
+ return;
35
+ }
36
+ (scoped as Record<string, unknown>).apiKey = value;
37
+ }
38
+
39
+ export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin {
40
+ return {
41
+ id: "firecrawl",
42
+ label: "Firecrawl Search",
43
+ hint: "Structured results with optional result scraping",
44
+ envVars: ["FIRECRAWL_API_KEY"],
45
+ placeholder: "fc-...",
46
+ signupUrl: "https://www.firecrawl.dev/",
47
+ docsUrl: "https://docs.openclaw.ai/tools/firecrawl",
48
+ autoDetectOrder: 60,
49
+ getCredentialValue: getScopedCredentialValue,
50
+ setCredentialValue: setScopedCredentialValue,
51
+ createTool: (ctx) => ({
52
+ description:
53
+ "Search the web using Firecrawl. Returns structured results with snippets from Firecrawl Search. Use firecrawl_search for Firecrawl-specific knobs like sources or categories.",
54
+ parameters: GenericFirecrawlSearchSchema,
55
+ execute: async (args) =>
56
+ await runFirecrawlSearch({
57
+ cfg: ctx.config,
58
+ query: typeof args.query === "string" ? args.query : "",
59
+ count: typeof args.count === "number" ? args.count : undefined,
60
+ }),
61
+ }),
62
+ };
63
+ }
@@ -0,0 +1,76 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import {
3
+ jsonResult,
4
+ readNumberParam,
5
+ readStringArrayParam,
6
+ readStringParam,
7
+ } from "../../../src/agents/tools/common.js";
8
+ import type { OpenClawPluginApi } from "../../../src/plugins/types.js";
9
+ import { runFirecrawlSearch } from "./firecrawl-client.js";
10
+
11
+ const FirecrawlSearchToolSchema = Type.Object(
12
+ {
13
+ query: Type.String({ description: "Search query string." }),
14
+ count: Type.Optional(
15
+ Type.Number({
16
+ description: "Number of results to return (1-10).",
17
+ minimum: 1,
18
+ maximum: 10,
19
+ }),
20
+ ),
21
+ sources: Type.Optional(
22
+ Type.Array(Type.String(), {
23
+ description: 'Optional sources list, for example ["web"], ["news"], or ["images"].',
24
+ }),
25
+ ),
26
+ categories: Type.Optional(
27
+ Type.Array(Type.String(), {
28
+ description: 'Optional Firecrawl categories, for example ["github"] or ["research"].',
29
+ }),
30
+ ),
31
+ scrapeResults: Type.Optional(
32
+ Type.Boolean({
33
+ description: "Include scraped result content when Firecrawl returns it.",
34
+ }),
35
+ ),
36
+ timeoutSeconds: Type.Optional(
37
+ Type.Number({
38
+ description: "Timeout in seconds for the Firecrawl Search request.",
39
+ minimum: 1,
40
+ }),
41
+ ),
42
+ },
43
+ { additionalProperties: false },
44
+ );
45
+
46
+ export function createFirecrawlSearchTool(api: OpenClawPluginApi) {
47
+ return {
48
+ name: "firecrawl_search",
49
+ label: "Firecrawl Search",
50
+ description:
51
+ "Search the web using Firecrawl v2/search. Can optionally include scraped content from result pages.",
52
+ parameters: FirecrawlSearchToolSchema,
53
+ execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
54
+ const query = readStringParam(rawParams, "query", { required: true });
55
+ const count = readNumberParam(rawParams, "count", { integer: true });
56
+ const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
57
+ integer: true,
58
+ });
59
+ const sources = readStringArrayParam(rawParams, "sources");
60
+ const categories = readStringArrayParam(rawParams, "categories");
61
+ const scrapeResults = rawParams.scrapeResults === true;
62
+
63
+ return jsonResult(
64
+ await runFirecrawlSearch({
65
+ cfg: api.config,
66
+ query,
67
+ count,
68
+ timeoutSeconds,
69
+ sources,
70
+ categories,
71
+ scrapeResults,
72
+ }),
73
+ );
74
+ },
75
+ };
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolzin/pool-bot",
3
- "version": "2026.3.25",
3
+ "version": "2026.3.26",
4
4
  "description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
5
5
  "keywords": [],
6
6
  "license": "MIT",
package/dist/.buildstamp DELETED
@@ -1 +0,0 @@
1
- 1773689665939