fix(electrobun): complete all 16 Codex #3 findings
CRITICAL:
- Message persistence race: snapshot batchEnd before async save
- Double-start guard: startingProjects Set prevents concurrent launches
- Symlink path traversal: fs.realpathSync() in path-guard.ts
- Relay false success: connect() returns { ok, machineId, error }
HIGH:
- Session restore skips if active session exists
- Remote remove: new RPC, cleans backend map
- Task board poll token: stale responses discarded after drag-drop
- Health concurrent tools: toolsInFlight counter (was boolean)
- bttask transactions: delete wraps comments+task, addComment validates
- PTY buffer cleared on reconnect
- PTY large paste: chunked String.fromCharCode (8KB chunks)
- Sidecar max line: 10MB limit prevents unbounded memory
- btmsg authorization: group validation, channel membership checks
MEDIUM:
- Session retention: max 5 per project, purgeSession/untrackProject
- Relay IPv6: URL parser replaces string split
- PTY schema: fixed misleading base64 comment
This commit is contained in:
parent
c145e37316
commit
0f75cb8e32
12 changed files with 190 additions and 42 deletions
|
|
@ -46,6 +46,9 @@
|
|||
let draggedTaskId = $state<string | null>(null);
|
||||
let dragOverCol = $state<string | null>(null);
|
||||
|
||||
// Fix #7 (Codex audit): Poll token to discard stale responses
|
||||
let pollToken = $state(0);
|
||||
|
||||
// ── Derived ──────────────────────────────────────────────────────────
|
||||
|
||||
let tasksByCol = $derived(
|
||||
|
|
@ -58,8 +61,11 @@
|
|||
// ── Data fetching ────────────────────────────────────────────────────
|
||||
|
||||
async function loadTasks() {
|
||||
// Fix #7 (Codex audit): Capture token before async call, discard if stale
|
||||
const tokenAtStart = ++pollToken;
|
||||
try {
|
||||
const res = await appRpc.request['bttask.listTasks']({ groupId });
|
||||
if (tokenAtStart < pollToken) return; // Stale response — discard
|
||||
tasks = res.tasks;
|
||||
} catch (err) {
|
||||
console.error('[TaskBoard] loadTasks:', err);
|
||||
|
|
@ -98,6 +104,7 @@
|
|||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task || task.status === newStatus) return;
|
||||
|
||||
pollToken++; // Fix #7: Invalidate in-flight polls
|
||||
try {
|
||||
const res = await appRpc.request['bttask.updateTaskStatus']({
|
||||
taskId,
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ function ensureListeners() {
|
|||
}
|
||||
persistMessages(session);
|
||||
scheduleCleanup(session.sessionId, session.projectId);
|
||||
// Fix #14 (Codex audit): Enforce max sessions per project on completion
|
||||
enforceMaxSessions(session.projectId);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ interface ProjectTracker {
|
|||
projectId: string;
|
||||
lastActivityTs: number;
|
||||
lastToolName: string | null;
|
||||
toolInFlight: boolean;
|
||||
// Fix #8 (Codex audit): Counter instead of boolean for concurrent tools
|
||||
toolsInFlight: number;
|
||||
costSnapshots: Array<[number, number]>; // [timestamp, costUsd]
|
||||
totalTokens: number;
|
||||
totalCost: number;
|
||||
|
|
@ -87,7 +88,7 @@ function computeHealth(tracker: ProjectTracker, now: number): ProjectHealth {
|
|||
|
||||
if (tracker.status === 'inactive' || tracker.status === 'done' || tracker.status === 'error') {
|
||||
activityState = 'inactive';
|
||||
} else if (tracker.toolInFlight) {
|
||||
} else if (tracker.toolsInFlight > 0) {
|
||||
activityState = 'running';
|
||||
activeTool = tracker.lastToolName;
|
||||
} else {
|
||||
|
|
@ -125,7 +126,7 @@ export function trackProject(projectId: string): void {
|
|||
projectId,
|
||||
lastActivityTs: Date.now(),
|
||||
lastToolName: null,
|
||||
toolInFlight: false,
|
||||
toolsInFlight: 0,
|
||||
costSnapshots: [],
|
||||
totalTokens: 0,
|
||||
totalCost: 0,
|
||||
|
|
@ -142,7 +143,8 @@ export function recordActivity(projectId: string, toolName?: string): void {
|
|||
t.status = 'running';
|
||||
if (toolName !== undefined) {
|
||||
t.lastToolName = toolName;
|
||||
t.toolInFlight = true;
|
||||
// Fix #8 (Codex audit): Increment counter for concurrent tool tracking
|
||||
t.toolsInFlight++;
|
||||
}
|
||||
if (!tickInterval) startHealthTick();
|
||||
}
|
||||
|
|
@ -152,7 +154,8 @@ export function recordToolDone(projectId: string): void {
|
|||
const t = trackers.get(projectId);
|
||||
if (!t) return;
|
||||
t.lastActivityTs = Date.now();
|
||||
t.toolInFlight = false;
|
||||
// Fix #8 (Codex audit): Decrement counter, floor at 0
|
||||
t.toolsInFlight = Math.max(0, t.toolsInFlight - 1);
|
||||
}
|
||||
|
||||
/** Record a token/cost snapshot for burn rate calculation. */
|
||||
|
|
@ -220,6 +223,16 @@ function startHealthTick(): void {
|
|||
}, TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/** Untrack a project (e.g. when project is removed). Fix #14 (Codex audit). */
|
||||
export function untrackProject(projectId: string): void {
|
||||
trackers.delete(projectId);
|
||||
// Stop tick if no more tracked projects
|
||||
if (trackers.size === 0 && tickInterval) {
|
||||
clearInterval(tickInterval);
|
||||
tickInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the health tick timer. */
|
||||
export function stopHealthTick(): void {
|
||||
if (tickInterval) {
|
||||
|
|
|
|||
|
|
@ -84,10 +84,11 @@
|
|||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Fix #6 (Codex audit): Use remote.remove RPC that disconnects AND deletes
|
||||
async function handleRemove(machineId: string) {
|
||||
try {
|
||||
await appRpc.request['remote.disconnect']({ machineId });
|
||||
} catch { /* may already be disconnected */ }
|
||||
await appRpc.request['remote.remove']({ machineId });
|
||||
} catch { /* may already be disconnected/removed */ }
|
||||
removeMachine(machineId);
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +132,9 @@
|
|||
<span class="status-text" style:color={statusColor(m.status)}>
|
||||
{statusLabel(m.status)}
|
||||
</span>
|
||||
{#if m.error}
|
||||
<span class="machine-error" title={m.error}>{m.error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="machine-actions">
|
||||
{#if m.status === 'connected'}
|
||||
|
|
@ -239,6 +243,7 @@
|
|||
}
|
||||
|
||||
.status-text { font-size: 0.6875rem; font-weight: 500; }
|
||||
.machine-error { font-size: 0.625rem; color: var(--ctp-red); max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.machine-actions { display: flex; gap: 0.25rem; flex-shrink: 0; }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue