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:
parent
33f8f5d026
commit
54aad5f383
1 changed files with 56 additions and 1 deletions
|
|
@ -145,6 +145,7 @@ export function trackProject(projectId: string): void {
|
||||||
lastActivityTs: Date.now(),
|
lastActivityTs: Date.now(),
|
||||||
lastToolName: null,
|
lastToolName: null,
|
||||||
toolsInFlight: 0,
|
toolsInFlight: 0,
|
||||||
|
activeToolMap: new Map(),
|
||||||
costSnapshots: [],
|
costSnapshots: [],
|
||||||
totalTokens: 0,
|
totalTokens: 0,
|
||||||
totalCost: 0,
|
totalCost: 0,
|
||||||
|
|
@ -163,6 +164,13 @@ export function recordActivity(projectId: string, toolName?: string): void {
|
||||||
t.lastToolName = toolName;
|
t.lastToolName = toolName;
|
||||||
// Fix #8 (Codex audit): Increment counter for concurrent tool tracking
|
// Fix #8 (Codex audit): Increment counter for concurrent tool tracking
|
||||||
t.toolsInFlight++;
|
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();
|
if (!tickInterval) startHealthTick();
|
||||||
}
|
}
|
||||||
|
|
@ -171,9 +179,32 @@ export function recordActivity(projectId: string, toolName?: string): void {
|
||||||
export function recordToolDone(projectId: string): void {
|
export function recordToolDone(projectId: string): void {
|
||||||
const t = trackers.get(projectId);
|
const t = trackers.get(projectId);
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
t.lastActivityTs = Date.now();
|
const now = Date.now();
|
||||||
|
t.lastActivityTs = now;
|
||||||
// Fix #8 (Codex audit): Decrement counter, floor at 0
|
// Fix #8 (Codex audit): Decrement counter, floor at 0
|
||||||
t.toolsInFlight = Math.max(0, t.toolsInFlight - 1);
|
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. */
|
/** Record a token/cost snapshot for burn rate calculation. */
|
||||||
|
|
@ -233,6 +264,30 @@ export function getHealthAggregates(): {
|
||||||
return { running, idle, stalled, totalBurnRatePerHour };
|
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. */
|
/** Start the health tick timer. */
|
||||||
function startHealthTick(): void {
|
function startHealthTick(): void {
|
||||||
if (tickInterval) return;
|
if (tickInterval) return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue