fix: event-loop freezes, plugin-install boot crash, tmux attach/scrollbar, Codex transcripts
- GET /api/workflows: one LEAD() window pass in a read worker (read-pool.js, workflow-stats.js); idx_events_session restored, idx_events_tool added - compat-sqlite gains function/pluck/readonly so a plugin install without a better-sqlite3 build boots again; antigravity-usage uses db.js's Database - install-hooks adds no checkout hooks while the agenthub plugin is enabled (events were doubled) - terminal attach runs the live tmux server's own binary; the pane scrollbar gets a visible 2-column style, and claude panes start with CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 so tmux keeps history to scroll - Codex sessions' conversation in GET /api/sessions/:id/transcript - Codex/Antigravity sessions claimed by their workspace without hooks - UI-audit follow-ups, docs and OpenAPI
This commit is contained in:
@@ -108,6 +108,8 @@ export function TerminalView({ runId, wsBaseUrl, onExit, autoFocus = true }: Ter
|
||||
const paletteRef = useRef(palette);
|
||||
paletteRef.current = palette;
|
||||
const termRef = useRef<Terminal | null>(null);
|
||||
// The current attach: a paste into a socket that is not open would be dropped.
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const { t } = useTranslation("run");
|
||||
const tRef = useRef(t);
|
||||
tRef.current = t;
|
||||
@@ -319,6 +321,7 @@ export function TerminalView({ runId, wsBaseUrl, onExit, autoFocus = true }: Ter
|
||||
const size = term.cols > 1 && term.rows > 1 ? `?cols=${term.cols}&rows=${term.rows}` : "";
|
||||
const sock = new WebSocket(`${wsBaseUrl}/ws-pty/${encodeURIComponent(runId)}${size}`);
|
||||
ws = sock;
|
||||
wsRef.current = sock;
|
||||
// Server sends PTY bytes as binary frames — default binaryType ("blob")
|
||||
// would hand onmessage a Blob that the string checks below never match,
|
||||
// silently dropping all terminal output. "arraybuffer" keeps it sync.
|
||||
@@ -432,8 +435,10 @@ export function TerminalView({ runId, wsBaseUrl, onExit, autoFocus = true }: Ter
|
||||
termRef.current?.paste(text);
|
||||
termRef.current?.focus();
|
||||
},
|
||||
// On screen: a console hidden with CSS measures 0×0.
|
||||
() => !!containerRef.current?.offsetWidth
|
||||
// On screen (a console hidden with CSS measures 0×0) AND attached:
|
||||
// `onData` drops input while the socket is connecting or reconnecting,
|
||||
// so a caller waits or says the paste failed instead.
|
||||
() => !!containerRef.current?.offsetWidth && wsRef.current?.readyState === WebSocket.OPEN
|
||||
),
|
||||
[runId]
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, cleanup, act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { TerminalView } from "../TerminalView";
|
||||
import { pasteToRun } from "../../../lib/panePaste";
|
||||
|
||||
const writeMock = vi.fn();
|
||||
const onDataHandlers: Array<(d: string) => void> = [];
|
||||
@@ -288,6 +289,24 @@ describe("TerminalView", () => {
|
||||
expect(screen.getByTestId("terminal-reconnecting")).toHaveTextContent("Reconnecting");
|
||||
});
|
||||
|
||||
it("takes no paste while its socket is not open, so the text is not dropped unseen", () => {
|
||||
const width = vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockReturnValue(800);
|
||||
try {
|
||||
render(<TerminalView runId="agenthub-lane-7" wsBaseUrl="ws://localhost:4820" />);
|
||||
const ws = MockWebSocket.instances[0]!;
|
||||
ws.readyState = 0; // CONNECTING
|
||||
expect(pasteToRun("agenthub-lane-7", "hello")).toBe(false);
|
||||
ws.readyState = 1;
|
||||
act(() => ws.onopen?.());
|
||||
expect(pasteToRun("agenthub-lane-7", "hello")).toBe(true);
|
||||
ws.readyState = 3; // CLOSED: Reconnecting…
|
||||
act(() => ws.onclose?.());
|
||||
expect(pasteToRun("agenthub-lane-7", "hello")).toBe(false);
|
||||
} finally {
|
||||
width.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("takes the keyboard as soon as it mounts", () => {
|
||||
// The terminal IS the page here — without this the first thing typed after
|
||||
// opening a lane went nowhere until the user clicked the pane.
|
||||
|
||||
Reference in New Issue
Block a user