bosun 0.36.2 → 0.36.3
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/analyze-agent-work-helpers.mjs +308 -0
- package/analyze-agent-work.mjs +926 -0
- package/autofix.mjs +2 -0
- package/codex-shell.mjs +85 -10
- package/git-editor-fix.mjs +273 -0
- package/mcp-registry.mjs +579 -0
- package/meeting-workflow-service.mjs +631 -0
- package/monitor.mjs +18 -103
- package/package.json +13 -2
- package/primary-agent.mjs +32 -12
- package/session-tracker.mjs +68 -0
- package/stream-resilience.mjs +17 -7
- package/ui/app.js +19 -4
- package/ui/components/chat-view.js +108 -5
- package/ui/components/session-list.js +1 -1
- package/ui/components/shared.js +188 -15
- package/ui/modules/icons.js +13 -0
- package/ui/modules/utils.js +44 -0
- package/ui/modules/voice.js +15 -6
- package/ui/styles/components.css +99 -3
- package/ui/styles/sessions.css +84 -12
- package/ui/tabs/chat.js +5 -1
- package/ui/tabs/control.js +16 -22
- package/ui/tabs/dashboard.js +85 -8
- package/ui/tabs/library.js +113 -17
- package/ui/tabs/settings.js +116 -2
- package/ui/tabs/tasks.js +388 -39
- package/ui/tabs/telemetry.js +0 -1
- package/ui/tabs/workflows.js +4 -0
- package/ui-server.mjs +193 -19
- package/update-check.mjs +41 -13
- package/voice-relay.mjs +816 -0
- package/voice-tools.mjs +679 -0
- package/workflow-templates/agents.mjs +6 -2
- package/workflow-templates/github.mjs +154 -12
- package/workflow-templates.mjs +3 -0
- package/github-reconciler.mjs +0 -506
- package/merge-strategy.mjs +0 -1210
- package/pr-cleanup-daemon.mjs +0 -992
- package/workspace-reaper.mjs +0 -405
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { classifyComplexity } from "./task-complexity.mjs";
|
|
2
|
+
|
|
3
|
+
export function normalizeTimestamp(value) {
|
|
4
|
+
const ts = Date.parse(value);
|
|
5
|
+
return Number.isFinite(ts) ? ts : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function normalizeErrorFingerprint(message) {
|
|
9
|
+
const text = String(message || "unknown").trim().toLowerCase();
|
|
10
|
+
if (!text) return "unknown";
|
|
11
|
+
return text
|
|
12
|
+
.replace(/0x[0-9a-f]+/gi, "0x#")
|
|
13
|
+
.replace(/\d+(?:\.\d+)?/g, "#")
|
|
14
|
+
.replace(/\s+/g, " ")
|
|
15
|
+
.slice(0, 120);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function groupBy(array, keyFn) {
|
|
19
|
+
const groups = {};
|
|
20
|
+
for (const item of array) {
|
|
21
|
+
const key = typeof keyFn === "function" ? keyFn(item) : item[keyFn];
|
|
22
|
+
if (!groups[key]) groups[key] = [];
|
|
23
|
+
groups[key].push(item);
|
|
24
|
+
}
|
|
25
|
+
return groups;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function average(numbers) {
|
|
29
|
+
if (numbers.length === 0) return 0;
|
|
30
|
+
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function incrementCounter(target, key) {
|
|
34
|
+
const resolved = key || "unknown";
|
|
35
|
+
target[resolved] = (target[resolved] || 0) + 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveKnownValue(...values) {
|
|
39
|
+
for (const value of values) {
|
|
40
|
+
if (value === null || value === undefined) continue;
|
|
41
|
+
const text = String(value).trim();
|
|
42
|
+
if (!text) continue;
|
|
43
|
+
if (text.toLowerCase() === "unknown") continue;
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildDistribution(counts, total) {
|
|
50
|
+
return Object.entries(counts)
|
|
51
|
+
.map(([label, count]) => ({
|
|
52
|
+
label,
|
|
53
|
+
count,
|
|
54
|
+
percent: total > 0 ? (count * 100.0) / total : 0,
|
|
55
|
+
}))
|
|
56
|
+
.sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const SIZE_LABEL_PATTERN = /\[(xs|s|m|l|xl|xxl|2xl)\]/i;
|
|
60
|
+
|
|
61
|
+
function extractSizeLabelFromTitle(title) {
|
|
62
|
+
const text = String(title || "");
|
|
63
|
+
const match = text.match(SIZE_LABEL_PATTERN);
|
|
64
|
+
if (!match) return "unknown";
|
|
65
|
+
const label = match[1].toLowerCase();
|
|
66
|
+
return label === "2xl" ? "xxl" : label;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function classifyComplexityBucket({ sizeLabel, title, description }) {
|
|
70
|
+
if (!description) return "unknown";
|
|
71
|
+
const normalizedSize = sizeLabel && sizeLabel !== "unknown" ? sizeLabel : null;
|
|
72
|
+
const result = classifyComplexity({
|
|
73
|
+
sizeLabel: normalizedSize,
|
|
74
|
+
title: title || "",
|
|
75
|
+
description: description || "",
|
|
76
|
+
});
|
|
77
|
+
return result?.tier || "unknown";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveNowTimestamp(now) {
|
|
81
|
+
if (now instanceof Date) return now.getTime();
|
|
82
|
+
if (typeof now === "number" && Number.isFinite(now)) return now;
|
|
83
|
+
if (typeof now === "string") {
|
|
84
|
+
const parsed = normalizeTimestamp(now);
|
|
85
|
+
if (parsed !== null) return parsed;
|
|
86
|
+
}
|
|
87
|
+
return Date.now();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function filterRecordsByWindow(
|
|
91
|
+
records,
|
|
92
|
+
{ days, now, timestampKey = "timestamp" } = {},
|
|
93
|
+
) {
|
|
94
|
+
if (!Array.isArray(records)) return [];
|
|
95
|
+
const windowDays = Number.isFinite(days) && days > 0 ? days : null;
|
|
96
|
+
if (!windowDays) return [...records];
|
|
97
|
+
const cutoff = resolveNowTimestamp(now) - windowDays * 24 * 60 * 60 * 1000;
|
|
98
|
+
return records.filter((record) => {
|
|
99
|
+
const ts = normalizeTimestamp(record?.[timestampKey]);
|
|
100
|
+
if (ts === null) return true;
|
|
101
|
+
return ts >= cutoff;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildErrorClusters(errors) {
|
|
106
|
+
if (!Array.isArray(errors) || errors.length === 0) return [];
|
|
107
|
+
|
|
108
|
+
const byFingerprint = groupBy(
|
|
109
|
+
errors,
|
|
110
|
+
(e) =>
|
|
111
|
+
e.data?.error_fingerprint ||
|
|
112
|
+
normalizeErrorFingerprint(e.data?.error_message),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return Object.entries(byFingerprint)
|
|
116
|
+
.map(([fingerprint, events]) => ({
|
|
117
|
+
fingerprint,
|
|
118
|
+
count: events.length,
|
|
119
|
+
affected_tasks: new Set(events.map((e) => e.task_id)).size,
|
|
120
|
+
affected_attempts: new Set(events.map((e) => e.attempt_id)).size,
|
|
121
|
+
first_seen: events[0].timestamp,
|
|
122
|
+
last_seen: events[events.length - 1].timestamp,
|
|
123
|
+
sample_message: events[0].data?.error_message || "",
|
|
124
|
+
categories: [
|
|
125
|
+
...new Set(events.map((e) => e.data?.error_category).filter(Boolean)),
|
|
126
|
+
],
|
|
127
|
+
}))
|
|
128
|
+
.sort((a, b) => b.count - a.count);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildTaskProfiles(metrics, errors) {
|
|
132
|
+
const taskProfiles = new Map();
|
|
133
|
+
const ensureTaskProfile = (taskId) => {
|
|
134
|
+
const key = taskId || "unknown";
|
|
135
|
+
if (!taskProfiles.has(key)) {
|
|
136
|
+
taskProfiles.set(key, {
|
|
137
|
+
task_id: key,
|
|
138
|
+
task_title: "",
|
|
139
|
+
task_description: "",
|
|
140
|
+
executor: "unknown",
|
|
141
|
+
model: "unknown",
|
|
142
|
+
durations: [],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return taskProfiles.get(key);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (const metric of metrics) {
|
|
149
|
+
const profile = ensureTaskProfile(metric.task_id || "unknown");
|
|
150
|
+
if (metric.task_title && !profile.task_title) {
|
|
151
|
+
profile.task_title = metric.task_title;
|
|
152
|
+
}
|
|
153
|
+
if (metric.task_description && !profile.task_description) {
|
|
154
|
+
profile.task_description = metric.task_description;
|
|
155
|
+
}
|
|
156
|
+
if (metric.executor && profile.executor === "unknown") {
|
|
157
|
+
profile.executor = metric.executor;
|
|
158
|
+
}
|
|
159
|
+
if (metric.model && profile.model === "unknown") {
|
|
160
|
+
profile.model = metric.model;
|
|
161
|
+
}
|
|
162
|
+
if (metric.metrics?.duration_ms) {
|
|
163
|
+
profile.durations.push(metric.metrics.duration_ms);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const error of errors) {
|
|
168
|
+
const profile = ensureTaskProfile(error.task_id || "unknown");
|
|
169
|
+
if (error.task_title && !profile.task_title) {
|
|
170
|
+
profile.task_title = error.task_title;
|
|
171
|
+
}
|
|
172
|
+
if (error.task_description && !profile.task_description) {
|
|
173
|
+
profile.task_description = error.task_description;
|
|
174
|
+
}
|
|
175
|
+
if (error.executor && profile.executor === "unknown") {
|
|
176
|
+
profile.executor = error.executor;
|
|
177
|
+
}
|
|
178
|
+
if (error.model && profile.model === "unknown") {
|
|
179
|
+
profile.model = error.model;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const profile of taskProfiles.values()) {
|
|
184
|
+
profile.size_label = extractSizeLabelFromTitle(profile.task_title);
|
|
185
|
+
profile.complexity = classifyComplexityBucket({
|
|
186
|
+
sizeLabel: profile.size_label,
|
|
187
|
+
title: profile.task_title,
|
|
188
|
+
description: profile.task_description,
|
|
189
|
+
});
|
|
190
|
+
profile.avg_duration_ms =
|
|
191
|
+
profile.durations.length > 0 ? average(profile.durations) : 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return taskProfiles;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildErrorCorrelationEntries(errors, taskProfiles) {
|
|
198
|
+
const correlations = new Map();
|
|
199
|
+
const ensureCorrelation = (fingerprint) => {
|
|
200
|
+
if (!correlations.has(fingerprint)) {
|
|
201
|
+
correlations.set(fingerprint, {
|
|
202
|
+
fingerprint,
|
|
203
|
+
count: 0,
|
|
204
|
+
task_ids: new Set(),
|
|
205
|
+
by_executor: {},
|
|
206
|
+
by_size: {},
|
|
207
|
+
by_complexity: {},
|
|
208
|
+
sample_message: "",
|
|
209
|
+
first_seen_ts: null,
|
|
210
|
+
last_seen_ts: null,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return correlations.get(fingerprint);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
for (const error of errors) {
|
|
217
|
+
const fingerprint =
|
|
218
|
+
error.data?.error_fingerprint ||
|
|
219
|
+
normalizeErrorFingerprint(error.data?.error_message);
|
|
220
|
+
const entry = ensureCorrelation(fingerprint);
|
|
221
|
+
entry.count += 1;
|
|
222
|
+
entry.task_ids.add(error.task_id || "unknown");
|
|
223
|
+
if (!entry.sample_message && error.data?.error_message) {
|
|
224
|
+
entry.sample_message = error.data.error_message;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const timestamp = normalizeTimestamp(error.timestamp);
|
|
228
|
+
if (timestamp !== null) {
|
|
229
|
+
if (!entry.first_seen_ts || timestamp < entry.first_seen_ts) {
|
|
230
|
+
entry.first_seen_ts = timestamp;
|
|
231
|
+
}
|
|
232
|
+
if (!entry.last_seen_ts || timestamp > entry.last_seen_ts) {
|
|
233
|
+
entry.last_seen_ts = timestamp;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const profile = taskProfiles.get(error.task_id || "unknown");
|
|
238
|
+
const sizeLabel = resolveKnownValue(
|
|
239
|
+
profile?.size_label,
|
|
240
|
+
extractSizeLabelFromTitle(error.task_title),
|
|
241
|
+
);
|
|
242
|
+
const executor = resolveKnownValue(profile?.executor, error.executor);
|
|
243
|
+
const complexity = resolveKnownValue(profile?.complexity);
|
|
244
|
+
|
|
245
|
+
incrementCounter(entry.by_size, sizeLabel);
|
|
246
|
+
incrementCounter(entry.by_executor, executor);
|
|
247
|
+
incrementCounter(entry.by_complexity, complexity);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return correlations;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function buildErrorCorrelationSummary({
|
|
254
|
+
errors = [],
|
|
255
|
+
metrics = [],
|
|
256
|
+
windowDays = 7,
|
|
257
|
+
top = 10,
|
|
258
|
+
} = {}) {
|
|
259
|
+
const windowDaysResolved =
|
|
260
|
+
Number.isFinite(windowDays) && windowDays > 0 ? windowDays : 7;
|
|
261
|
+
const topLimit = Number.isFinite(top) && top > 0 ? top : 10;
|
|
262
|
+
const errorList = Array.isArray(errors) ? errors : [];
|
|
263
|
+
const metricList = Array.isArray(metrics) ? metrics : [];
|
|
264
|
+
|
|
265
|
+
const taskProfiles = buildTaskProfiles(metricList, errorList);
|
|
266
|
+
const correlations = buildErrorCorrelationEntries(errorList, taskProfiles);
|
|
267
|
+
const sorted = [...correlations.values()].sort((a, b) => b.count - a.count);
|
|
268
|
+
const topEntries = sorted.slice(0, topLimit);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
window_days: windowDaysResolved,
|
|
272
|
+
total_errors: errorList.length,
|
|
273
|
+
total_fingerprints: correlations.size,
|
|
274
|
+
top: topLimit,
|
|
275
|
+
correlations: topEntries,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function buildErrorCorrelationJsonPayload(summary, { now } = {}) {
|
|
280
|
+
const safeSummary = summary && typeof summary === "object" ? summary : null;
|
|
281
|
+
const correlations = Array.isArray(safeSummary?.correlations)
|
|
282
|
+
? safeSummary.correlations
|
|
283
|
+
: [];
|
|
284
|
+
const generatedAt = new Date(resolveNowTimestamp(now)).toISOString();
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
generated_at: generatedAt,
|
|
288
|
+
window_days: safeSummary?.window_days ?? 7,
|
|
289
|
+
total_errors: safeSummary?.total_errors ?? 0,
|
|
290
|
+
total_fingerprints: safeSummary?.total_fingerprints ?? 0,
|
|
291
|
+
top: safeSummary?.top ?? correlations.length,
|
|
292
|
+
correlations: correlations.map((entry) => ({
|
|
293
|
+
fingerprint: entry.fingerprint,
|
|
294
|
+
count: entry.count,
|
|
295
|
+
task_count: entry.task_ids?.size || 0,
|
|
296
|
+
sample_message: entry.sample_message,
|
|
297
|
+
first_seen: entry.first_seen_ts
|
|
298
|
+
? new Date(entry.first_seen_ts).toISOString()
|
|
299
|
+
: null,
|
|
300
|
+
last_seen: entry.last_seen_ts
|
|
301
|
+
? new Date(entry.last_seen_ts).toISOString()
|
|
302
|
+
: null,
|
|
303
|
+
by_executor: buildDistribution(entry.by_executor, entry.count),
|
|
304
|
+
by_size: buildDistribution(entry.by_size, entry.count),
|
|
305
|
+
by_complexity: buildDistribution(entry.by_complexity, entry.count),
|
|
306
|
+
})),
|
|
307
|
+
};
|
|
308
|
+
}
|