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
@@ -58,7 +58,7 @@ vi.mock("../../lib/api", async (importOriginal) => {
};
});
import { SettingsHub } from "../SettingsHub";
import { SettingsHub, backDelta } from "../SettingsHub";
import { __resetAiTools } from "../../hooks/useAiTools";
import { subscribeToasts, type ToastItem } from "../../lib/toast";
@@ -243,6 +243,21 @@ describe("SettingsHub", () => {
await vi.waitFor(() => expect(row.className).toContain("ring-2"));
});
it("a malformed #anchor is ignored instead of crashing the page", async () => {
renderAt("/settings/data#%E0%A4");
expect(await screen.findByTestId("settings-data")).toBeInTheDocument();
});
it("Back always goes back: one step past the entry, at least one step", () => {
// Entered at index 2, now two pages further on: back to index 1.
expect(backDelta(4, 2)).toBe(-3);
expect(backDelta(2, 2)).toBe(-1);
// Browser Back moved below the entry with the hub still mounted: one step,
// never history.go(0) (a reload) or a step forward.
expect(backDelta(1, 2)).toBe(-1);
expect(backDelta(0, 2)).toBe(-1);
});
it("toasts when an unknown section falls back to General", async () => {
let seen: ToastItem[] = [];
const off = subscribeToasts((i) => (seen = i));