@poolzin/pool-bot 2026.3.13 → 2026.3.15

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 (186) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/agents/checkpoint-manager.js +291 -0
  3. package/dist/agents/poolbot-tools.js +5 -0
  4. package/dist/agents/subagent-announce-reliability.js +160 -0
  5. package/dist/agents/tool-result-truncation.js +299 -0
  6. package/dist/agents/tools/nodes-file-tool.js +197 -0
  7. package/dist/build-info.json +3 -3
  8. package/dist/cli/config-cli.js +60 -0
  9. package/dist/cron/cron-improvements.js +195 -0
  10. package/dist/discord/discord-improvements.js +167 -0
  11. package/dist/gateway/auth-rate-limit.js +19 -0
  12. package/dist/gateway/auth.js +41 -0
  13. package/dist/gateway/gateway-improvements.js +294 -0
  14. package/dist/gateway/node-command-policy.js +7 -2
  15. package/dist/infra/net/ssrf.js +15 -2
  16. package/dist/infra/shell-security.js +201 -0
  17. package/dist/memory/memory-improvements.js +239 -0
  18. package/dist/node-host/runner.js +146 -79
  19. package/dist/security/prototype-pollution.js +141 -0
  20. package/dist/security/webhook-security.js +253 -0
  21. package/dist/shared/net/ip.js +52 -1
  22. package/dist/slack/slack-improvements.js +225 -0
  23. package/dist/telegram/telegram-improvements.js +220 -0
  24. package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
  25. package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
  26. package/docs/competitive-analysis.md +421 -0
  27. package/docs/implementation-analysis.md +393 -0
  28. package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
  29. package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
  30. package/extensions/agency-agents/README.md +301 -0
  31. package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
  32. package/extensions/agency-agents/agents/README.md +602 -0
  33. package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
  34. package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
  35. package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
  36. package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
  37. package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
  38. package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
  39. package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
  40. package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
  41. package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
  42. package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
  43. package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
  44. package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
  45. package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
  46. package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
  47. package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
  48. package/extensions/agency-agents/agents/examples/README.md +48 -0
  49. package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
  50. package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
  51. package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
  52. package/extensions/agency-agents/agents/integrations/README.md +117 -0
  53. package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
  54. package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
  55. package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
  56. package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
  57. package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
  58. package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
  59. package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
  60. package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
  61. package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
  62. package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
  63. package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
  64. package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
  65. package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
  66. package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
  67. package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
  68. package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
  69. package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
  70. package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
  71. package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
  72. package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
  73. package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
  74. package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
  75. package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
  76. package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
  77. package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
  78. package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
  79. package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
  80. package/extensions/agency-agents/agents/scripts/install.sh +465 -0
  81. package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
  82. package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
  83. package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
  84. package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
  85. package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
  86. package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
  87. package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
  88. package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
  89. package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
  90. package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
  91. package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
  92. package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
  93. package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
  94. package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
  95. package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
  96. package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
  97. package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
  98. package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
  99. package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
  100. package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
  101. package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
  102. package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
  103. package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
  104. package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
  105. package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
  106. package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
  107. package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
  108. package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
  109. package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
  110. package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
  111. package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
  112. package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
  113. package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
  114. package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
  115. package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
  116. package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
  117. package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
  118. package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
  119. package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
  120. package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
  121. package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
  122. package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
  123. package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
  124. package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
  125. package/extensions/agency-agents/index.ts +733 -0
  126. package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
  127. package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
  128. package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
  129. package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
  130. package/extensions/agency-agents/node_modules/.bin/vite +21 -0
  131. package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
  132. package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
  133. package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  134. package/extensions/agency-agents/package.json +25 -0
  135. package/extensions/agency-agents/poolbot.plugin.json +11 -0
  136. package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
  137. package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
  138. package/extensions/agency-agents/src/types.ts +147 -0
  139. package/extensions/agency-agents/vitest.config.ts +8 -0
  140. package/extensions/hexstrike-ai/README.md +98 -0
  141. package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
  142. package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
  143. package/extensions/hexstrike-ai/package.json +29 -0
  144. package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
  145. package/extensions/hexstrike-ai/src/client.ts +91 -0
  146. package/extensions/hexstrike-ai/src/index.ts +170 -0
  147. package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
  148. package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
  149. package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
  150. package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
  151. package/extensions/hexstrike-ai/tsconfig.json +20 -0
  152. package/extensions/page-agent/README.md +159 -0
  153. package/extensions/page-agent/index.ts +595 -0
  154. package/extensions/page-agent/node_modules/.bin/jiti +21 -0
  155. package/extensions/page-agent/node_modules/.bin/playwright +21 -0
  156. package/extensions/page-agent/node_modules/.bin/tsc +21 -0
  157. package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
  158. package/extensions/page-agent/node_modules/.bin/tsx +21 -0
  159. package/extensions/page-agent/node_modules/.bin/vitest +21 -0
  160. package/extensions/page-agent/node_modules/.bin/yaml +21 -0
  161. package/extensions/page-agent/package.json +43 -0
  162. package/extensions/page-agent/poolbot.plugin.json +24 -0
  163. package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
  164. package/extensions/page-agent/src/PageAgentService.ts +636 -0
  165. package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
  166. package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
  167. package/extensions/page-agent/src/index.ts +20 -0
  168. package/extensions/page-agent/src/tools.test.ts +231 -0
  169. package/extensions/page-agent/src/tools.ts +167 -0
  170. package/extensions/page-agent/src/types.ts +198 -0
  171. package/extensions/xyops/README.md +227 -0
  172. package/extensions/xyops/index.ts +342 -0
  173. package/extensions/xyops/node_modules/.bin/jiti +21 -0
  174. package/extensions/xyops/node_modules/.bin/tsc +21 -0
  175. package/extensions/xyops/node_modules/.bin/tsserver +21 -0
  176. package/extensions/xyops/node_modules/.bin/tsx +21 -0
  177. package/extensions/xyops/node_modules/.bin/vitest +21 -0
  178. package/extensions/xyops/node_modules/.bin/yaml +21 -0
  179. package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  180. package/extensions/xyops/package.json +39 -0
  181. package/extensions/xyops/poolbot.plugin.json +21 -0
  182. package/extensions/xyops/src/client.test.ts +467 -0
  183. package/extensions/xyops/src/client.ts +157 -0
  184. package/extensions/xyops/src/types.ts +147 -0
  185. package/extensions/xyops/vitest.config.ts +8 -0
  186. package/package.json +1 -1
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Tool Result Truncation & Context Pruning
3
+ *
4
+ * Implements:
5
+ * - Head+tail truncation (preserves diagnostics)
6
+ * - Context pruning for tool-results during soft-trim
7
+ * - Preserve recent turns verbatim
8
+ * - Multimodal + tool-result hints
9
+ *
10
+ * OpenClaw #32384, #30951
11
+ */
12
+ /**
13
+ * Default truncation config
14
+ * Head+tail approach preserves important diagnostics
15
+ */
16
+ export const DEFAULT_TRUNCATION_CONFIG = {
17
+ maxToolResultChars: 50_000,
18
+ headChars: 10_000,
19
+ tailChars: 10_000,
20
+ minCharsForTruncation: 20_000,
21
+ preserveErrors: true,
22
+ };
23
+ /**
24
+ * Truncate tool result content with head+tail strategy
25
+ * OpenClaw #32384 - preserves diagnostic information
26
+ */
27
+ export function truncateToolResult(content, config = DEFAULT_TRUNCATION_CONFIG) {
28
+ const originalLength = content.length;
29
+ // Don't truncate if under threshold
30
+ if (originalLength <= config.minCharsForTruncation) {
31
+ return { truncated: content, wasTruncated: false, originalLength };
32
+ }
33
+ // Don't truncate if under max
34
+ if (originalLength <= config.maxToolResultChars) {
35
+ return { truncated: content, wasTruncated: false, originalLength };
36
+ }
37
+ // Head+tail truncation
38
+ const head = content.slice(0, config.headChars);
39
+ const tail = content.slice(-config.tailChars);
40
+ const omitted = originalLength - config.headChars - config.tailChars;
41
+ const truncated = `${head}\n\n[...${omitted.toLocaleString()} characters omitted...]\n\n${tail}`;
42
+ return { truncated, wasTruncated: true, originalLength };
43
+ }
44
+ /**
45
+ * Check if a message is a tool result
46
+ */
47
+ export function isToolResultMessage(message) {
48
+ return message.role === "toolResult";
49
+ }
50
+ /**
51
+ * Check if a tool result contains an error
52
+ */
53
+ export function isToolResultError(message) {
54
+ if (!isToolResultMessage(message)) {
55
+ return false;
56
+ }
57
+ return message.isError === true;
58
+ }
59
+ /**
60
+ * Extract tool result content from message
61
+ */
62
+ export function extractToolResultContent(message) {
63
+ const content = message.content;
64
+ if (typeof content === "string") {
65
+ return content;
66
+ }
67
+ if (content && typeof content === "object") {
68
+ try {
69
+ return JSON.stringify(content);
70
+ }
71
+ catch {
72
+ return JSON.stringify({ error: "Failed to stringify content" });
73
+ }
74
+ }
75
+ return "";
76
+ }
77
+ /**
78
+ * Apply truncation to tool result messages
79
+ * Preserves errors by default
80
+ */
81
+ export function truncateToolResultMessages(messages, config = DEFAULT_TRUNCATION_CONFIG) {
82
+ return messages.map((message) => {
83
+ if (!isToolResultMessage(message)) {
84
+ return message;
85
+ }
86
+ // Preserve errors if configured
87
+ if (config.preserveErrors && isToolResultError(message)) {
88
+ return message;
89
+ }
90
+ const content = extractToolResultContent(message);
91
+ const { truncated, wasTruncated } = truncateToolResult(content, config);
92
+ if (!wasTruncated) {
93
+ return message;
94
+ }
95
+ // Create new message with truncated content
96
+ return {
97
+ ...message,
98
+ content: truncated,
99
+ };
100
+ });
101
+ }
102
+ /**
103
+ * Default context pruning config
104
+ */
105
+ export const DEFAULT_CONTEXT_PRUNING_CONFIG = {
106
+ keepRecentTurns: 10,
107
+ pruneToolResultsOlderThan: 20,
108
+ keepErrorToolResults: true,
109
+ keepMultimodalContent: true,
110
+ compressToolResults: false,
111
+ };
112
+ /**
113
+ * Count turns in message history
114
+ * A turn = user message + assistant response
115
+ */
116
+ export function countTurns(messages) {
117
+ let turns = 0;
118
+ let hasUserMessage = false;
119
+ for (const message of messages) {
120
+ const role = message.role;
121
+ if (role === "user") {
122
+ hasUserMessage = true;
123
+ }
124
+ else if (role === "assistant" && hasUserMessage) {
125
+ turns += 1;
126
+ hasUserMessage = false;
127
+ }
128
+ }
129
+ // Count incomplete turn if there's a user message without response
130
+ if (hasUserMessage) {
131
+ turns += 1;
132
+ }
133
+ return turns;
134
+ }
135
+ /**
136
+ * Prune context for soft-trim while preserving important information
137
+ * OpenClaw #30951 - maintains diagnostic quality
138
+ */
139
+ export function pruneContextForSoftTrim(messages, config = DEFAULT_CONTEXT_PRUNING_CONFIG) {
140
+ let currentTurn = 0;
141
+ // Process messages in reverse to identify recent turns
142
+ const reversedMessages = [...messages].reverse();
143
+ const keptMessages = [];
144
+ for (const message of reversedMessages) {
145
+ const role = message.role;
146
+ const isToolResult = isToolResultMessage(message);
147
+ const isToolError = isToolResultError(message);
148
+ const hasMultimodal = hasMultimodalContent(message);
149
+ // Always keep user and assistant messages in recent turns
150
+ if (role === "user" || role === "assistant") {
151
+ if (currentTurn < config.keepRecentTurns) {
152
+ keptMessages.unshift(message);
153
+ if (role === "assistant") {
154
+ currentTurn += 1;
155
+ }
156
+ }
157
+ else {
158
+ // Older turns: selectively prune
159
+ if (!isToolResult) {
160
+ keptMessages.unshift(message);
161
+ }
162
+ else if (config.keepErrorToolResults && isToolError) {
163
+ keptMessages.unshift(message);
164
+ }
165
+ else if (config.keepMultimodalContent && hasMultimodal) {
166
+ keptMessages.unshift(message);
167
+ }
168
+ else if (config.compressToolResults) {
169
+ // Compress to summary instead of full content
170
+ const compressed = compressToolResult(message);
171
+ keptMessages.unshift(compressed);
172
+ }
173
+ }
174
+ continue;
175
+ }
176
+ // Tool results: apply pruning rules
177
+ if (isToolResult) {
178
+ if (currentTurn < config.keepRecentTurns) {
179
+ // Keep recent tool results
180
+ keptMessages.unshift(message);
181
+ }
182
+ else if (config.keepErrorToolResults && isToolError) {
183
+ // Always keep errors
184
+ keptMessages.unshift(message);
185
+ }
186
+ else if (config.keepMultimodalContent && hasMultimodal) {
187
+ // Keep multimodal content
188
+ keptMessages.unshift(message);
189
+ }
190
+ else if (config.compressToolResults) {
191
+ // Compress older tool results
192
+ const compressed = compressToolResult(message);
193
+ keptMessages.unshift(compressed);
194
+ }
195
+ // Otherwise: prune (don't add to keptMessages)
196
+ }
197
+ }
198
+ return keptMessages;
199
+ }
200
+ /**
201
+ * Check if a message has multimodal content (images, files, etc.)
202
+ */
203
+ export function hasMultimodalContent(message) {
204
+ const content = message.content;
205
+ if (!content) {
206
+ return false;
207
+ }
208
+ if (Array.isArray(content)) {
209
+ return content.some((item) => {
210
+ if (!item || typeof item !== "object") {
211
+ return false;
212
+ }
213
+ const record = item;
214
+ return (typeof record.image === "string" ||
215
+ typeof record.file === "string" ||
216
+ typeof record.data === "string" ||
217
+ typeof record.url === "string");
218
+ });
219
+ }
220
+ return false;
221
+ }
222
+ /**
223
+ * Compress tool result to summary (for context pruning)
224
+ */
225
+ export function compressToolResult(message) {
226
+ if (!isToolResultMessage(message)) {
227
+ return message;
228
+ }
229
+ const toolName = message.toolName;
230
+ const toolNameStr = typeof toolName === "string" && toolName.trim() ? toolName : "unknown";
231
+ const content = extractToolResultContent(message);
232
+ // Create summary instead of full content
233
+ const summary = `[Tool: ${toolNameStr}] - ${content.length.toLocaleString()} characters`;
234
+ return {
235
+ ...message,
236
+ content: summary,
237
+ };
238
+ }
239
+ /**
240
+ * Audit compaction quality
241
+ * OpenClaw #30951 - ensures compaction doesn't lose critical information
242
+ */
243
+ export function auditCompactionQuality(params) {
244
+ const { originalMessages, compactedMessages, summary } = params;
245
+ const issues = [];
246
+ const recommendations = [];
247
+ let score = 100;
248
+ // Check 1: Tool errors preserved
249
+ const originalErrors = originalMessages.filter(isToolResultError).length;
250
+ const compactedErrors = compactedMessages.filter(isToolResultError).length;
251
+ if (compactedErrors < originalErrors) {
252
+ issues.push(`Lost ${originalErrors - compactedErrors} tool error messages`);
253
+ recommendations.push("Preserve all tool errors during compaction");
254
+ score -= 20;
255
+ }
256
+ // Check 2: Recent turns preserved
257
+ const originalTurns = countTurns(originalMessages);
258
+ const compactedTurns = countTurns(compactedMessages);
259
+ if (compactedTurns < originalTurns * 0.3) {
260
+ issues.push("Compaction ratio too aggressive (<30% of turns preserved)");
261
+ recommendations.push("Keep at least 30% of recent turns verbatim");
262
+ score -= 15;
263
+ }
264
+ // Check 3: Summary quality
265
+ if (summary) {
266
+ if (summary.length < 50) {
267
+ issues.push("Summary too short (may lack detail)");
268
+ recommendations.push("Generate more detailed summaries (min 50 chars)");
269
+ score -= 10;
270
+ }
271
+ if (summary.length > 5000) {
272
+ issues.push("Summary too long (defeats compaction purpose)");
273
+ recommendations.push("Keep summaries concise (<5000 chars)");
274
+ score -= 5;
275
+ }
276
+ }
277
+ // Check 4: Multimodal content
278
+ const originalMultimodal = originalMessages.filter(hasMultimodalContent).length;
279
+ const compactedMultimodal = compactedMessages.filter(hasMultimodalContent).length;
280
+ if (compactedMultimodal < originalMultimodal) {
281
+ issues.push(`Lost ${originalMultimodal - compactedMultimodal} multimodal messages`);
282
+ recommendations.push("Preserve multimodal content during compaction");
283
+ score -= 15;
284
+ }
285
+ // Check 5: Message order
286
+ const lastOriginalRole = originalMessages[originalMessages.length - 1]?.role;
287
+ const lastCompactedRole = compactedMessages[compactedMessages.length - 1]?.role;
288
+ if (lastOriginalRole !== lastCompactedRole) {
289
+ issues.push("Message order may be incorrect (last message role mismatch)");
290
+ recommendations.push("Verify message ordering after compaction");
291
+ score -= 10;
292
+ }
293
+ return {
294
+ score: Math.max(0, score),
295
+ issues,
296
+ recommendations,
297
+ passed: score >= 70,
298
+ };
299
+ }
@@ -0,0 +1,197 @@
1
+ import crypto from "node:crypto";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
4
+ import { jsonResult, readStringParam } from "./common.js";
5
+ import { callGatewayTool, readGatewayCallOptions } from "./gateway.js";
6
+ import { resolveNodeId } from "./nodes-utils.js";
7
+ const FILE_TOOL_ACTIONS = ["read", "write", "exists", "delete", "list"];
8
+ const FileToolSchema = Type.Object({
9
+ action: stringEnum(FILE_TOOL_ACTIONS),
10
+ gatewayUrl: Type.Optional(Type.String()),
11
+ gatewayToken: Type.Optional(Type.String()),
12
+ timeoutMs: Type.Optional(Type.Number()),
13
+ node: Type.Optional(Type.String()),
14
+ // read/write/exists/delete
15
+ path: Type.Optional(Type.String()),
16
+ // read
17
+ encoding: optionalStringEnum(["utf8", "base64", "binary"]),
18
+ maxBytes: Type.Optional(Type.Number()),
19
+ // write
20
+ content: Type.Optional(Type.String()),
21
+ contentBase64: Type.Optional(Type.String()),
22
+ append: Type.Optional(Type.Boolean()),
23
+ createDirs: Type.Optional(Type.Boolean()),
24
+ // list
25
+ recursive: Type.Optional(Type.Boolean()),
26
+ includeHidden: Type.Optional(Type.Boolean()),
27
+ });
28
+ export function createNodesFileTool(_options) {
29
+ return {
30
+ label: "NodesFile",
31
+ name: "nodes_file",
32
+ description: "Read, write, and manage files on paired nodes (remote devices). Supports text and binary content with base64 encoding.",
33
+ parameters: FileToolSchema,
34
+ execute: async (_toolCallId, args) => {
35
+ const params = args;
36
+ const action = readStringParam(params, "action", { required: true });
37
+ const gatewayOpts = readGatewayCallOptions(params);
38
+ try {
39
+ switch (action) {
40
+ case "read": {
41
+ const node = readStringParam(params, "node", { required: true });
42
+ const path = readStringParam(params, "path", { required: true });
43
+ const nodeId = await resolveNodeId(gatewayOpts, node);
44
+ const encoding = typeof params.encoding === "string" ? params.encoding : "utf8";
45
+ const maxBytes = typeof params.maxBytes === "number" && Number.isFinite(params.maxBytes)
46
+ ? Math.max(1, Math.floor(params.maxBytes))
47
+ : undefined;
48
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
49
+ nodeId,
50
+ command: "file.read",
51
+ params: {
52
+ path,
53
+ encoding,
54
+ maxBytes,
55
+ },
56
+ idempotencyKey: crypto.randomUUID(),
57
+ });
58
+ const payload = raw?.payload ?? {};
59
+ if (typeof payload === "object" &&
60
+ payload !== null &&
61
+ "content" in payload &&
62
+ typeof payload.content === "string") {
63
+ const typedPayload = payload;
64
+ return jsonResult({
65
+ content: typedPayload.content,
66
+ encoding: typedPayload.encoding ?? encoding,
67
+ size: typedPayload.size,
68
+ truncated: typedPayload.truncated ?? false,
69
+ path,
70
+ });
71
+ }
72
+ throw new Error("Invalid response from file.read");
73
+ }
74
+ case "write": {
75
+ const node = readStringParam(params, "node", { required: true });
76
+ const path = readStringParam(params, "path", { required: true });
77
+ const nodeId = await resolveNodeId(gatewayOpts, node);
78
+ const content = typeof params.content === "string" ? params.content : undefined;
79
+ const contentBase64 = typeof params.contentBase64 === "string" ? params.contentBase64 : undefined;
80
+ if (content === undefined && contentBase64 === undefined) {
81
+ throw new Error("content or contentBase64 required for write action");
82
+ }
83
+ const append = typeof params.append === "boolean" ? params.append : false;
84
+ const createDirs = typeof params.createDirs === "boolean" ? params.createDirs : false;
85
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
86
+ nodeId,
87
+ command: "file.write",
88
+ params: {
89
+ path,
90
+ content,
91
+ contentBase64,
92
+ append,
93
+ createDirs,
94
+ },
95
+ idempotencyKey: crypto.randomUUID(),
96
+ });
97
+ const payload = raw?.payload ?? {};
98
+ if (typeof payload === "object" &&
99
+ payload !== null &&
100
+ "ok" in payload &&
101
+ payload.ok === true) {
102
+ const typedPayload = payload;
103
+ return jsonResult({
104
+ ok: true,
105
+ size: typedPayload.size,
106
+ path: typedPayload.path ?? path,
107
+ });
108
+ }
109
+ throw new Error("Invalid response from file.write");
110
+ }
111
+ case "exists": {
112
+ const node = readStringParam(params, "node", { required: true });
113
+ const path = readStringParam(params, "path", { required: true });
114
+ const nodeId = await resolveNodeId(gatewayOpts, node);
115
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
116
+ nodeId,
117
+ command: "file.exists",
118
+ params: { path },
119
+ idempotencyKey: crypto.randomUUID(),
120
+ });
121
+ const payload = raw?.payload ?? {};
122
+ if (typeof payload === "object" &&
123
+ payload !== null &&
124
+ "exists" in payload &&
125
+ typeof payload.exists === "boolean") {
126
+ const typedPayload = payload;
127
+ return jsonResult({
128
+ exists: typedPayload.exists,
129
+ isFile: typedPayload.isFile,
130
+ isDirectory: typedPayload.isDirectory,
131
+ size: typedPayload.size,
132
+ modified: typedPayload.modified,
133
+ path,
134
+ });
135
+ }
136
+ throw new Error("Invalid response from file.exists");
137
+ }
138
+ case "delete": {
139
+ const node = readStringParam(params, "node", { required: true });
140
+ const path = readStringParam(params, "path", { required: true });
141
+ const nodeId = await resolveNodeId(gatewayOpts, node);
142
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
143
+ nodeId,
144
+ command: "file.delete",
145
+ params: { path },
146
+ idempotencyKey: crypto.randomUUID(),
147
+ });
148
+ const payload = raw?.payload ?? {};
149
+ if (typeof payload === "object" &&
150
+ payload !== null &&
151
+ "ok" in payload &&
152
+ payload.ok === true) {
153
+ return jsonResult({ ok: true, path });
154
+ }
155
+ throw new Error("Invalid response from file.delete");
156
+ }
157
+ case "list": {
158
+ const node = readStringParam(params, "node", { required: true });
159
+ const path = readStringParam(params, "path", { required: true });
160
+ const nodeId = await resolveNodeId(gatewayOpts, node);
161
+ const recursive = typeof params.recursive === "boolean" ? params.recursive : false;
162
+ const includeHidden = typeof params.includeHidden === "boolean" ? params.includeHidden : false;
163
+ const raw = await callGatewayTool("node.invoke", gatewayOpts, {
164
+ nodeId,
165
+ command: "file.list",
166
+ params: { path, recursive, includeHidden },
167
+ idempotencyKey: crypto.randomUUID(),
168
+ });
169
+ const payload = raw?.payload ?? {};
170
+ if (typeof payload === "object" &&
171
+ payload !== null &&
172
+ "entries" in payload &&
173
+ Array.isArray(payload.entries)) {
174
+ const typedPayload = payload;
175
+ return jsonResult({
176
+ entries: typedPayload.entries,
177
+ path,
178
+ count: typedPayload.entries.length,
179
+ });
180
+ }
181
+ throw new Error("Invalid response from file.list");
182
+ }
183
+ default:
184
+ throw new Error(`Unknown action: ${action}`);
185
+ }
186
+ }
187
+ catch (err) {
188
+ const nodeLabel = typeof params.node === "string" && params.node.trim() ? params.node.trim() : "auto";
189
+ const gatewayLabel = gatewayOpts.gatewayUrl && gatewayOpts.gatewayUrl.trim()
190
+ ? gatewayOpts.gatewayUrl.trim()
191
+ : "default";
192
+ const message = err instanceof Error ? err.message : String(err);
193
+ throw new Error(`nodes_file (${action}) failed for node=${nodeLabel} via gateway=${gatewayLabel}: ${message}`, { cause: err });
194
+ }
195
+ },
196
+ };
197
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.3.13",
3
- "commit": "772f27d4ab21d89af08b22c846efea9a20f86f83",
4
- "builtAt": "2026-03-11T03:51:28.829Z"
2
+ "version": "2026.3.15",
3
+ "commit": "ecc8dbff78be4290bf7ebeb6b49f8d18b3ef414d",
4
+ "builtAt": "2026-03-12T05:59:35.973Z"
5
5
  }
