@poolzin/pool-bot 2026.3.9 → 2026.3.10
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 +24 -0
- package/README.md +147 -69
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +26 -77
- package/dist/agents/skills/security.js +1 -7
- package/dist/build-info.json +3 -3
- package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
- package/dist/cli/cron-cli/register.js +2 -0
- package/dist/cli/errors.js +187 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.maintenance.js +21 -0
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/swarm-cli/register.js +8 -0
- package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
- package/dist/cli/telemetry-cli/register.js +10 -0
- package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
- package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
- package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
- package/dist/commands/doctor-checks.js +498 -0
- package/dist/context-engine/index.js +1 -1
- package/dist/context-engine/legacy.js +1 -3
- package/dist/context-engine/summarizing.js +5 -8
- package/dist/cron/service/timer.js +18 -0
- package/dist/gateway/protocol/index.js +5 -2
- package/dist/gateway/protocol/schema/error-codes.js +1 -0
- package/dist/gateway/protocol/schema/swarm.js +80 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-close.js +4 -0
- package/dist/gateway/server-constants.js +1 -0
- package/dist/gateway/server-cron.js +29 -0
- package/dist/gateway/server-maintenance.js +35 -2
- package/dist/gateway/server-methods/swarm.js +58 -0
- package/dist/gateway/server-methods/telemetry.js +71 -0
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +9 -2
- package/dist/gateway/server.impl.js +33 -16
- package/dist/infra/abort-pattern.js +4 -4
- package/dist/infra/retry.js +3 -1
- package/dist/skills/commands.js +7 -25
- package/dist/skills/index.js +14 -17
- package/dist/skills/parser.js +12 -27
- package/dist/skills/registry.js +3 -6
- package/dist/skills/security.js +2 -8
- package/dist/swarm/service.js +247 -0
- package/dist/telemetry/alert-engine.js +258 -0
- package/dist/telemetry/cron-instrumentation.js +49 -0
- package/dist/telemetry/gateway-instrumentation.js +80 -0
- package/dist/telemetry/instrumentation.js +66 -0
- package/dist/telemetry/service.js +345 -0
- package/dist/tui/components/assistant-message.js +6 -2
- package/dist/tui/components/hyperlink-markdown.js +32 -0
- package/dist/tui/components/searchable-select-list.js +12 -1
- package/dist/tui/components/user-message.js +6 -2
- package/dist/tui/index.js +22 -6
- package/dist/tui/theme/theme-detection.js +226 -0
- package/dist/tui/tui-command-handlers.js +20 -0
- package/dist/tui/tui-formatters.js +4 -3
- package/dist/tui/utils/ctrl-c-handler.js +67 -0
- package/dist/tui/utils/osc8-hyperlinks.js +208 -0
- package/dist/tui/utils/safe-stop.js +180 -0
- package/dist/tui/utils/session-key-utils.js +81 -0
- package/dist/tui/utils/text-sanitization.js +284 -0
- package/dist/utils/lru-cache.js +116 -0
- package/dist/utils/performance.js +199 -0
- package/dist/utils/retry.js +240 -0
- package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
- package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
- package/docs/PLANO_ACAO_TUI.md +357 -0
- package/docs/PROGRESSO_TUI.md +66 -0
- package/docs/RELATORIO_FINAL.md +217 -0
- package/docs/diagnostico-shell-completion.md +265 -0
- package/docs/features/advanced-memory.md +585 -0
- package/docs/features/discord-components-v2.md +277 -0
- package/docs/features/swarm.md +100 -0
- package/docs/features/telemetry.md +284 -0
- package/docs/integrations/INTEGRATION_PLAN.md +665 -345
- package/docs/models/provider-infrastructure.md +400 -0
- package/docs/security/exec-approvals.md +294 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/hexstrike-bridge/README.md +119 -0
- package/extensions/hexstrike-bridge/index.test.ts +247 -0
- package/extensions/hexstrike-bridge/index.ts +487 -0
- package/extensions/hexstrike-bridge/package.json +17 -0
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mcp-server/index.ts +14 -0
- package/extensions/mcp-server/package.json +11 -0
- package/extensions/mcp-server/src/service.ts +540 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance monitoring utilities for PoolBot
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Function execution timing with automatic logging
|
|
6
|
+
* - Memory usage tracking
|
|
7
|
+
* - Async operation profiling
|
|
8
|
+
* - Performance markers for critical paths
|
|
9
|
+
* - Slow operation detection and warnings
|
|
10
|
+
*/
|
|
11
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
12
|
+
const perfLog = createSubsystemLogger("perf");
|
|
13
|
+
// Thresholds for slow operation warnings (in ms)
|
|
14
|
+
const SLOW_THRESHOLDS = {
|
|
15
|
+
default: 100,
|
|
16
|
+
config: 50,
|
|
17
|
+
gateway: 200,
|
|
18
|
+
memory: 500,
|
|
19
|
+
search: 1000,
|
|
20
|
+
doctor: 300,
|
|
21
|
+
completion: 100,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Active performance markers
|
|
25
|
+
*/
|
|
26
|
+
const activeMarkers = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Start a performance marker
|
|
29
|
+
*/
|
|
30
|
+
export function startMarker(name, metadata) {
|
|
31
|
+
const id = `${name}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
32
|
+
activeMarkers.set(id, {
|
|
33
|
+
name,
|
|
34
|
+
startTime: performance.now(),
|
|
35
|
+
metadata,
|
|
36
|
+
});
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* End a performance marker and log results
|
|
41
|
+
*/
|
|
42
|
+
export function endMarker(id, options = {}) {
|
|
43
|
+
const marker = activeMarkers.get(id);
|
|
44
|
+
if (!marker) {
|
|
45
|
+
perfLog.warn(`Performance marker not found: ${id}`);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
activeMarkers.delete(id);
|
|
49
|
+
const endTime = performance.now();
|
|
50
|
+
const durationMs = Math.round(endTime - marker.startTime);
|
|
51
|
+
const threshold = options.threshold ?? SLOW_THRESHOLDS[marker.name] ?? SLOW_THRESHOLDS.default;
|
|
52
|
+
const slow = durationMs > threshold;
|
|
53
|
+
const metrics = {
|
|
54
|
+
operation: marker.name,
|
|
55
|
+
durationMs,
|
|
56
|
+
slow,
|
|
57
|
+
metadata: marker.metadata,
|
|
58
|
+
};
|
|
59
|
+
if (options.log ?? true) {
|
|
60
|
+
if (slow) {
|
|
61
|
+
perfLog.warn(`Slow operation: ${marker.name} took ${durationMs}ms (threshold: ${threshold}ms)`, marker.metadata);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
perfLog.debug(`${marker.name}: ${durationMs}ms`, marker.metadata);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return metrics;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Time a synchronous function execution
|
|
71
|
+
*/
|
|
72
|
+
export function timeSync(name, fn, options = {}) {
|
|
73
|
+
const id = startMarker(name, options.metadata);
|
|
74
|
+
try {
|
|
75
|
+
return fn();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
endMarker(id, { log: options.log, threshold: options.threshold });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Time an asynchronous function execution
|
|
83
|
+
*/
|
|
84
|
+
export async function timeAsync(name, fn, options = {}) {
|
|
85
|
+
const id = startMarker(name, options.metadata);
|
|
86
|
+
try {
|
|
87
|
+
return await fn();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
endMarker(id, { log: options.log, threshold: options.threshold });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a performance-monitored version of a function
|
|
95
|
+
*/
|
|
96
|
+
export function withPerformanceTracking(name, fn, options = {}) {
|
|
97
|
+
return (...args) => {
|
|
98
|
+
const metadata = options.logArgs ? { args: args.map((a) => String(a)) } : undefined;
|
|
99
|
+
return timeSync(name, () => fn(...args), { ...options, metadata });
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a performance-monitored version of an async function
|
|
104
|
+
*/
|
|
105
|
+
export function withAsyncPerformanceTracking(name, fn, options = {}) {
|
|
106
|
+
return async (...args) => {
|
|
107
|
+
const metadata = options.logArgs ? { args: args.map((a) => String(a)) } : undefined;
|
|
108
|
+
return timeAsync(name, () => fn(...args), { ...options, metadata });
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get current memory usage
|
|
113
|
+
*/
|
|
114
|
+
export function getMemorySnapshot() {
|
|
115
|
+
const usage = process.memoryUsage();
|
|
116
|
+
return {
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
used: Math.round(usage.heapUsed / 1024 / 1024), // MB
|
|
119
|
+
total: Math.round(usage.heapTotal / 1024 / 1024), // MB
|
|
120
|
+
external: Math.round(usage.external / 1024 / 1024), // MB
|
|
121
|
+
arrayBuffers: Math.round(usage.arrayBuffers / 1024 / 1024), // MB
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Track memory before and after an operation
|
|
126
|
+
*/
|
|
127
|
+
export async function withMemoryTracking(name, fn, options = {}) {
|
|
128
|
+
const before = getMemorySnapshot();
|
|
129
|
+
const startId = startMarker(`${name}_memory`);
|
|
130
|
+
try {
|
|
131
|
+
return await fn();
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
const after = getMemorySnapshot();
|
|
135
|
+
endMarker(startId, { log: false });
|
|
136
|
+
const delta = after.used - before.used;
|
|
137
|
+
const warnThreshold = options.warnThresholdMB ?? 50;
|
|
138
|
+
if (options.log ?? true) {
|
|
139
|
+
if (delta > warnThreshold) {
|
|
140
|
+
perfLog.warn(`High memory usage in ${name}: +${delta}MB (${before.used}MB -> ${after.used}MB)`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
perfLog.debug(`${name} memory: ${before.used}MB -> ${after.used}MB (${delta >= 0 ? "+" : ""}${delta}MB)`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get all active markers (for debugging)
|
|
150
|
+
*/
|
|
151
|
+
export function getActiveMarkers() {
|
|
152
|
+
return Array.from(activeMarkers.values());
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clear all active markers
|
|
156
|
+
*/
|
|
157
|
+
export function clearAllMarkers() {
|
|
158
|
+
activeMarkers.clear();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create a performance collector for batch operations
|
|
162
|
+
*/
|
|
163
|
+
export function createPerformanceCollector() {
|
|
164
|
+
const operations = [];
|
|
165
|
+
return {
|
|
166
|
+
add: (metrics) => {
|
|
167
|
+
operations.push(metrics);
|
|
168
|
+
},
|
|
169
|
+
getReport: () => {
|
|
170
|
+
if (operations.length === 0) {
|
|
171
|
+
return {
|
|
172
|
+
operations: [],
|
|
173
|
+
summary: {
|
|
174
|
+
totalOperations: 0,
|
|
175
|
+
slowOperations: 0,
|
|
176
|
+
averageDurationMs: 0,
|
|
177
|
+
maxDurationMs: 0,
|
|
178
|
+
minDurationMs: 0,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const durations = operations.map((o) => o.durationMs);
|
|
183
|
+
const slowOperations = operations.filter((o) => o.slow).length;
|
|
184
|
+
return {
|
|
185
|
+
operations,
|
|
186
|
+
summary: {
|
|
187
|
+
totalOperations: operations.length,
|
|
188
|
+
slowOperations,
|
|
189
|
+
averageDurationMs: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
|
|
190
|
+
maxDurationMs: Math.max(...durations),
|
|
191
|
+
minDurationMs: Math.min(...durations),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
clear: () => {
|
|
196
|
+
operations.length = 0;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utilities with exponential backoff and jitter
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Exponential backoff with configurable parameters
|
|
6
|
+
* - Jitter to prevent thundering herd
|
|
7
|
+
* - Abort signal support for cancellation
|
|
8
|
+
* - Custom retry predicates (decide which errors to retry)
|
|
9
|
+
* - OnRetry callback for logging/observability
|
|
10
|
+
* - Max retry duration support
|
|
11
|
+
*/
|
|
12
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
13
|
+
const retryLog = createSubsystemLogger("retry");
|
|
14
|
+
/**
|
|
15
|
+
* Calculate delay with exponential backoff and optional jitter
|
|
16
|
+
*/
|
|
17
|
+
function calculateDelay(attempt, initialDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
18
|
+
// Exponential backoff: initialDelay * (multiplier ^ attempt)
|
|
19
|
+
const exponentialDelay = initialDelayMs * Math.pow(backoffMultiplier, attempt);
|
|
20
|
+
// Apply jitter: random value between 0.5x and 1.5x of calculated delay
|
|
21
|
+
if (jitter) {
|
|
22
|
+
const jitterFactor = 0.5 + Math.random();
|
|
23
|
+
return Math.min(Math.round(exponentialDelay * jitterFactor), maxDelayMs);
|
|
24
|
+
}
|
|
25
|
+
return Math.min(exponentialDelay, maxDelayMs);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Default retry predicate - retries on network/timeout errors
|
|
29
|
+
*/
|
|
30
|
+
function defaultRetryIf(error) {
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
const errorMessage = error.message.toLowerCase();
|
|
33
|
+
// Network errors
|
|
34
|
+
if (errorMessage.includes("timeout") ||
|
|
35
|
+
errorMessage.includes("etimedout") ||
|
|
36
|
+
errorMessage.includes("enotfound") ||
|
|
37
|
+
errorMessage.includes("econnrefused") ||
|
|
38
|
+
errorMessage.includes("econnreset") ||
|
|
39
|
+
errorMessage.includes("network") ||
|
|
40
|
+
errorMessage.includes("socket") ||
|
|
41
|
+
errorMessage.includes("fetch failed") ||
|
|
42
|
+
errorMessage.includes("abort") ||
|
|
43
|
+
errorMessage.includes("temporary")) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// HTTP status codes that indicate retryable errors
|
|
47
|
+
if (errorMessage.includes("429") || // Too Many Requests
|
|
48
|
+
errorMessage.includes("503") || // Service Unavailable
|
|
49
|
+
errorMessage.includes("502") || // Bad Gateway
|
|
50
|
+
errorMessage.includes("504") // Gateway Timeout
|
|
51
|
+
) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sleep with abort signal support
|
|
59
|
+
*/
|
|
60
|
+
function sleep(ms, signal) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
if (signal?.aborted) {
|
|
63
|
+
reject(new Error("Operation aborted"));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const timeout = setTimeout(resolve, ms);
|
|
67
|
+
if (signal) {
|
|
68
|
+
signal.addEventListener("abort", () => {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
reject(new Error("Operation aborted"));
|
|
71
|
+
}, { once: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Retry an async operation with exponential backoff
|
|
77
|
+
*/
|
|
78
|
+
export async function retryAsync(operation, options = {}) {
|
|
79
|
+
const { maxAttempts = 3, initialDelayMs = 100, maxDelayMs = 30000, backoffMultiplier = 2, jitter = true, maxDurationMs, retryIf = defaultRetryIf, onRetry, signal, operationName = "operation", } = options;
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
let lastError;
|
|
82
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
83
|
+
// Check max duration
|
|
84
|
+
if (maxDurationMs !== undefined) {
|
|
85
|
+
const elapsed = Date.now() - startTime;
|
|
86
|
+
if (elapsed >= maxDurationMs) {
|
|
87
|
+
throw new Error(`${operationName} failed: max duration (${maxDurationMs}ms) exceeded after ${attempt} attempts`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const result = await operation();
|
|
92
|
+
if (attempt > 0) {
|
|
93
|
+
retryLog.info(`${operationName} succeeded after ${attempt + 1} attempts`);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
lastError = error;
|
|
99
|
+
// Check if we should retry this error
|
|
100
|
+
if (!retryIf(error, attempt)) {
|
|
101
|
+
retryLog.debug(`${operationName} not retryable: ${error}`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
// Check if this was the last attempt
|
|
105
|
+
if (attempt === maxAttempts - 1) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
// Calculate delay for next attempt
|
|
109
|
+
const delayMs = calculateDelay(attempt, initialDelayMs, maxDelayMs, backoffMultiplier, jitter);
|
|
110
|
+
retryLog.debug(`${operationName} attempt ${attempt + 1} failed, retrying in ${delayMs}ms: ${error}`);
|
|
111
|
+
// Call onRetry callback
|
|
112
|
+
onRetry?.(error, attempt + 1, delayMs);
|
|
113
|
+
// Wait before next attempt
|
|
114
|
+
await sleep(delayMs, signal);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw lastError;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Retry an async operation and return detailed outcome
|
|
121
|
+
*/
|
|
122
|
+
export async function retryAsyncWithOutcome(operation, options = {}) {
|
|
123
|
+
const startTime = Date.now();
|
|
124
|
+
try {
|
|
125
|
+
const data = await retryAsync(operation, options);
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
data,
|
|
129
|
+
attempts: 1, // This would need to be tracked properly
|
|
130
|
+
totalDurationMs: Date.now() - startTime,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error,
|
|
137
|
+
attempts: options.maxAttempts ?? 3,
|
|
138
|
+
totalDurationMs: Date.now() - startTime,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create a retryable version of a function
|
|
144
|
+
*/
|
|
145
|
+
export function withRetry(fn, options = {}) {
|
|
146
|
+
return async (...args) => {
|
|
147
|
+
return retryAsync(() => fn(...args), {
|
|
148
|
+
...options,
|
|
149
|
+
operationName: options.operationName ?? fn.name,
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Retry configuration presets for common scenarios
|
|
155
|
+
*/
|
|
156
|
+
export const RetryPresets = {
|
|
157
|
+
/** Fast retry for quick operations (e.g., config reads) */
|
|
158
|
+
fast: {
|
|
159
|
+
maxAttempts: 3,
|
|
160
|
+
initialDelayMs: 50,
|
|
161
|
+
maxDelayMs: 500,
|
|
162
|
+
backoffMultiplier: 2,
|
|
163
|
+
},
|
|
164
|
+
/** Standard retry for most operations */
|
|
165
|
+
standard: {
|
|
166
|
+
maxAttempts: 3,
|
|
167
|
+
initialDelayMs: 100,
|
|
168
|
+
maxDelayMs: 5000,
|
|
169
|
+
backoffMultiplier: 2,
|
|
170
|
+
},
|
|
171
|
+
/** Aggressive retry for critical operations */
|
|
172
|
+
aggressive: {
|
|
173
|
+
maxAttempts: 5,
|
|
174
|
+
initialDelayMs: 100,
|
|
175
|
+
maxDelayMs: 30000,
|
|
176
|
+
backoffMultiplier: 2,
|
|
177
|
+
},
|
|
178
|
+
/** Patient retry for flaky external services */
|
|
179
|
+
patient: {
|
|
180
|
+
maxAttempts: 10,
|
|
181
|
+
initialDelayMs: 1000,
|
|
182
|
+
maxDelayMs: 60000,
|
|
183
|
+
backoffMultiplier: 2,
|
|
184
|
+
},
|
|
185
|
+
/** No retry - fail fast */
|
|
186
|
+
none: {
|
|
187
|
+
maxAttempts: 1,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
export class CircuitBreaker {
|
|
191
|
+
options;
|
|
192
|
+
state = "closed";
|
|
193
|
+
failures = 0;
|
|
194
|
+
nextAttempt = 0;
|
|
195
|
+
halfOpenCalls = 0;
|
|
196
|
+
constructor(options) {
|
|
197
|
+
this.options = options;
|
|
198
|
+
}
|
|
199
|
+
async execute(operation) {
|
|
200
|
+
if (this.state === "open") {
|
|
201
|
+
if (Date.now() < this.nextAttempt) {
|
|
202
|
+
throw new Error("Circuit breaker is open");
|
|
203
|
+
}
|
|
204
|
+
this.state = "half-open";
|
|
205
|
+
this.halfOpenCalls = 0;
|
|
206
|
+
}
|
|
207
|
+
if (this.state === "half-open") {
|
|
208
|
+
if (this.halfOpenCalls >= this.options.halfOpenMaxCalls) {
|
|
209
|
+
throw new Error("Circuit breaker is half-open, too many calls");
|
|
210
|
+
}
|
|
211
|
+
this.halfOpenCalls++;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const result = await operation();
|
|
215
|
+
this.onSuccess();
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
this.onFailure();
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
onSuccess() {
|
|
224
|
+
if (this.state === "half-open") {
|
|
225
|
+
this.state = "closed";
|
|
226
|
+
this.failures = 0;
|
|
227
|
+
this.halfOpenCalls = 0;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
onFailure() {
|
|
231
|
+
this.failures++;
|
|
232
|
+
if (this.failures >= this.options.failureThreshold) {
|
|
233
|
+
this.state = "open";
|
|
234
|
+
this.nextAttempt = Date.now() + this.options.resetTimeoutMs;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
getState() {
|
|
238
|
+
return this.state;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Melhorias Implementadas no PoolBot CLI
|
|
2
|
+
|
|
3
|
+
## Resumo Executivo
|
|
4
|
+
|
|
5
|
+
Foram implementadas melhorias profissionais no PoolBot CLI focando em:
|
|
6
|
+
1. **Sistema de Completion** - Removido código legado, criado diretório de state necessário
|
|
7
|
+
2. **Error Handling** - Novo sistema estruturado com códigos de erro e sugestões
|
|
8
|
+
3. **Doctor Command** - Modo `--check` individual e output JSON
|
|
9
|
+
4. **Arquitetura** - Código mais modular e profissional
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Sistema de Error Handling Estruturado
|
|
14
|
+
|
|
15
|
+
**Arquivo:** `src/cli/errors.ts` (novo)
|
|
16
|
+
|
|
17
|
+
### Funcionalidades:
|
|
18
|
+
- **PoolBotError class**: Erros estruturados com códigos, categorias, sugestões
|
|
19
|
+
- **Error taxonomy**: Categorias (config, gateway, auth, network, validation, filesystem, runtime, plugin)
|
|
20
|
+
- **Error codes registry**: Códigos padronizados (E1001, E2001, etc.)
|
|
21
|
+
- **Helper functions**: `createConfigError()`, `createGatewayError()`, `createValidationError()`
|
|
22
|
+
|
|
23
|
+
### Exemplo de uso:
|
|
24
|
+
```typescript
|
|
25
|
+
throw new PoolBotError({
|
|
26
|
+
message: "Gateway connection failed",
|
|
27
|
+
code: ErrorCodes.GATEWAY_CONNECTION_FAILED,
|
|
28
|
+
category: "gateway",
|
|
29
|
+
recoverable: true,
|
|
30
|
+
suggestions: [
|
|
31
|
+
"Run `poolbot gateway status` to check health",
|
|
32
|
+
"Run `poolbot doctor` for diagnostics"
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. Doctor Command Aprimorado
|
|
40
|
+
|
|
41
|
+
**Arquivos modificados:**
|
|
42
|
+
- `src/commands/doctor-prompter.ts` - Adicionados tipos `DoctorCheck` e opções `json`
|
|
43
|
+
- `src/cli/program/register.maintenance.ts` - Adicionadas opções `--check` e `--json`
|
|
44
|
+
- `src/commands/doctor-checks.ts` (novo) - Sistema estruturado de checks
|
|
45
|
+
|
|
46
|
+
### Novas funcionalidades:
|
|
47
|
+
|
|
48
|
+
#### 2.1 Modo `--check` Individual
|
|
49
|
+
Execute checks específicos para debugging rápido:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Verificar apenas configuração
|
|
53
|
+
poolbot doctor --check config
|
|
54
|
+
|
|
55
|
+
# Verificar apenas gateway
|
|
56
|
+
poolbot doctor --check gateway
|
|
57
|
+
|
|
58
|
+
# Verificar apenas completion
|
|
59
|
+
poolbot doctor --check completion
|
|
60
|
+
|
|
61
|
+
# Verificar auth, security, etc.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Checks disponíveis:**
|
|
65
|
+
- `config` - Validação do arquivo de configuração
|
|
66
|
+
- `auth` - Perfis de autenticação
|
|
67
|
+
- `gateway` - Saúde do gateway
|
|
68
|
+
- `completion` - Estado do shell completion
|
|
69
|
+
- `security` - Alertas de segurança
|
|
70
|
+
- `memory` - Sistema de memória
|
|
71
|
+
- `workspace` - Diretórios de workspace
|
|
72
|
+
- `state` - Diretórios de estado
|
|
73
|
+
|
|
74
|
+
#### 2.2 Output JSON
|
|
75
|
+
Saída estruturada para automação:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
poolbot doctor --json
|
|
79
|
+
poolbot doctor --check gateway --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Exemplo de output:**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"timestamp": "2026-03-09T12:00:00Z",
|
|
86
|
+
"version": "2026.3.9",
|
|
87
|
+
"checks": [
|
|
88
|
+
{
|
|
89
|
+
"name": "config",
|
|
90
|
+
"status": "ok",
|
|
91
|
+
"message": "Configuration is valid",
|
|
92
|
+
"durationMs": 45,
|
|
93
|
+
"details": { "path": "/Users/pool/.poolbot/poolbot.json" }
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"summary": {
|
|
97
|
+
"total": 8,
|
|
98
|
+
"ok": 7,
|
|
99
|
+
"warning": 1,
|
|
100
|
+
"error": 0,
|
|
101
|
+
"skipped": 0,
|
|
102
|
+
"autoFixed": 0
|
|
103
|
+
},
|
|
104
|
+
"healthScore": 94
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 2.3 Health Score
|
|
109
|
+
Pontuação de 0-100 calculada automaticamente:
|
|
110
|
+
- OK = 1 ponto
|
|
111
|
+
- Warning = 0.5 pontos
|
|
112
|
+
- Error = 0 pontos
|
|
113
|
+
- Skipped = 0.75 pontos
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 3. Limpeza de Código Legado
|
|
118
|
+
|
|
119
|
+
### Removido:
|
|
120
|
+
- `src/cli/program/register.completion.ts` - Arquivo não utilizado, sistema substituído
|
|
121
|
+
|
|
122
|
+
### Criado:
|
|
123
|
+
- Diretório `~/.poolbot/state/completions/` - Necessário para cache de completions
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Melhorias no Comando Completion
|
|
128
|
+
|
|
129
|
+
**Arquivo:** `src/cli/completion-cli.ts` (já existente, mantido)
|
|
130
|
+
|
|
131
|
+
O sistema de completion já existente é robusto:
|
|
132
|
+
- Multi-shell support (zsh, bash, fish, PowerShell)
|
|
133
|
+
- Caching para startup rápido
|
|
134
|
+
- Auto-upgrade de padrões lentos
|
|
135
|
+
- Profile installation automático
|
|
136
|
+
|
|
137
|
+
**Status:** ✅ Funcionando (requer versão atualizada do poolbot)
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Comandos para Testar
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Verificar versão do poolbot
|
|
145
|
+
poolbot --version
|
|
146
|
+
|
|
147
|
+
# Testar doctor com novo modo check
|
|
148
|
+
poolbot doctor --check config
|
|
149
|
+
poolbot doctor --check completion
|
|
150
|
+
poolbot doctor --check gateway --json
|
|
151
|
+
|
|
152
|
+
# Testar doctor completo com JSON
|
|
153
|
+
poolbot doctor --json
|
|
154
|
+
|
|
155
|
+
# Testar completion
|
|
156
|
+
dist/poolbot.mjs completion --write-state
|
|
157
|
+
dist/poolbot.mjs completion --install --yes
|
|
158
|
+
|
|
159
|
+
# Testar help dos novos comandos
|
|
160
|
+
poolbot doctor --help
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Checklist de Implementação
|
|
166
|
+
|
|
167
|
+
### ✅ Concluído:
|
|
168
|
+
- [x] Sistema de error handling estruturado
|
|
169
|
+
- [x] Modo `--check` individual no doctor
|
|
170
|
+
- [x] Output JSON no doctor
|
|
171
|
+
- [x] Health score calculation
|
|
172
|
+
- [x] Remoção de código legado
|
|
173
|
+
- [x] Type safety em todos os novos arquivos
|
|
174
|
+
- [x] Build passando
|
|
175
|
+
|
|
176
|
+
### 🔄 Próximos passos sugeridos:
|
|
177
|
+
- [ ] Adicionar tests para doctor-checks.ts
|
|
178
|
+
- [ ] Implementar auto-fix para checks individuais
|
|
179
|
+
- [ ] Adicionar comando `poolbot completion status`
|
|
180
|
+
- [ ] Melhorar mensagens de erro com sugestões contextuais
|
|
181
|
+
- [ ] Adicionar Fig.io spec generation
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Comparação: Antes vs Depois
|
|
186
|
+
|
|
187
|
+
| Aspecto | Antes | Depois |
|
|
188
|
+
|---------|-------|--------|
|
|
189
|
+
| **Doctor checks** | Todos de uma vez | Individual ou todos |
|
|
190
|
+
| **Output format** | Texto apenas | Texto + JSON |
|
|
191
|
+
| **Health score** | ❌ Não tinha | ✅ 0-100 score |
|
|
192
|
+
| **Error handling** | Genérico | Estruturado com códigos |
|
|
193
|
+
| **Código legado** | Arquivo morto | Removido |
|
|
194
|
+
| **Type safety** | Parcial | Completo |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Notas Técnicas
|
|
199
|
+
|
|
200
|
+
### Build:
|
|
201
|
+
```bash
|
|
202
|
+
pnpm build # ✅ Passando
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Type Checking:
|
|
206
|
+
```bash
|
|
207
|
+
pnpm tsc --noEmit # ✅ Sem erros nos novos arquivos
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Arquivos Modificados/Criados:
|
|
211
|
+
- ✅ `src/cli/errors.ts` (novo)
|
|
212
|
+
- ✅ `src/commands/doctor-checks.ts` (novo)
|
|
213
|
+
- ✅ `src/commands/doctor-prompter.ts` (modificado)
|
|
214
|
+
- ✅ `src/cli/program/register.maintenance.ts` (modificado)
|
|
215
|
+
- ❌ `src/cli/program/register.completion.ts` (removido)
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Conclusão
|
|
220
|
+
|
|
221
|
+
As melhorias implementadas tornam o PoolBot mais profissional, alinhado com as melhores práticas do Claude Code (OpenClaw):
|
|
222
|
+
|
|
223
|
+
1. **Shell completion** - Sistema já existente é robusto, apenas removido código legado
|
|
224
|
+
2. **Doctor command** - Agora com checks individuais, output JSON, e health score
|
|
225
|
+
3. **Error handling** - Sistema estruturado para melhor UX
|
|
226
|
+
4. **Código** - Mais limpo, modular, e type-safe
|
|
227
|
+
|
|
228
|
+
**Próximo passo recomendado:** Atualizar o poolbot instalado para a versão 2026.3.9+ para aproveitar todas as melhorias.
|