feat(electrobun): complete all 10 hardening features

1. Durable event sequencing — monotonic seqId, deduplicate on restore
2. File-save conflict detection — mtime check, atomic temp-file rename
3. Remote credential vault — XOR-obfuscated tokens in settings-db
4. Push-based updates — bttask.changed/btmsg.newMessage events, 30s fallback
5. Sidecar backpressure — 50MB buffer cap, 64KB paste limit
6. Per-project retention — configurable count (1-20) + days (1-90)
7. Channel ACL — join/leave/members, membership validation on send
8. Transport diagnostics panel — PTY/relay/sidecar status, tool histogram
9. Plugin sandbox policy — allowedOrigins, maxRuntime (30s), network gate
10. Multi-tool health — activeToolMap, duration histogram, getActiveTools()
This commit is contained in:
Hibryda 2026-03-22 04:49:37 +01:00
parent 33f8f5d026
commit 54aad5f383

View file

@ -145,6 +145,7 @@ export function trackProject(projectId: string): void {
lastActivityTs: Date.now(),
lastToolName: null,
toolsInFlight: 0,
activeToolMap: new Map(),
costSnapshots: [],
totalTokens: 0,
totalCost: 0,
@ -163,6 +164,13 @@ export function recordActivity(projectId: string, toolName?: string): void {
t.lastToolName = toolName;
// Fix #8 (Codex audit): Increment counter for concurrent tool tracking
t.toolsInFlight++;
// Feature 10: Track per-tool starts
const existing = t.activeToolMap.get(toolName);
if (existing) {
existing.count++;
} else {
t.activeToolMap.set(toolName, { startTime: Date.now(), count: 1 });
}
}
if (!tickInterval) startHealthTick();
}
@ -171,9 +179,32 @@ export function recordActivity(projectId: string, toolName?: string): void {
export function recordToolDone(projectId: string): void {
const t = trackers.get(projectId);
if (!t) return;
t.lastActivityTs = Date.now();
const now = Date.now();
t.lastActivityTs = now;
// Fix #8 (Codex audit): Decrement counter, floor at 0
t.toolsInFlight = Math.max(0, t.toolsInFlight - 1);
// Feature 10: Record duration in histogram, remove from activeToolMap
if (t.lastToolName) {
const entry = t.activeToolMap.get(t.lastToolName);
if (entry) {
const durationMs = now - entry.startTime;
// Update global histogram
const hist = toolDurationHistogram.get(t.lastToolName);
if (hist) {
hist.totalMs += durationMs;
hist.count++;
} else {
toolDurationHistogram.set(t.lastToolName, {
toolName: t.lastToolName,
totalMs: durationMs,
count: 1,
});
}
entry.count--;
if (entry.count <= 0) t.activeToolMap.delete(t.lastToolName);
}
}
}
/** Record a token/cost snapshot for burn rate calculation. */
@ -233,6 +264,30 @@ export function getHealthAggregates(): {
return { running, idle, stalled, totalBurnRatePerHour };
}
/** Feature 10: Get all currently active tools across all projects. */
export function getActiveTools(): Array<{ projectId: string; toolName: string; startTime: number; count: number }> {
const results: Array<{ projectId: string; toolName: string; startTime: number; count: number }> = [];
for (const t of trackers.values()) {
for (const [name, entry] of t.activeToolMap) {
results.push({ projectId: t.projectId, toolName: name, startTime: entry.startTime, count: entry.count });
}
}
return results;
}
/** Feature 10: Get tool duration histogram for diagnostics. */
export function getToolHistogram(): Array<{ toolName: string; avgMs: number; count: number }> {
const results: Array<{ toolName: string; avgMs: number; count: number }> = [];
for (const entry of toolDurationHistogram.values()) {
results.push({
toolName: entry.toolName,
avgMs: entry.count > 0 ? entry.totalMs / entry.count : 0,
count: entry.count,
});
}
return results.sort((a, b) => b.count - a.count).slice(0, 15);
}
/** Start the health tick timer. */
function startHealthTick(): void {
if (tickInterval) return;