@@ -364,4 +364,64 @@ export function registerConfigCli(program) {
364
364
  defaultRuntime.exit(1);
365
365
  }
366
366
  });
367
+ cmd
368
+ .command("validate")
369
+ .description("Validate the current config file without modifying it")
370
+ .option("--json", "Output validation result as JSON", false)
371
+ .action(async (opts) => {
372
+ try {
373
+ const snapshot = await readConfigFileSnapshot();
374
+ const validated = validateConfigObjectWithPlugins(snapshot.parsed);
375
+ if (opts.json) {
376
+ defaultRuntime.log(JSON.stringify({
377
+ valid: validated.ok,
378
+ issues: validated.ok ? [] : validated.issues,
379
+ warnings: validated.warnings,
380
+ legacyIssues: snapshot.legacyIssues,
381
+ }, null, 2));
382
+ defaultRuntime.exit(validated.ok ? 0 : 1);
383
+ return;
384
+ }
385
+ if (validated.ok) {
386
+ defaultRuntime.log(theme.success("✓ Config is valid"));
387
+ if (validated.warnings.length > 0) {
388
+ defaultRuntime.log(theme.warn(`\n${validated.warnings.length} warning(s):`));
389
+ for (const warning of validated.warnings) {
390
+ defaultRuntime.log(` - ${warning.path}: ${warning.message}`);
391
+ }
392
+ }
393
+ if (snapshot.legacyIssues.length > 0) {
394
+ defaultRuntime.log(theme.warn(`\n${snapshot.legacyIssues.length} legacy issue(s) detected:`));
395
+ for (const issue of snapshot.legacyIssues) {
396
+ defaultRuntime.log(` - ${issue}`);
397
+ }
398
+ defaultRuntime.log(theme.muted("\nRun 'poolbot config' wizard to migrate."));
399
+ }
400
+ defaultRuntime.exit(0);
401
+ }
402
+ else {
403
+ defaultRuntime.error(danger("✗ Config validation failed"));
404
+ for (const issue of validated.issues) {
405
+ defaultRuntime.error(` - ${issue.path}: ${issue.message}`);
406
+ }
407
+ if (validated.warnings.length > 0) {
408
+ defaultRuntime.log(theme.warn(`\n${validated.warnings.length} warning(s):`));
409
+ for (const warning of validated.warnings) {
410
+ defaultRuntime.log(` - ${warning.path}: ${warning.message}`);
411
+ }
412
+ }
413
+ if (snapshot.legacyIssues.length > 0) {
414
+ defaultRuntime.log(theme.warn(`\n${snapshot.legacyIssues.length} legacy issue(s) also detected:`));
415
+ for (const issue of snapshot.legacyIssues) {
416
+ defaultRuntime.log(` - ${issue}`);
417
+ }
418
+ }
419
+ defaultRuntime.exit(1);
420
+ }
421
+ }
422
+ catch (err) {
423
+ defaultRuntime.error(danger(String(err)));
424
+ defaultRuntime.exit(1);
425
+ }
426
+ });
367
427
  }