fix(electrobun): address all 22 Codex review #2 findings
CRITICAL:
- DocsTab XSS: DOMPurify sanitization on all {@html} output
- File RPC path traversal: guardPath() validates against project CWDs
HIGH:
- SSH injection: spawn /usr/bin/ssh via PTY args, no shell string
- Search XSS: strip HTML, highlight matches client-side with <mark>
- Terminal listener leak: cleanup functions stored + called in onDestroy
- FileBrowser race: request token, discard stale responses
- SearchOverlay race: same request token pattern
- App startup ordering: groups.list chains into active_group restore
- PtyClient timeout: 5-second auth timeout on connect()
- Rule 55: 6 {#if} patterns converted to style:display toggle
MEDIUM:
- Agent persistence: only persist NEW messages (lastPersistedIndex)
- Search errors: typed error response, "Invalid query" UI
- Health store wired: agent events call recordActivity/setProjectStatus
- index.ts SRP: split into 8 domain handler modules (298 lines)
- App.svelte: extracted workspace-store.svelte.ts
- rpc.ts: typed AppRpcHandle, removed `any`
LOW:
- CommandPalette listener wired in App.svelte
- Dead code removed (removeGroup, onDragStart, plugin loaded)
This commit is contained in:
parent
8e756d3523
commit
1cd4558740
28 changed files with 1342 additions and 1164 deletions
144
ui-electrobun/src/bun/handlers/agent-handlers.ts
Normal file
144
ui-electrobun/src/bun/handlers/agent-handlers.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Agent + Session persistence RPC handlers.
|
||||
*/
|
||||
|
||||
import type { SidecarManager } from "../sidecar-manager.ts";
|
||||
import type { SessionDb } from "../session-db.ts";
|
||||
|
||||
export function createAgentHandlers(
|
||||
sidecarManager: SidecarManager,
|
||||
sessionDb: SessionDb,
|
||||
sendToWebview: { send: Record<string, (...args: unknown[]) => void> },
|
||||
) {
|
||||
return {
|
||||
"agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv, additionalDirectories, worktreeName }: Record<string, unknown>) => {
|
||||
try {
|
||||
const result = sidecarManager.startSession(
|
||||
sessionId as string,
|
||||
provider as string,
|
||||
prompt as string,
|
||||
{ cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv, additionalDirectories, worktreeName } as Record<string, unknown>,
|
||||
);
|
||||
|
||||
if (result.ok) {
|
||||
sidecarManager.onMessage(sessionId as string, (sid: string, messages: unknown) => {
|
||||
try {
|
||||
(sendToWebview.send as Record<string, Function>)["agent.message"]({ sessionId: sid, messages });
|
||||
} catch (err) {
|
||||
console.error("[agent.message] forward error:", err);
|
||||
}
|
||||
});
|
||||
|
||||
sidecarManager.onStatus(sessionId as string, (sid: string, status: string, error?: string) => {
|
||||
try {
|
||||
(sendToWebview.send as Record<string, Function>)["agent.status"]({ sessionId: sid, status, error });
|
||||
} catch (err) {
|
||||
console.error("[agent.status] forward error:", err);
|
||||
}
|
||||
|
||||
const sessions = sidecarManager.listSessions();
|
||||
const session = sessions.find((s: Record<string, unknown>) => s.sessionId === sid);
|
||||
if (session) {
|
||||
try {
|
||||
(sendToWebview.send as Record<string, Function>)["agent.cost"]({
|
||||
sessionId: sid,
|
||||
costUsd: session.costUsd,
|
||||
inputTokens: session.inputTokens,
|
||||
outputTokens: session.outputTokens,
|
||||
});
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[agent.start]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"agent.stop": ({ sessionId }: { sessionId: string }) => {
|
||||
try {
|
||||
return sidecarManager.stopSession(sessionId);
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[agent.stop]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"agent.prompt": ({ sessionId, prompt }: { sessionId: string; prompt: string }) => {
|
||||
try {
|
||||
return sidecarManager.writePrompt(sessionId, prompt);
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[agent.prompt]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"agent.list": () => {
|
||||
try {
|
||||
return { sessions: sidecarManager.listSessions() };
|
||||
} catch (err) {
|
||||
console.error("[agent.list]", err);
|
||||
return { sessions: [] };
|
||||
}
|
||||
},
|
||||
|
||||
// Session persistence
|
||||
"session.save": ({ projectId, sessionId, provider, status, costUsd, inputTokens, outputTokens, model, error, createdAt, updatedAt }: Record<string, unknown>) => {
|
||||
try {
|
||||
sessionDb.saveSession({ projectId, sessionId, provider, status, costUsd, inputTokens, outputTokens, model, error, createdAt, updatedAt } as Record<string, unknown>);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[session.save]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
|
||||
"session.load": ({ projectId }: { projectId: string }) => {
|
||||
try {
|
||||
return { session: sessionDb.loadSession(projectId) };
|
||||
} catch (err) {
|
||||
console.error("[session.load]", err);
|
||||
return { session: null };
|
||||
}
|
||||
},
|
||||
|
||||
"session.list": ({ projectId }: { projectId: string }) => {
|
||||
try {
|
||||
return { sessions: sessionDb.listSessionsByProject(projectId) };
|
||||
} catch (err) {
|
||||
console.error("[session.list]", err);
|
||||
return { sessions: [] };
|
||||
}
|
||||
},
|
||||
|
||||
"session.messages.save": ({ messages }: { messages: Array<Record<string, unknown>> }) => {
|
||||
try {
|
||||
sessionDb.saveMessages(messages.map((m) => ({
|
||||
sessionId: m.sessionId, msgId: m.msgId, role: m.role,
|
||||
content: m.content, toolName: m.toolName, toolInput: m.toolInput,
|
||||
timestamp: m.timestamp, costUsd: (m.costUsd as number) ?? 0,
|
||||
inputTokens: (m.inputTokens as number) ?? 0, outputTokens: (m.outputTokens as number) ?? 0,
|
||||
})));
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.error("[session.messages.save]", err);
|
||||
return { ok: false };
|
||||
}
|
||||
},
|
||||
|
||||
"session.messages.load": ({ sessionId }: { sessionId: string }) => {
|
||||
try {
|
||||
return { messages: sessionDb.loadMessages(sessionId) };
|
||||
} catch (err) {
|
||||
console.error("[session.messages.load]", err);
|
||||
return { messages: [] };
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue