@poolzin/pool-bot 2026.3.11 → 2026.3.14

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 (195) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/agents/checkpoint-manager.js +291 -0
  4. package/dist/agents/poolbot-tools.js +5 -0
  5. package/dist/agents/subagent-announce-reliability.js +160 -0
  6. package/dist/agents/tool-result-truncation.js +299 -0
  7. package/dist/agents/tools/nodes-file-tool.js +197 -0
  8. package/dist/build-info.json +3 -3
  9. package/dist/cli/config-cli.js +60 -0
  10. package/dist/cron/cron-improvements.js +195 -0
  11. package/dist/discord/discord-improvements.js +167 -0
  12. package/dist/gateway/auth-rate-limit.js +19 -0
  13. package/dist/gateway/auth.js +41 -0
  14. package/dist/gateway/gateway-improvements.js +294 -0
  15. package/dist/gateway/node-command-policy.js +7 -2
  16. package/dist/infra/net/ssrf.js +15 -2
  17. package/dist/infra/shell-security.js +201 -0
  18. package/dist/memory/memory-improvements.js +239 -0
  19. package/dist/node-host/runner.js +146 -79
  20. package/dist/security/prototype-pollution.js +141 -0
  21. package/dist/security/webhook-security.js +253 -0
  22. package/dist/shared/net/ip.js +52 -1
  23. package/dist/slack/slack-improvements.js +225 -0
  24. package/dist/telegram/telegram-improvements.js +220 -0
  25. package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
  26. package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
  27. package/docs/competitive-analysis.md +421 -0
  28. package/docs/implementation-analysis.md +393 -0
  29. package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
  30. package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
  31. package/docs/refactor/plugin-development-guide.md +281 -0
  32. package/extensions/agency-agents/README.md +301 -0
  33. package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
  34. package/extensions/agency-agents/agents/README.md +602 -0
  35. package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
  36. package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
  37. package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
  38. package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
  39. package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
  40. package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
  41. package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
  42. package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
  43. package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
  44. package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
  45. package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
  46. package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
  47. package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
  48. package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
  49. package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
  50. package/extensions/agency-agents/agents/examples/README.md +48 -0
  51. package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
  52. package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
  53. package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
  54. package/extensions/agency-agents/agents/integrations/README.md +117 -0
  55. package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
  56. package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
  57. package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
  58. package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
  59. package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
  60. package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
  61. package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
  62. package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
  63. package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
  64. package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
  65. package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
  66. package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
  67. package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
  68. package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
  69. package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
  70. package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
  71. package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
  72. package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
  73. package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
  74. package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
  75. package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
  76. package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
  77. package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
  78. package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
  79. package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
  80. package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
  81. package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
  82. package/extensions/agency-agents/agents/scripts/install.sh +465 -0
  83. package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
  84. package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
  85. package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
  86. package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
  87. package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
  88. package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
  89. package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
  90. package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
  91. package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
  92. package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
  93. package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
  94. package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
  95. package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
  96. package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
  97. package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
  98. package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
  99. package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
  100. package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
  101. package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
  102. package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
  103. package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
  104. package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
  105. package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
  106. package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
  107. package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
  108. package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
  109. package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
  110. package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
  111. package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
  112. package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
  113. package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
  114. package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
  115. package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
  116. package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
  117. package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
  118. package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
  119. package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
  120. package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
  121. package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
  122. package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
  123. package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
  124. package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
  125. package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
  126. package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
  127. package/extensions/agency-agents/index.ts +733 -0
  128. package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
  129. package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
  130. package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
  131. package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
  132. package/extensions/agency-agents/node_modules/.bin/vite +21 -0
  133. package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
  134. package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
  135. package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  136. package/extensions/agency-agents/package.json +25 -0
  137. package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
  138. package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
  139. package/extensions/agency-agents/src/types.ts +147 -0
  140. package/extensions/agency-agents/vitest.config.ts +8 -0
  141. package/extensions/hexstrike-ai/README.md +98 -0
  142. package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
  143. package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
  144. package/extensions/hexstrike-ai/package.json +29 -0
  145. package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
  146. package/extensions/hexstrike-ai/src/client.ts +91 -0
  147. package/extensions/hexstrike-ai/src/index.ts +170 -0
  148. package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
  149. package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
  150. package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
  151. package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
  152. package/extensions/hexstrike-ai/tsconfig.json +20 -0
  153. package/extensions/hexstrike-bridge/package.json +1 -1
  154. package/extensions/hexstrike-bridge/poolbot.plugin.json +23 -0
  155. package/extensions/mcp-server/poolbot.plugin.json +10 -0
  156. package/extensions/page-agent/README.md +159 -0
  157. package/extensions/page-agent/index.ts +595 -0
  158. package/extensions/page-agent/node_modules/.bin/jiti +21 -0
  159. package/extensions/page-agent/node_modules/.bin/playwright +21 -0
  160. package/extensions/page-agent/node_modules/.bin/tsc +21 -0
  161. package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
  162. package/extensions/page-agent/node_modules/.bin/tsx +21 -0
  163. package/extensions/page-agent/node_modules/.bin/vitest +21 -0
  164. package/extensions/page-agent/node_modules/.bin/yaml +21 -0
  165. package/extensions/page-agent/package.json +43 -0
  166. package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
  167. package/extensions/page-agent/src/PageAgentService.ts +636 -0
  168. package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
  169. package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
  170. package/extensions/page-agent/src/index.ts +20 -0
  171. package/extensions/page-agent/src/tools.test.ts +231 -0
  172. package/extensions/page-agent/src/tools.ts +167 -0
  173. package/extensions/page-agent/src/types.ts +198 -0
  174. package/extensions/template/README.md +101 -0
  175. package/extensions/template/index.ts +38 -0
  176. package/extensions/template/package.json +15 -0
  177. package/extensions/template/poolbot.plugin.json +10 -0
  178. package/extensions/xyops/README.md +227 -0
  179. package/extensions/xyops/index.ts +342 -0
  180. package/extensions/xyops/node_modules/.bin/jiti +21 -0
  181. package/extensions/xyops/node_modules/.bin/tsc +21 -0
  182. package/extensions/xyops/node_modules/.bin/tsserver +21 -0
  183. package/extensions/xyops/node_modules/.bin/tsx +21 -0
  184. package/extensions/xyops/node_modules/.bin/vitest +21 -0
  185. package/extensions/xyops/node_modules/.bin/yaml +21 -0
  186. package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  187. package/extensions/xyops/package.json +39 -0
  188. package/extensions/xyops/src/client.test.ts +467 -0
  189. package/extensions/xyops/src/client.ts +157 -0
  190. package/extensions/xyops/src/types.ts +147 -0
  191. package/extensions/xyops/vitest.config.ts +8 -0
  192. package/package.json +1 -1
  193. package/extensions/mavalie/README.md +0 -97
  194. package/extensions/mavalie/package.json +0 -15
  195. package/extensions/mavalie/src/index.ts +0 -62
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Memory Improvements
3
+ *
4
+ * Implements OpenClaw improvements:
5
+ * - QMD collection safety (avoid destructive rebinds)
6
+ * - Hybrid search with FTS fallback + query expansion
7
+ * - Multimodal indexing (images/audio) with Gemini embeddings
8
+ * - SQLite contention resilience (PRAGMA busy_timeout)
9
+ * - Batch embedding with conservative limits
10
+ *
11
+ * OpenClaw #30951, #32384
12
+ */
13
+ export const DEFAULT_QMD_COLLECTION_SAFETY_CONFIG = {
14
+ enabled: true,
15
+ maxConcurrentCollections: 1, // Prevent concurrent writes
16
+ collectionTimeoutMs: 60_000, // 1 minute
17
+ retryOnFailure: true,
18
+ maxRetries: 3,
19
+ backupBeforeCollect: true,
20
+ };
21
+ const qmdCollectionStates = new Map();
22
+ /**
23
+ * Get or create QMD collection state
24
+ */
25
+ export function getOrCreateQmdCollectionState(agentId) {
26
+ const existing = qmdCollectionStates.get(agentId);
27
+ if (existing) {
28
+ return existing;
29
+ }
30
+ const newState = {
31
+ collecting: false,
32
+ collectionCount: 0,
33
+ failedCount: 0,
34
+ };
35
+ qmdCollectionStates.set(agentId, newState);
36
+ return newState;
37
+ }
38
+ /**
39
+ * Check if QMD collection is safe to run
40
+ */
41
+ export function isQmdCollectionSafe(agentId, config = DEFAULT_QMD_COLLECTION_SAFETY_CONFIG) {
42
+ if (!config.enabled) {
43
+ return { safe: true };
44
+ }
45
+ const state = getOrCreateQmdCollectionState(agentId);
46
+ if (state.collecting) {
47
+ return { safe: false, reason: "Collection already in progress" };
48
+ }
49
+ return { safe: true };
50
+ }
51
+ /**
52
+ * Start QMD collection with safety checks
53
+ */
54
+ export function startQmdCollection(agentId) {
55
+ const state = getOrCreateQmdCollectionState(agentId);
56
+ state.collecting = true;
57
+ }
58
+ /**
59
+ * End QMD collection (success or failure)
60
+ */
61
+ export function endQmdCollection(params) {
62
+ const { agentId, success, error } = params;
63
+ const state = getOrCreateQmdCollectionState(agentId);
64
+ state.collecting = false;
65
+ state.lastCollectionAt = Date.now();
66
+ if (success) {
67
+ state.collectionCount += 1;
68
+ state.lastError = undefined;
69
+ }
70
+ else {
71
+ state.failedCount += 1;
72
+ state.lastError = error;
73
+ }
74
+ }
75
+ export const DEFAULT_HYBRID_SEARCH_CONFIG = {
76
+ enabled: true,
77
+ enableFtsFallback: true,
78
+ ftsMinScore: 0.5,
79
+ enableQueryExpansion: true,
80
+ maxExpandedQueries: 5,
81
+ expansionSynonyms: {
82
+ code: ["function", "method", "class", "module", "file"],
83
+ error: ["bug", "issue", "problem", "failure", "exception"],
84
+ test: ["spec", "unit test", "integration test", "e2e"],
85
+ config: ["configuration", "settings", "options"],
86
+ api: ["endpoint", "route", "handler", "service"],
87
+ },
88
+ combineResults: true,
89
+ vectorWeight: 0.6,
90
+ ftsWeight: 0.4,
91
+ };
92
+ /**
93
+ * Expand query with synonyms for better search coverage
94
+ */
95
+ export function expandQuery(query, config = DEFAULT_HYBRID_SEARCH_CONFIG) {
96
+ if (!config.enableQueryExpansion) {
97
+ return [query];
98
+ }
99
+ const expanded = new Set([query]);
100
+ const queryLower = query.toLowerCase();
101
+ for (const [term, synonyms] of Object.entries(config.expansionSynonyms)) {
102
+ if (queryLower.includes(term)) {
103
+ for (const synonym of synonyms) {
104
+ const expandedQuery = queryLower.replace(term, synonym);
105
+ if (expanded.size < config.maxExpandedQueries) {
106
+ expanded.add(expandedQuery);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return Array.from(expanded).slice(0, config.maxExpandedQueries);
112
+ }
113
+ /**
114
+ * Combine vector and FTS search results with weighted scoring
115
+ */
116
+ export function combineSearchResults(params) {
117
+ const { vectorResults, ftsResults, config } = params;
118
+ const combined = new Map();
119
+ // Add vector results
120
+ for (const result of vectorResults) {
121
+ combined.set(result.id, {
122
+ ...result,
123
+ score: result.score * config.vectorWeight,
124
+ sources: ["vector"],
125
+ });
126
+ }
127
+ // Add/merge FTS results
128
+ for (const result of ftsResults) {
129
+ const existing = combined.get(result.id);
130
+ if (existing) {
131
+ // Merge scores
132
+ existing.score += result.score * config.ftsWeight;
133
+ existing.sources.push("fts");
134
+ }
135
+ else {
136
+ combined.set(result.id, {
137
+ ...result,
138
+ score: result.score * config.ftsWeight,
139
+ sources: ["fts"],
140
+ });
141
+ }
142
+ }
143
+ // Convert to array and sort by score
144
+ return Array.from(combined.values())
145
+ .map(({ sources, ...rest }) => ({
146
+ ...rest,
147
+ source: sources.length > 1 ? "combined" : sources[0],
148
+ }))
149
+ .sort((a, b) => b.score - a.score);
150
+ }
151
+ export const DEFAULT_MULTIMODAL_INDEXING_CONFIG = {
152
+ enabled: true,
153
+ indexImages: true,
154
+ indexAudio: true,
155
+ indexVideos: false, // Videos are expensive, disabled by default
156
+ embeddingModel: "gemini", // Gemini has good multimodal support
157
+ maxFileSizeBytes: 10 * 1024 * 1024, // 10 MB
158
+ batchSize: 10,
159
+ extractTextFromImages: true,
160
+ generateImageCaptions: true,
161
+ transcribeAudio: true,
162
+ };
163
+ export const DEFAULT_SQLITE_CONTENTION_CONFIG = {
164
+ enabled: true,
165
+ busyTimeoutMs: 30_000, // 30 seconds
166
+ walMode: true, // Write-Ahead Logging for better concurrency
167
+ synchronousMode: "NORMAL", // Good balance of safety and performance
168
+ cacheSizePages: 2000, // ~8 MB cache
169
+ maxBusyRetries: 5,
170
+ retryDelayMs: 100,
171
+ };
172
+ /**
173
+ * Generate SQLite PRAGMA statements for contention resilience
174
+ */
175
+ export function generateSqlitePragmas(config = DEFAULT_SQLITE_CONTENTION_CONFIG) {
176
+ if (!config.enabled) {
177
+ return [];
178
+ }
179
+ const pragmas = [];
180
+ pragmas.push(`PRAGMA busy_timeout = ${config.busyTimeoutMs};`);
181
+ if (config.walMode) {
182
+ pragmas.push("PRAGMA journal_mode = WAL;");
183
+ }
184
+ pragmas.push(`PRAGMA synchronous = ${config.synchronousMode};`);
185
+ pragmas.push(`PRAGMA cache_size = ${config.cacheSizePages};`);
186
+ // Enable foreign keys
187
+ pragmas.push("PRAGMA foreign_keys = ON;");
188
+ // Enable temp store in memory
189
+ pragmas.push("PRAGMA temp_store = MEMORY;");
190
+ return pragmas;
191
+ }
192
+ export const DEFAULT_BATCH_EMBEDDING_CONFIG = {
193
+ enabled: true,
194
+ maxBatchSize: 100, // Conservative default
195
+ batchTimeoutMs: 60_000, // 1 minute
196
+ maxConcurrentBatches: 2, // Limit concurrent requests
197
+ retryOnFailure: true,
198
+ maxRetries: 3,
199
+ backoffBaseDelayMs: 1000,
200
+ rateLimitPerMinute: 60, // 1 request per second average
201
+ };
202
+ const batchEmbeddingStates = new Map();
203
+ /**
204
+ * Get or create batch embedding state
205
+ */
206
+ export function getOrCreateBatchEmbeddingState(provider) {
207
+ const existing = batchEmbeddingStates.get(provider);
208
+ if (existing) {
209
+ return existing;
210
+ }
211
+ const newState = {
212
+ currentBatchSize: 0,
213
+ batchesProcessed: 0,
214
+ failedBatches: 0,
215
+ requestsThisMinute: [],
216
+ };
217
+ batchEmbeddingStates.set(provider, newState);
218
+ return newState;
219
+ }
220
+ /**
221
+ * Check if batch embedding is within rate limits
222
+ */
223
+ export function checkBatchEmbeddingRateLimit(provider, config = DEFAULT_BATCH_EMBEDDING_CONFIG) {
224
+ if (!config.enabled) {
225
+ return { allowed: true };
226
+ }
227
+ const state = getOrCreateBatchEmbeddingState(provider);
228
+ const now = Date.now();
229
+ const windowMs = 60_000; // 1 minute
230
+ // Clean old requests
231
+ state.requestsThisMinute = state.requestsThisMinute.filter((ts) => now - ts < windowMs);
232
+ if (state.requestsThisMinute.length >= config.rateLimitPerMinute) {
233
+ const oldestRequest = state.requestsThisMinute[0];
234
+ const retryAfterMs = windowMs - (now - oldestRequest);
235
+ return { allowed: false, retryAfterMs };
236
+ }
237
+ state.requestsThisMinute.push(now);
238
+ return { allowed: true };
239
+ }
@@ -9,15 +9,13 @@ import { getMachineDisplayName } from "../infra/machine-name.js";
9
9
  import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
10
10
  import { loadConfig } from "../config/config.js";
11
11
  import { resolveBrowserConfig } from "../browser/config.js";
12
- import { createBrowserControlContext, startBrowserControlServiceFromConfig, } from "../browser/control-service.js";
13
- import { createBrowserRouteDispatcher } from "../browser/routes/dispatcher.js";
12
+ import { startBrowserControlServiceFromConfig, } from "../browser/control-service.js";
14
13
  import { detectMime } from "../media/mime.js";
15
14
  import { resolveAgentConfig } from "../agents/agent-scope.js";
16
15
  import { ensurePoolbotCliOnPath } from "../infra/path-env.js";
17
16
  import { VERSION } from "../version.js";
18
17
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
19
18
  import { ensureNodeHostConfig, saveNodeHostConfig } from "./config.js";
20
- import { withTimeout } from "./with-timeout.js";
21
19
  import { GatewayClient } from "../gateway/client.js";
22
20
  function resolveExecSecurity(value) {
23
21
  return value === "deny" || value === "allowlist" || value === "full" ? value : "allowlist";
@@ -385,6 +383,11 @@ export async function runNodeHost(opts) {
385
383
  "system.which",
386
384
  "system.execApprovals.get",
387
385
  "system.execApprovals.set",
386
+ "file.read",
387
+ "file.write",
388
+ "file.exists",
389
+ "file.delete",
390
+ "file.list",
388
391
  ...(browserProxyEnabled ? ["browser.proxy"] : []),
389
392
  ],
390
393
  pathEnv,
@@ -508,101 +511,165 @@ async function handleInvoke(frame, client, skillBins) {
508
511
  return;
509
512
  }
510
513
  if (command === "browser.proxy") {
514
+ // ... existing code ...
515
+ return;
516
+ }
517
+ // File operation handlers for nodes_file tool
518
+ if (command === "file.read") {
511
519
  try {
512
520
  const params = decodeParams(frame.paramsJSON);
513
- const pathValue = typeof params.path === "string" ? params.path.trim() : "";
514
- if (!pathValue) {
521
+ const filePath = String(params.path ?? "").trim();
522
+ if (!filePath) {
515
523
  throw new Error("INVALID_REQUEST: path required");
516
524
  }
517
- const proxyConfig = resolveBrowserProxyConfig();
518
- if (!proxyConfig.enabled) {
519
- throw new Error("UNAVAILABLE: node browser proxy disabled");
520
- }
521
- await ensureBrowserControlService();
522
- const cfg = loadConfig();
523
- const resolved = resolveBrowserConfig(cfg.browser, cfg);
524
- const requestedProfile = typeof params.profile === "string" ? params.profile.trim() : "";
525
- const allowedProfiles = proxyConfig.allowProfiles;
526
- if (allowedProfiles.length > 0) {
527
- if (pathValue !== "/profiles") {
528
- const profileToCheck = requestedProfile || resolved.defaultProfile;
529
- if (!isProfileAllowed({ allowProfiles: allowedProfiles, profile: profileToCheck })) {
530
- throw new Error("INVALID_REQUEST: browser profile not allowed");
531
- }
532
- }
533
- else if (requestedProfile) {
534
- if (!isProfileAllowed({ allowProfiles: allowedProfiles, profile: requestedProfile })) {
535
- throw new Error("INVALID_REQUEST: browser profile not allowed");
536
- }
537
- }
525
+ const encoding = params.encoding === "base64" ? "base64" : "utf8";
526
+ const content = await fsPromises.readFile(filePath, encoding);
527
+ const stat = await fsPromises.stat(filePath);
528
+ const payload = {
529
+ content,
530
+ encoding,
531
+ size: stat.size,
532
+ mtime: stat.mtime.toISOString(),
533
+ };
534
+ await sendInvokeResult(client, frame, {
535
+ ok: true,
536
+ payloadJSON: JSON.stringify(payload),
537
+ });
538
+ }
539
+ catch (err) {
540
+ const code = err?.code === "ENOENT" ? "NOT_FOUND" : "INVALID_REQUEST";
541
+ await sendInvokeResult(client, frame, {
542
+ ok: false,
543
+ error: { code, message: String(err) },
544
+ });
545
+ }
546
+ return;
547
+ }
548
+ if (command === "file.write") {
549
+ try {
550
+ const params = decodeParams(frame.paramsJSON);
551
+ const filePath = String(params.path ?? "").trim();
552
+ if (!filePath) {
553
+ throw new Error("INVALID_REQUEST: path required");
538
554
  }
539
- const method = typeof params.method === "string" ? params.method.toUpperCase() : "GET";
540
- const path = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
541
- const body = params.body;
542
- const query = {};
543
- if (requestedProfile) {
544
- query.profile = requestedProfile;
555
+ if (typeof params.content !== "string") {
556
+ throw new Error("INVALID_REQUEST: content required");
545
557
  }
546
- const rawQuery = params.query ?? {};
547
- for (const [key, value] of Object.entries(rawQuery)) {
548
- if (value === undefined || value === null)
549
- continue;
550
- query[key] = typeof value === "string" ? value : String(value);
558
+ const encoding = params.encoding === "base64" ? "base64" : "utf8";
559
+ const flag = params.append ? "a" : "w";
560
+ if (params.createDirs) {
561
+ const dir = path.dirname(filePath);
562
+ await fsPromises.mkdir(dir, { recursive: true });
551
563
  }
552
- const dispatcher = createBrowserRouteDispatcher(createBrowserControlContext());
553
- const response = await withTimeout((_signal) => dispatcher.dispatch({
554
- method: method === "DELETE" ? "DELETE" : method === "POST" ? "POST" : "GET",
555
- path,
556
- query,
557
- body,
558
- }), params.timeoutMs, "browser proxy request");
559
- if (response.status >= 400) {
560
- const message = response.body && typeof response.body === "object" && "error" in response.body
561
- ? String(response.body.error)
562
- : `HTTP ${response.status}`;
563
- throw new Error(message);
564
+ await fsPromises.writeFile(filePath, params.content, { encoding, flag });
565
+ const stat = await fsPromises.stat(filePath);
566
+ const payload = {
567
+ path: filePath,
568
+ size: stat.size,
569
+ mtime: stat.mtime.toISOString(),
570
+ };
571
+ await sendInvokeResult(client, frame, {
572
+ ok: true,
573
+ payloadJSON: JSON.stringify(payload),
574
+ });
575
+ }
576
+ catch (err) {
577
+ await sendInvokeResult(client, frame, {
578
+ ok: false,
579
+ error: { code: "INVALID_REQUEST", message: String(err) },
580
+ });
581
+ }
582
+ return;
583
+ }
584
+ if (command === "file.exists") {
585
+ try {
586
+ const params = decodeParams(frame.paramsJSON);
587
+ const filePath = String(params.path ?? "").trim();
588
+ if (!filePath) {
589
+ throw new Error("INVALID_REQUEST: path required");
564
590
  }
565
- const result = response.body;
566
- if (allowedProfiles.length > 0 && path === "/profiles") {
567
- const obj = typeof result === "object" && result !== null ? result : {};
568
- const profiles = Array.isArray(obj.profiles) ? obj.profiles : [];
569
- obj.profiles = profiles.filter((entry) => {
570
- if (!entry || typeof entry !== "object")
571
- return false;
572
- const name = entry.name;
573
- return typeof name === "string" && allowedProfiles.includes(name);
574
- });
591
+ const stat = await fsPromises.stat(filePath).catch(() => null);
592
+ const payload = {
593
+ exists: stat !== null,
594
+ isFile: stat?.isFile() ?? false,
595
+ isDirectory: stat?.isDirectory() ?? false,
596
+ size: stat?.size ?? 0,
597
+ mtime: stat?.mtime?.toISOString() ?? null,
598
+ };
599
+ await sendInvokeResult(client, frame, {
600
+ ok: true,
601
+ payloadJSON: JSON.stringify(payload),
602
+ });
603
+ }
604
+ catch (err) {
605
+ await sendInvokeResult(client, frame, {
606
+ ok: false,
607
+ error: { code: "INVALID_REQUEST", message: String(err) },
608
+ });
609
+ }
610
+ return;
611
+ }
612
+ if (command === "file.delete") {
613
+ try {
614
+ const params = decodeParams(frame.paramsJSON);
615
+ const filePath = String(params.path ?? "").trim();
616
+ if (!filePath) {
617
+ throw new Error("INVALID_REQUEST: path required");
575
618
  }
576
- let files;
577
- const paths = collectBrowserProxyPaths(result);
578
- if (paths.length > 0) {
579
- const loaded = await Promise.all(paths.map(async (p) => {
580
- try {
581
- const file = await readBrowserProxyFile(p);
582
- if (!file) {
583
- throw new Error("file not found");
584
- }
585
- return file;
586
- }
587
- catch (err) {
588
- throw new Error(`browser proxy file read failed for ${p}: ${String(err)}`, {
589
- cause: err,
590
- });
619
+ await fsPromises.unlink(filePath);
620
+ const payload = { deleted: true, path: filePath };
621
+ await sendInvokeResult(client, frame, {
622
+ ok: true,
623
+ payloadJSON: JSON.stringify(payload),
624
+ });
625
+ }
626
+ catch (err) {
627
+ const code = err?.code === "ENOENT" ? "NOT_FOUND" : "INVALID_REQUEST";
628
+ await sendInvokeResult(client, frame, {
629
+ ok: false,
630
+ error: { code, message: String(err) },
631
+ });
632
+ }
633
+ return;
634
+ }
635
+ if (command === "file.list") {
636
+ try {
637
+ const params = decodeParams(frame.paramsJSON);
638
+ const dirPath = String(params.path ?? "").trim() || ".";
639
+ const recursive = params.recursive === true;
640
+ async function listDir(dir, basePath) {
641
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
642
+ const results = [];
643
+ for (const entry of entries) {
644
+ const fullPath = path.join(dir, entry.name);
645
+ const relativePath = path.join(basePath, entry.name);
646
+ const stat = await fsPromises.stat(fullPath).catch(() => null);
647
+ results.push({
648
+ name: entry.name,
649
+ path: relativePath,
650
+ isFile: entry.isFile(),
651
+ isDirectory: entry.isDirectory(),
652
+ size: stat?.size ?? 0,
653
+ });
654
+ if (recursive && entry.isDirectory()) {
655
+ const subEntries = await listDir(fullPath, relativePath);
656
+ results.push(...subEntries);
591
657
  }
592
- }));
593
- if (loaded.length > 0)
594
- files = loaded;
658
+ }
659
+ return results;
595
660
  }
596
- const payload = files ? { result, files } : { result };
661
+ const entries = await listDir(dirPath, "");
662
+ const payload = { path: dirPath, entries, recursive };
597
663
  await sendInvokeResult(client, frame, {
598
664
  ok: true,
599
665
  payloadJSON: JSON.stringify(payload),
600
666
  });
601
667
  }
602
668
  catch (err) {
669
+ const code = err?.code === "ENOENT" ? "NOT_FOUND" : "INVALID_REQUEST";
603
670
  await sendInvokeResult(client, frame, {
604
671
  ok: false,
605
- error: { code: "INVALID_REQUEST", message: String(err) },
672
+ error: { code, message: String(err) },
606
673
  });
607
674
  }
608
675
  return;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Prototype Pollution Prevention Utilities
3
+ *
4
+ * Prevents prototype pollution attacks via object merges, JSON parsing,
5
+ * and user-supplied property assignments.
6
+ *
7
+ * OpenClaw #30951, #25827
8
+ */
9
+ // Dangerous property names that could lead to prototype pollution
10
+ const DANGEROUS_KEYS = new Set([
11
+ "__proto__",
12
+ "constructor",
13
+ "prototype",
14
+ "__defineGetter__",
15
+ "__defineSetter__",
16
+ "__lookupGetter__",
17
+ "__lookupSetter__",
18
+ ]);
19
+ /**
20
+ * Check if a key is dangerous and could lead to prototype pollution.
21
+ */
22
+ export function isDangerousKey(key) {
23
+ return DANGEROUS_KEYS.has(key);
24
+ }
25
+ /**
26
+ * Safely merge two objects while preventing prototype pollution.
27
+ * Rejects any object containing dangerous keys at any nesting level.
28
+ */
29
+ export function safeMerge(target, source, options) {
30
+ const allowlist = new Set(options?.allowlist ?? []);
31
+ const deep = options?.deep ?? true;
32
+ function validateObject(obj, path = []) {
33
+ if (obj === null || typeof obj !== "object") {
34
+ return;
35
+ }
36
+ if (Array.isArray(obj)) {
37
+ obj.forEach((item, index) => validateObject(item, [...path, `[${index}]`]));
38
+ return;
39
+ }
40
+ const keys = Object.keys(obj);
41
+ for (const key of keys) {
42
+ if (isDangerousKey(key) && !allowlist.has(key)) {
43
+ throw new Error(`Prototype pollution attempt detected: dangerous key "${key}" at path ${path.join(".")}`);
44
+ }
45
+ if (deep) {
46
+ validateObject(obj[key], [...path, key]);
47
+ }
48
+ }
49
+ }
50
+ validateObject(source);
51
+ // Use Object.assign for shallow merge or implement deep merge
52
+ if (!deep) {
53
+ return Object.assign({}, target, source);
54
+ }
55
+ // Deep merge with safety checks
56
+ const result = { ...target };
57
+ for (const key of Object.keys(source)) {
58
+ const sourceValue = source[key];
59
+ const targetValue = result[key];
60
+ if (sourceValue &&
61
+ typeof sourceValue === "object" &&
62
+ !Array.isArray(sourceValue) &&
63
+ targetValue &&
64
+ typeof targetValue === "object" &&
65
+ !Array.isArray(targetValue)) {
66
+ result[key] = safeMerge(targetValue, sourceValue, { deep: true });
67
+ }
68
+ else {
69
+ result[key] = sourceValue;
70
+ }
71
+ }
72
+ return result;
73
+ }
74
+ /**
75
+ * Parse JSON safely, rejecting payloads with dangerous keys.
76
+ */
77
+ export function safeJsonParse(text, reviver) {
78
+ const parsed = JSON.parse(text, reviver);
79
+ function validate(obj) {
80
+ if (obj === null || typeof obj !== "object") {
81
+ return;
82
+ }
83
+ if (Array.isArray(obj)) {
84
+ obj.forEach(validate);
85
+ return;
86
+ }
87
+ for (const key of Object.keys(obj)) {
88
+ if (isDangerousKey(key)) {
89
+ throw new Error(`Prototype pollution attempt detected in JSON: dangerous key "${key}"`);
90
+ }
91
+ validate(obj[key]);
92
+ }
93
+ }
94
+ validate(parsed);
95
+ return parsed;
96
+ }
97
+ /**
98
+ * Sanitize an object by removing dangerous keys.
99
+ * Use this when you need to preserve as much data as possible while removing threats.
100
+ */
101
+ export function sanitizeObject(obj, path = []) {
102
+ if (obj === null || typeof obj !== "object") {
103
+ return obj;
104
+ }
105
+ if (Array.isArray(obj)) {
106
+ return obj.map((item, index) => sanitizeObject(item, [...path, `[${index}]`]));
107
+ }
108
+ const result = {};
109
+ for (const [key, value] of Object.entries(obj)) {
110
+ if (isDangerousKey(key)) {
111
+ continue; // Skip dangerous keys
112
+ }
113
+ result[key] = sanitizeObject(value, [...path, key]);
114
+ }
115
+ return result;
116
+ }
117
+ /**
118
+ * Validate that configuration object doesn't contain dangerous keys.
119
+ * Throws with detailed error message including the path to the dangerous key.
120
+ */
121
+ export function validateConfigSafe(obj, context = "config") {
122
+ function walk(current, path) {
123
+ if (current === null || typeof current !== "object") {
124
+ return;
125
+ }
126
+ if (Array.isArray(current)) {
127
+ current.forEach((item, index) => walk(item, [...path, `[${index}]`]));
128
+ return;
129
+ }
130
+ for (const key of Object.keys(current)) {
131
+ const currentPath = [...path, key];
132
+ const fullPath = `${context}${currentPath.join(".")}`;
133
+ if (isDangerousKey(key)) {
134
+ throw new Error(`Security violation: dangerous key "${key}" found at ${fullPath}. ` +
135
+ `This could lead to prototype pollution attacks.`);
136
+ }
137
+ walk(current[key], currentPath);
138
+ }
139
+ }
140
+ walk(obj, []);
141
+ }