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
+18 -7
View File
@@ -113,6 +113,18 @@ const PAGE_SIZE = 50;
// stream still refreshes, which a trailing debounce never did.
const REFRESH_THROTTLE_MS = 1000;
/** The agents on screen not asked about yet, and the sessions to ask for them. */
export function agentsToFetch(
events: { session_id: string; agent_id?: string | null }[],
asked: ReadonlySet<string>
): { agentIds: string[]; sessionIds: string[] } {
const fresh = events.filter((e) => e.agent_id && !asked.has(e.agent_id));
return {
agentIds: [...new Set(fresh.map((e) => e.agent_id as string))],
sessionIds: [...new Set(fresh.map((e) => e.session_id))],
};
}
export function ActivityFeed({ embedded = false }: { embedded?: boolean } = {}) {
const eventCtx = useContextMenu<DashboardEvent>();
const { t } = useTranslation("activity");
@@ -158,7 +170,7 @@ export function ActivityFeed({ embedded = false }: { embedded?: boolean } = {})
// agent_id → subagent-facing info for the agents of the sessions on screen,
// fetched in one call per new set of sessions (never every agent).
const [agentInfoById, setAgentInfoById] = useState<Map<string, AgentInfo>>(() => new Map());
const knownAgentSessions = useRef(new Set<string>());
const askedAgents = useRef(new Set<string>());
const pausedRef = useRef(paused);
pausedRef.current = paused;
@@ -223,13 +235,12 @@ export function ActivityFeed({ embedded = false }: { embedded?: boolean } = {})
load();
}, [load]);
// Agent labels for the sessions on screen that we have not seen yet.
// Agent labels for agents on screen not asked about yet — keyed by agent, so
// a subagent spawned later in a live session gets its label too.
useEffect(() => {
const missing = [...new Set(events.map((e) => e.session_id))].filter(
(sid) => !knownAgentSessions.current.has(sid)
);
const { agentIds, sessionIds: missing } = agentsToFetch(events, askedAgents.current);
if (missing.length === 0) return;
for (const sid of missing) knownAgentSessions.current.add(sid);
for (const id of agentIds) askedAgents.current.add(id);
api.agents
.list({ session_ids: missing, limit: 5000 })
.then(({ agents }) =>
@@ -247,7 +258,7 @@ export function ActivityFeed({ embedded = false }: { embedded?: boolean } = {})
)
.catch(() => {
// Non-fatal: subagent pills fall back to the short-id label.
for (const sid of missing) knownAgentSessions.current.delete(sid);
for (const id of agentIds) askedAgents.current.delete(id);
});
}, [events]);