@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,345 @@
|
|
|
1
|
+
import { context, metrics, trace } from "@opentelemetry/api";
|
|
2
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
3
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
4
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
5
|
+
import { ConsoleMetricExporter, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader, } from "@opentelemetry/sdk-metrics";
|
|
6
|
+
import { BatchSpanProcessor, ConsoleSpanExporter, InMemorySpanExporter, NodeTracerProvider, SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-node";
|
|
7
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
8
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
9
|
+
const log = createSubsystemLogger("telemetry");
|
|
10
|
+
export const defaultTelemetryConfig = {
|
|
11
|
+
enabled: true,
|
|
12
|
+
serviceName: "poolbot",
|
|
13
|
+
serviceVersion: process.env.npm_package_version ?? "unknown",
|
|
14
|
+
tracing: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
exporter: "console",
|
|
17
|
+
sampleRate: 1.0,
|
|
18
|
+
},
|
|
19
|
+
metrics: {
|
|
20
|
+
enabled: true,
|
|
21
|
+
exporter: "console",
|
|
22
|
+
exportIntervalMs: 60000,
|
|
23
|
+
aggregationTemporality: 1, // CUMULATIVE
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export class TelemetryService {
|
|
27
|
+
config;
|
|
28
|
+
tracerProvider;
|
|
29
|
+
meterProvider;
|
|
30
|
+
tracer;
|
|
31
|
+
meter;
|
|
32
|
+
inMemorySpanExporter;
|
|
33
|
+
inMemoryMetricExporter;
|
|
34
|
+
counters = new Map();
|
|
35
|
+
histograms = new Map();
|
|
36
|
+
gauges = new Map();
|
|
37
|
+
gaugeValues = new Map();
|
|
38
|
+
activeSpans = new Map();
|
|
39
|
+
isStarted = false;
|
|
40
|
+
alertEngine;
|
|
41
|
+
onAlertCallback;
|
|
42
|
+
constructor(config = {}) {
|
|
43
|
+
this.config = { ...defaultTelemetryConfig, ...config };
|
|
44
|
+
if (config.tracing) {
|
|
45
|
+
this.config.tracing = { ...defaultTelemetryConfig.tracing, ...config.tracing };
|
|
46
|
+
}
|
|
47
|
+
if (config.metrics) {
|
|
48
|
+
this.config.metrics = { ...defaultTelemetryConfig.metrics, ...config.metrics };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async start() {
|
|
52
|
+
if (this.isStarted || !this.config.enabled) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
log.info("Starting telemetry service...");
|
|
56
|
+
const resource = resourceFromAttributes({
|
|
57
|
+
[ATTR_SERVICE_NAME]: this.config.serviceName,
|
|
58
|
+
[ATTR_SERVICE_VERSION]: this.config.serviceVersion,
|
|
59
|
+
...this.config.attributes,
|
|
60
|
+
});
|
|
61
|
+
if (this.config.tracing.enabled) {
|
|
62
|
+
this.setupTracing(resource);
|
|
63
|
+
}
|
|
64
|
+
if (this.config.metrics.enabled) {
|
|
65
|
+
this.setupMetrics(resource);
|
|
66
|
+
}
|
|
67
|
+
this.isStarted = true;
|
|
68
|
+
log.info("Telemetry service started");
|
|
69
|
+
}
|
|
70
|
+
stop() {
|
|
71
|
+
if (!this.isStarted) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
log.info("Stopping telemetry service...");
|
|
75
|
+
void this.tracerProvider?.shutdown();
|
|
76
|
+
void this.meterProvider?.shutdown();
|
|
77
|
+
this.isStarted = false;
|
|
78
|
+
log.info("Telemetry service stopped");
|
|
79
|
+
}
|
|
80
|
+
setupTracing(resource) {
|
|
81
|
+
const spanProcessors = [];
|
|
82
|
+
switch (this.config.tracing.exporter) {
|
|
83
|
+
case "console":
|
|
84
|
+
spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter()));
|
|
85
|
+
break;
|
|
86
|
+
case "memory":
|
|
87
|
+
this.inMemorySpanExporter = new InMemorySpanExporter();
|
|
88
|
+
spanProcessors.push(new SimpleSpanProcessor(this.inMemorySpanExporter));
|
|
89
|
+
break;
|
|
90
|
+
case "otlp": {
|
|
91
|
+
const endpoint = this.config.tracing.otlpEndpoint ?? "http://localhost:4318/v1/traces";
|
|
92
|
+
spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({ url: endpoint })));
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "none":
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
this.tracerProvider = new NodeTracerProvider({
|
|
100
|
+
resource,
|
|
101
|
+
spanProcessors,
|
|
102
|
+
});
|
|
103
|
+
this.tracerProvider.register();
|
|
104
|
+
this.tracer = trace.getTracer(this.config.serviceName, this.config.serviceVersion);
|
|
105
|
+
}
|
|
106
|
+
setupMetrics(resource) {
|
|
107
|
+
const readers = [];
|
|
108
|
+
switch (this.config.metrics.exporter) {
|
|
109
|
+
case "console":
|
|
110
|
+
readers.push(new PeriodicExportingMetricReader({
|
|
111
|
+
exporter: new ConsoleMetricExporter(),
|
|
112
|
+
exportIntervalMillis: this.config.metrics.exportIntervalMs,
|
|
113
|
+
}));
|
|
114
|
+
break;
|
|
115
|
+
case "memory":
|
|
116
|
+
this.inMemoryMetricExporter = new InMemoryMetricExporter(this.config.metrics.aggregationTemporality);
|
|
117
|
+
readers.push(new PeriodicExportingMetricReader({
|
|
118
|
+
exporter: this.inMemoryMetricExporter,
|
|
119
|
+
exportIntervalMillis: this.config.metrics.exportIntervalMs,
|
|
120
|
+
}));
|
|
121
|
+
break;
|
|
122
|
+
case "otlp": {
|
|
123
|
+
const endpoint = this.config.metrics.otlpEndpoint ?? "http://localhost:4318/v1/metrics";
|
|
124
|
+
readers.push(new PeriodicExportingMetricReader({
|
|
125
|
+
exporter: new OTLPMetricExporter({ url: endpoint }),
|
|
126
|
+
exportIntervalMillis: this.config.metrics.exportIntervalMs,
|
|
127
|
+
}));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "none":
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
this.meterProvider = new MeterProvider({
|
|
135
|
+
resource,
|
|
136
|
+
readers,
|
|
137
|
+
});
|
|
138
|
+
metrics.setGlobalMeterProvider(this.meterProvider);
|
|
139
|
+
this.meter = metrics.getMeter(this.config.serviceName, this.config.serviceVersion);
|
|
140
|
+
}
|
|
141
|
+
isEnabled() {
|
|
142
|
+
return this.isStarted && this.config.enabled;
|
|
143
|
+
}
|
|
144
|
+
createSpan(name, attributes) {
|
|
145
|
+
if (!this.tracer)
|
|
146
|
+
return undefined;
|
|
147
|
+
const span = this.tracer.startSpan(name, { attributes });
|
|
148
|
+
const spanId = span.spanContext().spanId;
|
|
149
|
+
this.activeSpans.set(spanId, span);
|
|
150
|
+
return span;
|
|
151
|
+
}
|
|
152
|
+
endSpan(span, status) {
|
|
153
|
+
if (status) {
|
|
154
|
+
const statusCode = status.code === "ok" ? 1 : 2;
|
|
155
|
+
span.setStatus({ code: statusCode, message: status.message });
|
|
156
|
+
}
|
|
157
|
+
span.end();
|
|
158
|
+
this.activeSpans.delete(span.spanContext().spanId);
|
|
159
|
+
}
|
|
160
|
+
withSpan(name, fn, attributes) {
|
|
161
|
+
if (!this.tracer) {
|
|
162
|
+
return Promise.resolve(fn());
|
|
163
|
+
}
|
|
164
|
+
return this.tracer.startActiveSpan(name, { attributes }, async (span) => {
|
|
165
|
+
try {
|
|
166
|
+
const result = await fn();
|
|
167
|
+
span.setStatus({ code: 1 }); // OK
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
span.recordException(error);
|
|
172
|
+
span.setStatus({ code: 2, message: String(error) }); // ERROR
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
span.end();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
recordCounter(name, value = 1, attributes) {
|
|
181
|
+
if (!this.meter)
|
|
182
|
+
return;
|
|
183
|
+
let counter = this.counters.get(name);
|
|
184
|
+
if (!counter) {
|
|
185
|
+
counter = this.meter.createCounter(name);
|
|
186
|
+
this.counters.set(name, counter);
|
|
187
|
+
}
|
|
188
|
+
counter.add(value, attributes);
|
|
189
|
+
}
|
|
190
|
+
recordHistogram(name, value, attributes, options) {
|
|
191
|
+
if (!this.meter)
|
|
192
|
+
return;
|
|
193
|
+
let histogram = this.histograms.get(name);
|
|
194
|
+
if (!histogram) {
|
|
195
|
+
histogram = this.meter.createHistogram(name, options);
|
|
196
|
+
this.histograms.set(name, histogram);
|
|
197
|
+
}
|
|
198
|
+
histogram.record(value, attributes);
|
|
199
|
+
}
|
|
200
|
+
recordGauge(name, value, _attributes) {
|
|
201
|
+
if (!this.meter)
|
|
202
|
+
return;
|
|
203
|
+
const existingGauge = this.gauges.get(name);
|
|
204
|
+
if (!existingGauge) {
|
|
205
|
+
const newGauge = this.meter.createObservableGauge(name, {
|
|
206
|
+
valueType: 1, // DOUBLE
|
|
207
|
+
});
|
|
208
|
+
this.gauges.set(name, newGauge);
|
|
209
|
+
}
|
|
210
|
+
this.gaugeValues.set(name, value);
|
|
211
|
+
}
|
|
212
|
+
addEvent(span, name, attributes) {
|
|
213
|
+
span.addEvent(name, attributes);
|
|
214
|
+
}
|
|
215
|
+
getCurrentSpan() {
|
|
216
|
+
return trace.getSpan(context.active()) ?? undefined;
|
|
217
|
+
}
|
|
218
|
+
getSnapshot() {
|
|
219
|
+
if (!this.inMemorySpanExporter || !this.inMemoryMetricExporter) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
const spans = this.inMemorySpanExporter.getFinishedSpans();
|
|
223
|
+
const exportedMetrics = this.inMemoryMetricExporter.getMetrics();
|
|
224
|
+
const spanInfos = spans.map((span) => {
|
|
225
|
+
const spanContext = span.spanContext();
|
|
226
|
+
const startTime = span.startTime;
|
|
227
|
+
const endTime = span.endTime;
|
|
228
|
+
return {
|
|
229
|
+
traceId: spanContext.traceId,
|
|
230
|
+
spanId: spanContext.spanId,
|
|
231
|
+
parentSpanId: span.parentSpanId,
|
|
232
|
+
name: span.name,
|
|
233
|
+
startTime: startTime[0] * 1000000000 + startTime[1],
|
|
234
|
+
endTime: endTime ? endTime[0] * 1000000000 + endTime[1] : undefined,
|
|
235
|
+
status: span.status.code === 0 ? "unset" : span.status.code === 1 ? "ok" : "error",
|
|
236
|
+
attributes: Object.fromEntries(Object.entries(span.attributes)),
|
|
237
|
+
events: span.events.map((e) => {
|
|
238
|
+
const time = e.time;
|
|
239
|
+
return {
|
|
240
|
+
name: e.name,
|
|
241
|
+
timestamp: time[0] * 1000000000 + time[1],
|
|
242
|
+
attributes: Object.fromEntries(Object.entries(e.attributes ?? {})),
|
|
243
|
+
};
|
|
244
|
+
}),
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
const metricValues = [];
|
|
248
|
+
for (const metric of exportedMetrics) {
|
|
249
|
+
for (const scopeMetric of metric.scopeMetrics) {
|
|
250
|
+
for (const m of scopeMetric.metrics) {
|
|
251
|
+
const descriptor = m.descriptor;
|
|
252
|
+
const dataPoints = m.dataPoints;
|
|
253
|
+
if (dataPoints) {
|
|
254
|
+
for (const dp of dataPoints) {
|
|
255
|
+
metricValues.push({
|
|
256
|
+
name: descriptor.name,
|
|
257
|
+
value: typeof dp.value === "number" ? dp.value : 0,
|
|
258
|
+
attributes: dp.attributes,
|
|
259
|
+
timestamp: Date.now(),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
timestamp: Date.now(),
|
|
268
|
+
spans: spanInfos,
|
|
269
|
+
metrics: metricValues,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
clearSnapshot() {
|
|
273
|
+
this.inMemorySpanExporter?.reset();
|
|
274
|
+
this.inMemoryMetricExporter?.reset();
|
|
275
|
+
}
|
|
276
|
+
getConfig() {
|
|
277
|
+
return { ...this.config };
|
|
278
|
+
}
|
|
279
|
+
async shutdown() {
|
|
280
|
+
log.debug("telemetry: shutting down");
|
|
281
|
+
await this.tracerProvider?.shutdown();
|
|
282
|
+
await this.meterProvider?.shutdown();
|
|
283
|
+
}
|
|
284
|
+
setAlertEngine(engine) {
|
|
285
|
+
this.alertEngine = engine;
|
|
286
|
+
engine.onAlert((alert) => {
|
|
287
|
+
this.onAlertCallback?.(alert);
|
|
288
|
+
});
|
|
289
|
+
log.info(`Alert engine configured with ${engine.getRules().length} rules`);
|
|
290
|
+
}
|
|
291
|
+
onAlert(callback) {
|
|
292
|
+
this.onAlertCallback = callback;
|
|
293
|
+
}
|
|
294
|
+
evaluateAlerts(snapshot) {
|
|
295
|
+
if (!this.alertEngine)
|
|
296
|
+
return [];
|
|
297
|
+
const snap = snapshot ?? this.getSnapshot();
|
|
298
|
+
if (!snap)
|
|
299
|
+
return [];
|
|
300
|
+
return this.alertEngine.evaluate(snap);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
let globalTelemetryService;
|
|
304
|
+
export function getGlobalTelemetryService() {
|
|
305
|
+
return globalTelemetryService;
|
|
306
|
+
}
|
|
307
|
+
export function setGlobalTelemetryService(service) {
|
|
308
|
+
globalTelemetryService = service;
|
|
309
|
+
}
|
|
310
|
+
export function createTelemetryService(config) {
|
|
311
|
+
return new TelemetryService(config);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Convert OTel config from zod schema format to TelemetryService config format.
|
|
315
|
+
*/
|
|
316
|
+
export function telemetryConfigFromOtelConfig(otel, opts) {
|
|
317
|
+
if (!otel?.enabled) {
|
|
318
|
+
return {
|
|
319
|
+
...defaultTelemetryConfig,
|
|
320
|
+
enabled: false,
|
|
321
|
+
tracing: { ...defaultTelemetryConfig.tracing, enabled: false },
|
|
322
|
+
metrics: { ...defaultTelemetryConfig.metrics, enabled: false },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const exporterType = otel.endpoint ? "otlp" : "console";
|
|
326
|
+
return {
|
|
327
|
+
enabled: true,
|
|
328
|
+
serviceName: otel.serviceName ?? opts?.defaultServiceName ?? "poolbot",
|
|
329
|
+
serviceVersion: opts?.serviceVersion ?? process.env.npm_package_version ?? "unknown",
|
|
330
|
+
tracing: {
|
|
331
|
+
enabled: otel.traces ?? true,
|
|
332
|
+
exporter: exporterType,
|
|
333
|
+
otlpEndpoint: otel.endpoint,
|
|
334
|
+
sampleRate: otel.sampleRate ?? 1.0,
|
|
335
|
+
},
|
|
336
|
+
metrics: {
|
|
337
|
+
enabled: otel.metrics ?? true,
|
|
338
|
+
exporter: exporterType,
|
|
339
|
+
otlpEndpoint: otel.endpoint,
|
|
340
|
+
exportIntervalMs: otel.flushIntervalMs ?? 60000,
|
|
341
|
+
aggregationTemporality: 1, // CUMULATIVE
|
|
342
|
+
},
|
|
343
|
+
attributes: otel.headers,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer } from "@mariozechner/pi-tui";
|
|
2
2
|
import { markdownTheme, theme } from "../theme/theme.js";
|
|
3
|
+
import { sanitizeRenderableText } from "../utils/text-sanitization.js";
|
|
3
4
|
export class AssistantMessageComponent extends Container {
|
|
4
5
|
body;
|
|
5
6
|
constructor(text) {
|
|
6
7
|
super();
|
|
7
|
-
|
|
8
|
+
// Sanitize text before rendering to prevent terminal corruption
|
|
9
|
+
const sanitizedText = sanitizeRenderableText(text);
|
|
10
|
+
this.body = new Markdown(sanitizedText, 1, 0, markdownTheme, {
|
|
8
11
|
color: (line) => theme.fg(line),
|
|
9
12
|
});
|
|
10
13
|
this.addChild(new Spacer(1));
|
|
11
14
|
this.addChild(this.body);
|
|
12
15
|
}
|
|
13
16
|
setText(text) {
|
|
14
|
-
|
|
17
|
+
// Sanitize text before updating
|
|
18
|
+
this.body.setText(sanitizeRenderableText(text));
|
|
15
19
|
}
|
|
16
20
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperlink Markdown Component
|
|
3
|
+
*
|
|
4
|
+
* Extends Markdown component with OSC 8 hyperlink support.
|
|
5
|
+
* Automatically detects and converts URLs to clickable hyperlinks.
|
|
6
|
+
*/
|
|
7
|
+
import { Markdown } from "@mariozechner/pi-tui";
|
|
8
|
+
import { hyperlinkUrls } from "../utils/osc8-hyperlinks.js";
|
|
9
|
+
/**
|
|
10
|
+
* Markdown component with automatic hyperlink detection
|
|
11
|
+
*/
|
|
12
|
+
export class HyperlinkMarkdown extends Markdown {
|
|
13
|
+
constructor(text, padLeft, padRight, theme, options) {
|
|
14
|
+
// Convert URLs to hyperlinks before passing to parent
|
|
15
|
+
const textWithHyperlinks = hyperlinkUrls(text);
|
|
16
|
+
super(textWithHyperlinks, padLeft, padRight, theme, options);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Update text with hyperlink conversion
|
|
20
|
+
*/
|
|
21
|
+
setText(text) {
|
|
22
|
+
const textWithHyperlinks = hyperlinkUrls(text);
|
|
23
|
+
super.setText(textWithHyperlinks);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set raw text without hyperlink conversion
|
|
27
|
+
* Use this if you need to bypass automatic URL detection
|
|
28
|
+
*/
|
|
29
|
+
setRawText(text) {
|
|
30
|
+
super.setText(text);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { getEditorKeybindings, Input, isKeyRelease, matchesKey, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { LRUCache } from "../../utils/lru-cache.js";
|
|
2
3
|
import { visibleWidth } from "../../terminal/ansi.js";
|
|
3
4
|
import { findWordBoundaryIndex, fuzzyFilterLower, prepareSearchItems } from "./fuzzy-filter.js";
|
|
5
|
+
/**
|
|
6
|
+
* Maximum number of cached regex patterns to prevent memory leaks
|
|
7
|
+
*/
|
|
8
|
+
const MAX_REGEX_CACHE_SIZE = 100;
|
|
4
9
|
/**
|
|
5
10
|
* A select list with a search input at the top for fuzzy filtering.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Smart tiered filtering (exact > word-boundary > description > fuzzy)
|
|
14
|
+
* - LRU-cached regex patterns for performance
|
|
15
|
+
* - Keyboard navigation with vim-style bindings
|
|
6
16
|
*/
|
|
7
17
|
export class SearchableSelectList {
|
|
8
18
|
items;
|
|
@@ -11,7 +21,7 @@ export class SearchableSelectList {
|
|
|
11
21
|
maxVisible;
|
|
12
22
|
theme;
|
|
13
23
|
searchInput;
|
|
14
|
-
regexCache
|
|
24
|
+
regexCache;
|
|
15
25
|
onSelect;
|
|
16
26
|
onCancel;
|
|
17
27
|
onSelectionChange;
|
|
@@ -21,6 +31,7 @@ export class SearchableSelectList {
|
|
|
21
31
|
this.maxVisible = maxVisible;
|
|
22
32
|
this.theme = theme;
|
|
23
33
|
this.searchInput = new Input();
|
|
34
|
+
this.regexCache = new LRUCache({ maxSize: MAX_REGEX_CACHE_SIZE });
|
|
24
35
|
}
|
|
25
36
|
getCachedRegex(pattern) {
|
|
26
37
|
let regex = this.regexCache.get(pattern);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer } from "@mariozechner/pi-tui";
|
|
2
2
|
import { markdownTheme, theme } from "../theme/theme.js";
|
|
3
|
+
import { sanitizeRenderableText } from "../utils/text-sanitization.js";
|
|
3
4
|
export class UserMessageComponent extends Container {
|
|
4
5
|
body;
|
|
5
6
|
constructor(text) {
|
|
6
7
|
super();
|
|
7
|
-
|
|
8
|
+
// Sanitize text before rendering to prevent terminal corruption
|
|
9
|
+
const sanitizedText = sanitizeRenderableText(text);
|
|
10
|
+
this.body = new Markdown(sanitizedText, 1, 1, markdownTheme, {
|
|
8
11
|
bgColor: (line) => theme.userBg(line),
|
|
9
12
|
color: (line) => theme.userText(line),
|
|
10
13
|
});
|
|
@@ -12,6 +15,7 @@ export class UserMessageComponent extends Container {
|
|
|
12
15
|
this.addChild(this.body);
|
|
13
16
|
}
|
|
14
17
|
setText(text) {
|
|
15
|
-
|
|
18
|
+
// Sanitize text before updating
|
|
19
|
+
this.body.setText(sanitizeRenderableText(text));
|
|
16
20
|
}
|
|
17
21
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -88,7 +88,18 @@ export const SpinnerChars = {
|
|
|
88
88
|
dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
89
89
|
line: ["-", "\\", "|", "/"],
|
|
90
90
|
arrow: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
|
|
91
|
-
bounce: [
|
|
91
|
+
bounce: [
|
|
92
|
+
"( ● )",
|
|
93
|
+
"( ● )",
|
|
94
|
+
"( ● )",
|
|
95
|
+
"( ● )",
|
|
96
|
+
"( ●)",
|
|
97
|
+
"( ● )",
|
|
98
|
+
"( ● )",
|
|
99
|
+
"( ● )",
|
|
100
|
+
"( ● )",
|
|
101
|
+
"(● )",
|
|
102
|
+
],
|
|
92
103
|
pulse: ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"],
|
|
93
104
|
};
|
|
94
105
|
/**
|
|
@@ -386,7 +397,7 @@ export class Table {
|
|
|
386
397
|
});
|
|
387
398
|
}
|
|
388
399
|
renderHorizontalLine(left, right, horizontal, cross, widths) {
|
|
389
|
-
const segments = widths.map(w => horizontal.repeat(w));
|
|
400
|
+
const segments = widths.map((w) => horizontal.repeat(w));
|
|
390
401
|
return left + segments.join(cross) + right;
|
|
391
402
|
}
|
|
392
403
|
renderRow(cells, vertical) {
|
|
@@ -466,7 +477,7 @@ export class Prompts {
|
|
|
466
477
|
* Select from list prompt
|
|
467
478
|
*/
|
|
468
479
|
static async select(options) {
|
|
469
|
-
const available = options.choices.filter(c => !c.disabled);
|
|
480
|
+
const available = options.choices.filter((c) => !c.disabled);
|
|
470
481
|
console.log(options.message);
|
|
471
482
|
available.forEach((choice, i) => {
|
|
472
483
|
const marker = choice.value === options.default ? colorize("›", Colors.cyan) : " ";
|
|
@@ -482,7 +493,7 @@ export class Prompts {
|
|
|
482
493
|
return available[num - 1].value;
|
|
483
494
|
}
|
|
484
495
|
// Try matching by value
|
|
485
|
-
const match = available.find(c => c.value.toLowerCase() === answer.trim().toLowerCase());
|
|
496
|
+
const match = available.find((c) => c.value.toLowerCase() === answer.trim().toLowerCase());
|
|
486
497
|
if (match)
|
|
487
498
|
return match.value;
|
|
488
499
|
console.log(colorize("Invalid selection", Colors.red));
|
|
@@ -515,7 +526,7 @@ export function logStatus(status, message) {
|
|
|
515
526
|
export function box(content, options = {}) {
|
|
516
527
|
const caps = detectCapabilities();
|
|
517
528
|
const lines = content.split("\n");
|
|
518
|
-
const maxLineLen = Math.max(...lines.map(l => l.length));
|
|
529
|
+
const maxLineLen = Math.max(...lines.map((l) => l.length));
|
|
519
530
|
const width = options.width ?? maxLineLen + 4;
|
|
520
531
|
const padding = options.padding ?? 1;
|
|
521
532
|
const borders = {
|
|
@@ -533,7 +544,12 @@ export function box(content, options = {}) {
|
|
|
533
544
|
: options.title;
|
|
534
545
|
const titleWithPadding = ` ${title} `;
|
|
535
546
|
const before = Math.floor((width - titleWithPadding.length) / 2);
|
|
536
|
-
topBorder =
|
|
547
|
+
topBorder =
|
|
548
|
+
borders.tl +
|
|
549
|
+
borders.h.repeat(before) +
|
|
550
|
+
titleWithPadding +
|
|
551
|
+
borders.h.repeat(width - before - titleWithPadding.length - 2) +
|
|
552
|
+
borders.tr;
|
|
537
553
|
}
|
|
538
554
|
result.push(topBorder);
|
|
539
555
|
// Padding top
|