fix(electrobun): address all 20 Codex review findings
CRITICAL: - PTY leak: Terminal.svelte now calls pty.close on destroy, not just unsubscribe - Agent session cleanup: clearSession() removes done/error sessions, backend deletes after 60s grace period HIGH: - Clone branch passthrough: user's branch name flows through callback - Circular imports: extracted rpc.ts singleton, broke main.ts ↔ App.svelte cycle - Settings wired to runtime: Terminal reads cursor/scrollback from settings - Security disclaimer: added "prototype — not system keyring" notice - ThemeEditor: fixed basePalette → initialPalette reference MEDIUM: - Clone race: UUID suffix instead of count-based index - Silent failures: structured error returns from PTY handlers - WebKitGTK mount: only current + previous group mounted - Debug listeners: gated behind DEBUG, cleanup on destroy - NDJSON residual buffer parsed on process exit - Codex adapter: deduplicated tool_call/tool_result - extraEnv: rejects CLAUDE*/CODEX*/OLLAMA* keys - settings-db: runMigrations() with version tracking - active_group: persisted via settings.set LOW: - Removed dead demo code, unused variables - color-mix() fallbacks added
This commit is contained in:
parent
ef0183de7f
commit
29a3370e79
18 changed files with 331 additions and 114 deletions
|
|
@ -4,7 +4,7 @@
|
|||
import { CanvasAddon } from '@xterm/addon-canvas';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { ImageAddon } from '@xterm/addon-image';
|
||||
import { electrobun } from './main.ts';
|
||||
import { appRpc } from './rpc.ts';
|
||||
import { fontStore } from './font-store.svelte.ts';
|
||||
import { themeStore } from './theme-store.svelte.ts';
|
||||
import { getXtermTheme } from './themes.ts';
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
let fitAddon: FitAddon;
|
||||
let unsubFont: (() => void) | null = null;
|
||||
let ro: ResizeObserver | null = null;
|
||||
let destroyed = false;
|
||||
|
||||
/** Decode a base64 string from the daemon into a Uint8Array. */
|
||||
function decodeBase64(b64: string): Uint8Array {
|
||||
|
|
@ -60,20 +61,41 @@
|
|||
term.open(termEl);
|
||||
fitAddon.fit();
|
||||
|
||||
// ── Read cursor/scrollback settings ─────────────────────────────────
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const { settings } = await appRpc.request['settings.getAll']({});
|
||||
if (settings['cursor_style']) {
|
||||
const style = settings['cursor_style'];
|
||||
if (style === 'block' || style === 'underline' || style === 'bar') {
|
||||
term.options.cursorStyle = style;
|
||||
}
|
||||
}
|
||||
if (settings['cursor_blink'] === 'false') {
|
||||
term.options.cursorBlink = false;
|
||||
}
|
||||
if (settings['scrollback']) {
|
||||
const sb = parseInt(settings['scrollback'], 10);
|
||||
if (!isNaN(sb) && sb >= 0) term.options.scrollback = sb;
|
||||
}
|
||||
} catch { /* non-critical — use defaults */ }
|
||||
})();
|
||||
|
||||
// ── Subscribe to terminal font changes ─────────────────────────────────
|
||||
|
||||
unsubFont = fontStore.onTermFontChange((family: string, size: number) => {
|
||||
term.options.fontFamily = family || 'JetBrains Mono, Fira Code, monospace';
|
||||
term.options.fontSize = size;
|
||||
fitAddon.fit();
|
||||
electrobun.rpc?.request['pty.resize']({ sessionId, cols: term.cols, rows: term.rows }).catch(() => {});
|
||||
appRpc.request['pty.resize']({ sessionId, cols: term.cols, rows: term.rows }).catch(() => {});
|
||||
});
|
||||
|
||||
// ── Connect to PTY daemon (fire-and-forget from onMount) ───────────────
|
||||
|
||||
void (async () => {
|
||||
const { cols, rows } = term;
|
||||
const result = await electrobun.rpc?.request['pty.create']({ sessionId, cols, rows, cwd });
|
||||
const result = await appRpc.request['pty.create']({ sessionId, cols, rows, cwd });
|
||||
if (!result?.ok) {
|
||||
term.writeln(`\x1b[31m[agor] Failed to connect to PTY daemon: ${result?.error ?? 'unknown error'}\x1b[0m`);
|
||||
term.writeln('\x1b[33m[agor] Is agor-ptyd running? Start it with: agor-ptyd\x1b[0m');
|
||||
|
|
@ -82,20 +104,22 @@
|
|||
|
||||
// ── Receive output from daemon ─────────────────────────────────────────
|
||||
|
||||
electrobun.rpc?.addMessageListener('pty.output', ({ sessionId: sid, data }: { sessionId: string; data: string }) => {
|
||||
if (sid !== sessionId) return;
|
||||
const outputHandler = ({ sessionId: sid, data }: { sessionId: string; data: string }) => {
|
||||
if (destroyed || sid !== sessionId) return;
|
||||
term.write(decodeBase64(data));
|
||||
});
|
||||
};
|
||||
appRpc.addMessageListener('pty.output', outputHandler);
|
||||
|
||||
electrobun.rpc?.addMessageListener('pty.closed', ({ sessionId: sid, exitCode }: { sessionId: string; exitCode: number | null }) => {
|
||||
if (sid !== sessionId) return;
|
||||
const closedHandler = ({ sessionId: sid, exitCode }: { sessionId: string; exitCode: number | null }) => {
|
||||
if (destroyed || sid !== sessionId) return;
|
||||
term.writeln(`\r\n\x1b[90m[Process exited${exitCode !== null ? ` with code ${exitCode}` : ''}]\x1b[0m`);
|
||||
});
|
||||
};
|
||||
appRpc.addMessageListener('pty.closed', closedHandler);
|
||||
|
||||
// ── Send user input to daemon ──────────────────────────────────────────
|
||||
|
||||
term.onData((data: string) => {
|
||||
electrobun.rpc?.request['pty.write']({ sessionId, data }).catch((err: unknown) => {
|
||||
appRpc.request['pty.write']({ sessionId, data }).catch((err: unknown) => {
|
||||
console.error('[pty.write] error:', err);
|
||||
});
|
||||
});
|
||||
|
|
@ -103,7 +127,7 @@
|
|||
// ── Sync resize events to daemon ───────────────────────────────────────
|
||||
|
||||
term.onResize(({ cols: c, rows: r }) => {
|
||||
electrobun.rpc?.request['pty.resize']({ sessionId, cols: c, rows: r }).catch(() => {});
|
||||
appRpc.request['pty.resize']({ sessionId, cols: c, rows: r }).catch(() => {});
|
||||
});
|
||||
|
||||
ro = new ResizeObserver(() => { fitAddon.fit(); });
|
||||
|
|
@ -111,9 +135,11 @@
|
|||
});
|
||||
|
||||
onDestroy(() => {
|
||||
destroyed = true;
|
||||
unsubFont?.();
|
||||
ro?.disconnect();
|
||||
electrobun.rpc?.request['pty.unsubscribe']({ sessionId }).catch(() => {});
|
||||
// Fix #1: Close the PTY session (not just unsubscribe) to prevent session leak
|
||||
appRpc.request['pty.close']({ sessionId }).catch(() => {});
|
||||
term?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue