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:
2026-10-05 17:01:05 +07:00
parent 442f133930
commit 198fbb7980
78 changed files with 2716 additions and 873 deletions
+3 -2
View File
@@ -751,7 +751,9 @@ export function LaneDiffView({
const [anchor, setAnchor] = useState<{ top: number; left: number } | null>(null);
const place = useCallback(() => {
const r = anchorEl.current?.isConnected ? anchorEl.current.getBoundingClientRect() : null;
if (r) setAnchor({ top: r.bottom + 2, left: r.left });
// A long diff unmounts rows scrolled out of its window, the clicked number
// with them: the box docks under the diff rather than float over other lines.
setAnchor(r ? { top: r.bottom + 2, left: r.left } : null);
}, []);
useEffect(() => {
if (commentAt == null) return;
@@ -764,7 +766,6 @@ export function LaneDiffView({
? {
open: (line: number, el?: HTMLElement) => {
anchorEl.current = el ?? null;
setAnchor(null);
place();
setCommentAt(line);
setUndelivered(false);
@@ -17,7 +17,8 @@
* any other session opens in its workspace too (`?lane=<id>&session=<id>`,
* `SessionReplay`): its conversation with a prompt box, resumed only when a
* message is SENT, because a resume re-reads the whole transcript into context.
* The list shows `SESSION_LIMIT` rows and a **Show N more** under them. A
* The list shows `SESSION_LIMIT` rows and a **See all (N more)** under them that
* loads every one. A
* live tab whose session was archived is not brought back as a New session,
* and archiving a live one asks first. F2 renames the focused row too; each
* dot carries its meaning as screen-reader text.
@@ -39,7 +40,7 @@ import { useAiTools } from "../../hooks/useAiTools";
import { useContextMenu } from "../ContextMenu";
import { SessionMenu } from "./SessionMenu";
export const SESSION_LIMIT = 20;
export const SESSION_LIMIT = 6;
/**
* The live pane holding a session: the run the hooks bound it to
@@ -244,7 +245,7 @@ export function WorkspaceSessionList({
const [busy, setBusy] = useState(false);
const ctx = useContextMenu<RunHandle>();
const sessionCtx = useContextMenu<Session>();
// Rows shown; Show more adds a page.
// Rows shown; See all raises it to the whole list.
const [limit, setLimit] = useState(SESSION_LIMIT);
// This workspace's live AI tabs. A tab started bare (no prompt) has its
@@ -525,10 +526,10 @@ export function WorkspaceSessionList({
<button
type="button"
data-testid={`workspace-sessions-more-${lane.id}`}
onClick={() => setLimit((n) => n + SESSION_LIMIT)}
onClick={() => own && setLimit(own.total)}
className="w-full py-1 pl-8 pr-2 text-left text-[11px] text-fg-muted hover:text-fg-primary"
>
{t("tree.showMore", { count: Math.min(more, SESSION_LIMIT) })}
{t("tree.seeAll", { count: more })}
</button>
)}
{failure}
@@ -528,6 +528,17 @@ describe("LaneDiffView — line comments", () => {
expect(screen.queryByTestId("diff-comment-box")).toBeNull();
});
it("docks the box under the diff once its line scrolls out of a windowed diff", () => {
render(<LaneDiffView file={FILE} layout="unified" onComment={() => true} />);
const btn = screen.getByTestId("diff-comment-line-1");
fireEvent.click(btn);
expect(screen.getByTestId("diff-comment-box").style.position).toBe("fixed");
// What windowing does to a row scrolled out of range.
btn.remove();
fireEvent.scroll(screen.getByTestId("lane-diff-view"));
expect(screen.getByTestId("diff-comment-box").style.position).toBe("");
});
it("offers no comment button without onComment", () => {
render(<LaneDiffView file={FILE} layout="unified" />);
expect(screen.queryByTestId("diff-comment-line-1")).toBeNull();