codebot-ai 1.8.0 → 1.9.0
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/dist/agent.d.ts +8 -0
- package/dist/agent.js +46 -3
- package/dist/cli.js +83 -4
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -1
- package/dist/metrics.d.ts +60 -0
- package/dist/metrics.js +296 -0
- package/dist/risk.d.ts +52 -0
- package/dist/risk.js +367 -0
- package/dist/sarif.d.ts +82 -0
- package/dist/sarif.js +176 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MetricsCollector for CodeBot v1.9.0
|
|
4
|
+
*
|
|
5
|
+
* Structured telemetry: counters + histograms.
|
|
6
|
+
* Persists to ~/.codebot/telemetry/metrics-YYYY-MM-DD.jsonl
|
|
7
|
+
* Optional OTLP HTTP export when OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
|
8
|
+
*
|
|
9
|
+
* Pattern: fail-safe, session-scoped, never throws.
|
|
10
|
+
* Follows TokenTracker conventions from src/telemetry.ts.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.MetricsCollector = void 0;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const http = __importStar(require("http"));
|
|
51
|
+
const https = __importStar(require("https"));
|
|
52
|
+
// ── Helpers ──
|
|
53
|
+
/** Encode a metric key: name|label1=val1|label2=val2 (sorted labels) */
|
|
54
|
+
function encodeKey(name, labels) {
|
|
55
|
+
if (!labels || Object.keys(labels).length === 0)
|
|
56
|
+
return name;
|
|
57
|
+
const sorted = Object.entries(labels)
|
|
58
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
59
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
60
|
+
.join('|');
|
|
61
|
+
return `${name}|${sorted}`;
|
|
62
|
+
}
|
|
63
|
+
/** Decode a metric key back to name + labels */
|
|
64
|
+
function decodeKey(key) {
|
|
65
|
+
const parts = key.split('|');
|
|
66
|
+
const name = parts[0];
|
|
67
|
+
const labels = {};
|
|
68
|
+
for (let i = 1; i < parts.length; i++) {
|
|
69
|
+
const eq = parts[i].indexOf('=');
|
|
70
|
+
if (eq > 0) {
|
|
71
|
+
labels[parts[i].substring(0, eq)] = parts[i].substring(eq + 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { name, labels };
|
|
75
|
+
}
|
|
76
|
+
/** Compute percentile from sorted array */
|
|
77
|
+
function percentile(sorted, p) {
|
|
78
|
+
if (sorted.length === 0)
|
|
79
|
+
return 0;
|
|
80
|
+
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
81
|
+
return sorted[Math.max(0, idx)];
|
|
82
|
+
}
|
|
83
|
+
// ── Collector ──
|
|
84
|
+
const MAX_BUCKETS = 1000; // cap stored observations to prevent memory bloat
|
|
85
|
+
class MetricsCollector {
|
|
86
|
+
sessionId;
|
|
87
|
+
counters = new Map();
|
|
88
|
+
histograms = new Map();
|
|
89
|
+
constructor(sessionId) {
|
|
90
|
+
this.sessionId = sessionId || `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
91
|
+
}
|
|
92
|
+
getSessionId() {
|
|
93
|
+
return this.sessionId;
|
|
94
|
+
}
|
|
95
|
+
/** Increment a counter by delta (default 1) */
|
|
96
|
+
increment(name, labels, delta = 1) {
|
|
97
|
+
const key = encodeKey(name, labels);
|
|
98
|
+
this.counters.set(key, (this.counters.get(key) || 0) + delta);
|
|
99
|
+
}
|
|
100
|
+
/** Record a histogram observation */
|
|
101
|
+
observe(name, value, labels) {
|
|
102
|
+
const key = encodeKey(name, labels);
|
|
103
|
+
const existing = this.histograms.get(key);
|
|
104
|
+
if (existing) {
|
|
105
|
+
existing.count++;
|
|
106
|
+
existing.sum += value;
|
|
107
|
+
existing.min = Math.min(existing.min, value);
|
|
108
|
+
existing.max = Math.max(existing.max, value);
|
|
109
|
+
if (existing.buckets.length < MAX_BUCKETS) {
|
|
110
|
+
existing.buckets.push(value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.histograms.set(key, {
|
|
115
|
+
count: 1,
|
|
116
|
+
sum: value,
|
|
117
|
+
min: value,
|
|
118
|
+
max: value,
|
|
119
|
+
buckets: [value],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Read a counter value */
|
|
124
|
+
getCounter(name, labels) {
|
|
125
|
+
return this.counters.get(encodeKey(name, labels)) || 0;
|
|
126
|
+
}
|
|
127
|
+
/** Read a histogram summary */
|
|
128
|
+
getHistogram(name, labels) {
|
|
129
|
+
const key = encodeKey(name, labels);
|
|
130
|
+
const h = this.histograms.get(key);
|
|
131
|
+
if (!h)
|
|
132
|
+
return null;
|
|
133
|
+
const { name: n, labels: l } = decodeKey(key);
|
|
134
|
+
return { name: n, labels: l, ...h };
|
|
135
|
+
}
|
|
136
|
+
/** Full session snapshot */
|
|
137
|
+
snapshot() {
|
|
138
|
+
const counters = [];
|
|
139
|
+
for (const [key, value] of this.counters) {
|
|
140
|
+
const { name, labels } = decodeKey(key);
|
|
141
|
+
counters.push({ name, labels, value });
|
|
142
|
+
}
|
|
143
|
+
const histograms = [];
|
|
144
|
+
for (const [key, h] of this.histograms) {
|
|
145
|
+
const { name, labels } = decodeKey(key);
|
|
146
|
+
histograms.push({ name, labels, ...h });
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
sessionId: this.sessionId,
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
counters,
|
|
152
|
+
histograms,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Persist snapshot to ~/.codebot/telemetry/metrics-YYYY-MM-DD.jsonl */
|
|
156
|
+
save(sessionId) {
|
|
157
|
+
try {
|
|
158
|
+
const telemetryDir = path.join(os.homedir(), '.codebot', 'telemetry');
|
|
159
|
+
fs.mkdirSync(telemetryDir, { recursive: true });
|
|
160
|
+
const snap = this.snapshot();
|
|
161
|
+
if (sessionId)
|
|
162
|
+
snap.sessionId = sessionId;
|
|
163
|
+
const date = new Date().toISOString().split('T')[0];
|
|
164
|
+
const filePath = path.join(telemetryDir, `metrics-${date}.jsonl`);
|
|
165
|
+
fs.appendFileSync(filePath, JSON.stringify(snap) + '\n', 'utf-8');
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Telemetry failures are non-fatal
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Human-readable per-tool breakdown */
|
|
172
|
+
formatSummary() {
|
|
173
|
+
const lines = ['Metrics Summary', '─'.repeat(50)];
|
|
174
|
+
// Counters
|
|
175
|
+
const counterEntries = Array.from(this.counters.entries())
|
|
176
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
177
|
+
if (counterEntries.length > 0) {
|
|
178
|
+
lines.push('Counters:');
|
|
179
|
+
for (const [key, value] of counterEntries) {
|
|
180
|
+
const { name, labels } = decodeKey(key);
|
|
181
|
+
const labelStr = Object.keys(labels).length > 0
|
|
182
|
+
? ` {${Object.entries(labels).map(([k, v]) => `${k}="${v}"`).join(', ')}}`
|
|
183
|
+
: '';
|
|
184
|
+
lines.push(` ${name}${labelStr}: ${value}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Histograms — per-tool breakdown
|
|
188
|
+
const histEntries = Array.from(this.histograms.entries())
|
|
189
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
190
|
+
if (histEntries.length > 0) {
|
|
191
|
+
lines.push('Histograms:');
|
|
192
|
+
for (const [key, h] of histEntries) {
|
|
193
|
+
const { name, labels } = decodeKey(key);
|
|
194
|
+
const labelStr = Object.keys(labels).length > 0
|
|
195
|
+
? ` {${Object.entries(labels).map(([k, v]) => `${k}="${v}"`).join(', ')}}`
|
|
196
|
+
: '';
|
|
197
|
+
const sorted = [...h.buckets].sort((a, b) => a - b);
|
|
198
|
+
const avg = h.count > 0 ? (h.sum / h.count).toFixed(3) : '0';
|
|
199
|
+
const p50 = percentile(sorted, 50).toFixed(3);
|
|
200
|
+
const p95 = percentile(sorted, 95).toFixed(3);
|
|
201
|
+
const p99 = percentile(sorted, 99).toFixed(3);
|
|
202
|
+
lines.push(` ${name}${labelStr}: count=${h.count} avg=${avg} p50=${p50} p95=${p95} p99=${p99} min=${h.min.toFixed(3)} max=${h.max.toFixed(3)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (counterEntries.length === 0 && histEntries.length === 0) {
|
|
206
|
+
lines.push(' (no metrics recorded)');
|
|
207
|
+
}
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Export snapshot in OTLP JSON format via HTTP POST.
|
|
212
|
+
* Only fires when OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
|
213
|
+
* Fails silently — never blocks or crashes.
|
|
214
|
+
*/
|
|
215
|
+
exportOtel(snap) {
|
|
216
|
+
try {
|
|
217
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
218
|
+
if (!endpoint)
|
|
219
|
+
return;
|
|
220
|
+
const data = snap || this.snapshot();
|
|
221
|
+
const payload = this.buildOtlpPayload(data);
|
|
222
|
+
const url = new URL('/v1/metrics', endpoint);
|
|
223
|
+
const body = JSON.stringify(payload);
|
|
224
|
+
const mod = url.protocol === 'https:' ? https : http;
|
|
225
|
+
const req = mod.request(url, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: {
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
'Content-Length': Buffer.byteLength(body),
|
|
230
|
+
},
|
|
231
|
+
timeout: 5000,
|
|
232
|
+
});
|
|
233
|
+
req.on('error', () => { });
|
|
234
|
+
req.on('timeout', () => req.destroy());
|
|
235
|
+
req.write(body);
|
|
236
|
+
req.end();
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// OTLP export failures are non-fatal
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Build OTLP-compatible JSON payload */
|
|
243
|
+
buildOtlpPayload(snap) {
|
|
244
|
+
const metrics = [];
|
|
245
|
+
for (const counter of snap.counters) {
|
|
246
|
+
metrics.push({
|
|
247
|
+
name: counter.name,
|
|
248
|
+
sum: {
|
|
249
|
+
dataPoints: [{
|
|
250
|
+
asInt: counter.value,
|
|
251
|
+
attributes: Object.entries(counter.labels).map(([k, v]) => ({
|
|
252
|
+
key: k, value: { stringValue: v },
|
|
253
|
+
})),
|
|
254
|
+
timeUnixNano: Date.now() * 1_000_000,
|
|
255
|
+
}],
|
|
256
|
+
isMonotonic: true,
|
|
257
|
+
aggregationTemporality: 2, // CUMULATIVE
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
for (const hist of snap.histograms) {
|
|
262
|
+
metrics.push({
|
|
263
|
+
name: hist.name,
|
|
264
|
+
histogram: {
|
|
265
|
+
dataPoints: [{
|
|
266
|
+
count: hist.count,
|
|
267
|
+
sum: hist.sum,
|
|
268
|
+
min: hist.min,
|
|
269
|
+
max: hist.max,
|
|
270
|
+
attributes: Object.entries(hist.labels).map(([k, v]) => ({
|
|
271
|
+
key: k, value: { stringValue: v },
|
|
272
|
+
})),
|
|
273
|
+
timeUnixNano: Date.now() * 1_000_000,
|
|
274
|
+
}],
|
|
275
|
+
aggregationTemporality: 2,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
resourceMetrics: [{
|
|
281
|
+
resource: {
|
|
282
|
+
attributes: [
|
|
283
|
+
{ key: 'service.name', value: { stringValue: 'codebot' } },
|
|
284
|
+
{ key: 'session.id', value: { stringValue: snap.sessionId } },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
scopeMetrics: [{
|
|
288
|
+
scope: { name: 'codebot-metrics', version: '1.9.0' },
|
|
289
|
+
metrics,
|
|
290
|
+
}],
|
|
291
|
+
}],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.MetricsCollector = MetricsCollector;
|
|
296
|
+
//# sourceMappingURL=metrics.js.map
|
package/dist/risk.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RiskScorer for CodeBot v1.9.0
|
|
3
|
+
*
|
|
4
|
+
* Per-tool-call risk assessment using 6-factor weighted scoring (0–100).
|
|
5
|
+
* Factors: tool permission, file path sensitivity, command destructiveness,
|
|
6
|
+
* network access, data volume, cumulative session risk.
|
|
7
|
+
*
|
|
8
|
+
* Levels: green (0–25), yellow (26–50), orange (51–75), red (76+).
|
|
9
|
+
* NEVER throws — risk scoring failures must not crash the agent.
|
|
10
|
+
*/
|
|
11
|
+
export interface RiskAssessment {
|
|
12
|
+
score: number;
|
|
13
|
+
level: 'green' | 'yellow' | 'orange' | 'red';
|
|
14
|
+
factors: RiskFactor[];
|
|
15
|
+
}
|
|
16
|
+
export interface RiskFactor {
|
|
17
|
+
name: string;
|
|
18
|
+
weight: number;
|
|
19
|
+
rawScore: number;
|
|
20
|
+
weighted: number;
|
|
21
|
+
reason: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class RiskScorer {
|
|
24
|
+
private sessionHistory;
|
|
25
|
+
/**
|
|
26
|
+
* Assess risk for a tool call.
|
|
27
|
+
*
|
|
28
|
+
* @param toolName — name of the tool being invoked
|
|
29
|
+
* @param args — tool arguments
|
|
30
|
+
* @param permission — tool's effective permission level
|
|
31
|
+
*/
|
|
32
|
+
assess(toolName: string, args: Record<string, unknown>, permission?: 'auto' | 'prompt' | 'always-ask'): RiskAssessment;
|
|
33
|
+
/** Get all assessments from this session */
|
|
34
|
+
getHistory(): RiskAssessment[];
|
|
35
|
+
/** Get the session's cumulative average risk */
|
|
36
|
+
getSessionAverage(): number;
|
|
37
|
+
/** Format a colored risk indicator for CLI display */
|
|
38
|
+
static formatIndicator(assessment: RiskAssessment): string;
|
|
39
|
+
/** Factor 1: Tool permission level (weight 30) */
|
|
40
|
+
private scorePermissionLevel;
|
|
41
|
+
/** Factor 2: File path sensitivity (weight 20) */
|
|
42
|
+
private scoreFilePathSensitivity;
|
|
43
|
+
/** Factor 3: Command destructiveness (weight 20) */
|
|
44
|
+
private scoreCommandDestructiveness;
|
|
45
|
+
/** Factor 4: Network access (weight 15) */
|
|
46
|
+
private scoreNetworkAccess;
|
|
47
|
+
/** Factor 5: Data volume (weight 10) */
|
|
48
|
+
private scoreDataVolume;
|
|
49
|
+
/** Factor 6: Cumulative session risk (weight 5) */
|
|
50
|
+
private scoreCumulativeRisk;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=risk.d.ts.map
|