@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.
- package/CHANGELOG.md +121 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/checkpoint-manager.js +291 -0
- package/dist/agents/poolbot-tools.js +5 -0
- package/dist/agents/subagent-announce-reliability.js +160 -0
- package/dist/agents/tool-result-truncation.js +299 -0
- package/dist/agents/tools/nodes-file-tool.js +197 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/config-cli.js +60 -0
- package/dist/cron/cron-improvements.js +195 -0
- package/dist/discord/discord-improvements.js +167 -0
- package/dist/gateway/auth-rate-limit.js +19 -0
- package/dist/gateway/auth.js +41 -0
- package/dist/gateway/gateway-improvements.js +294 -0
- package/dist/gateway/node-command-policy.js +7 -2
- package/dist/infra/net/ssrf.js +15 -2
- package/dist/infra/shell-security.js +201 -0
- package/dist/memory/memory-improvements.js +239 -0
- package/dist/node-host/runner.js +146 -79
- package/dist/security/prototype-pollution.js +141 -0
- package/dist/security/webhook-security.js +253 -0
- package/dist/shared/net/ip.js +52 -1
- package/dist/slack/slack-improvements.js +225 -0
- package/dist/telegram/telegram-improvements.js +220 -0
- package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
- package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
- package/docs/competitive-analysis.md +421 -0
- package/docs/implementation-analysis.md +393 -0
- package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
- package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
- package/docs/refactor/plugin-development-guide.md +281 -0
- package/extensions/agency-agents/README.md +301 -0
- package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
- package/extensions/agency-agents/agents/README.md +602 -0
- package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
- package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
- package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
- package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
- package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
- package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
- package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
- package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
- package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
- package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
- package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
- package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
- package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
- package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
- package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
- package/extensions/agency-agents/agents/examples/README.md +48 -0
- package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
- package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
- package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
- package/extensions/agency-agents/agents/integrations/README.md +117 -0
- package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
- package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
- package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
- package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
- package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
- package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
- package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
- package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
- package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
- package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
- package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
- package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
- package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
- package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
- package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
- package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
- package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
- package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
- package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
- package/extensions/agency-agents/agents/scripts/install.sh +465 -0
- package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
- package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
- package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
- package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
- package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
- package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
- package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
- package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
- package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
- package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
- package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
- package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
- package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
- package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
- package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
- package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
- package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
- package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
- package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
- package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
- package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
- package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
- package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
- package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
- package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
- package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
- package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
- package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
- package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
- package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
- package/extensions/agency-agents/index.ts +733 -0
- package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +21 -0
- package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
- package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
- package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/agency-agents/package.json +25 -0
- package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
- package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
- package/extensions/agency-agents/src/types.ts +147 -0
- package/extensions/agency-agents/vitest.config.ts +8 -0
- package/extensions/hexstrike-ai/README.md +98 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
- package/extensions/hexstrike-ai/package.json +29 -0
- package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
- package/extensions/hexstrike-ai/src/client.ts +91 -0
- package/extensions/hexstrike-ai/src/index.ts +170 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
- package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
- package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
- package/extensions/hexstrike-ai/tsconfig.json +20 -0
- package/extensions/hexstrike-bridge/package.json +1 -1
- package/extensions/hexstrike-bridge/poolbot.plugin.json +23 -0
- package/extensions/mcp-server/poolbot.plugin.json +10 -0
- package/extensions/page-agent/README.md +159 -0
- package/extensions/page-agent/index.ts +595 -0
- package/extensions/page-agent/node_modules/.bin/jiti +21 -0
- package/extensions/page-agent/node_modules/.bin/playwright +21 -0
- package/extensions/page-agent/node_modules/.bin/tsc +21 -0
- package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
- package/extensions/page-agent/node_modules/.bin/tsx +21 -0
- package/extensions/page-agent/node_modules/.bin/vitest +21 -0
- package/extensions/page-agent/node_modules/.bin/yaml +21 -0
- package/extensions/page-agent/package.json +43 -0
- package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
- package/extensions/page-agent/src/PageAgentService.ts +636 -0
- package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
- package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
- package/extensions/page-agent/src/index.ts +20 -0
- package/extensions/page-agent/src/tools.test.ts +231 -0
- package/extensions/page-agent/src/tools.ts +167 -0
- package/extensions/page-agent/src/types.ts +198 -0
- package/extensions/template/README.md +101 -0
- package/extensions/template/index.ts +38 -0
- package/extensions/template/package.json +15 -0
- package/extensions/template/poolbot.plugin.json +10 -0
- package/extensions/xyops/README.md +227 -0
- package/extensions/xyops/index.ts +342 -0
- package/extensions/xyops/node_modules/.bin/jiti +21 -0
- package/extensions/xyops/node_modules/.bin/tsc +21 -0
- package/extensions/xyops/node_modules/.bin/tsserver +21 -0
- package/extensions/xyops/node_modules/.bin/tsx +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +21 -0
- package/extensions/xyops/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/xyops/package.json +39 -0
- package/extensions/xyops/src/client.test.ts +467 -0
- package/extensions/xyops/src/client.ts +157 -0
- package/extensions/xyops/src/types.ts +147 -0
- package/extensions/xyops/vitest.config.ts +8 -0
- package/package.json +1 -1
- package/extensions/mavalie/README.md +0 -97
- package/extensions/mavalie/package.json +0 -15
- 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
|
+
}
|
package/dist/node-host/runner.js
CHANGED
|
@@ -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 {
|
|
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
|
|
514
|
-
if (!
|
|
521
|
+
const filePath = String(params.path ?? "").trim();
|
|
522
|
+
if (!filePath) {
|
|
515
523
|
throw new Error("INVALID_REQUEST: path required");
|
|
516
524
|
}
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
540
|
-
|
|
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
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
path,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
594
|
-
files = loaded;
|
|
658
|
+
}
|
|
659
|
+
return results;
|
|
595
660
|
}
|
|
596
|
-
const
|
|
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
|
|
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
|
+
}
|