From 3225e6b4be9e1595a450ae1f30865f64701303dc Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 16 Jun 2026 23:03:11 +0200 Subject: [PATCH] fix(agent-hub-ui): corrected agent hub render flow in editorContainer - Fixed Agent Hub display to render in editorContainer instead of floating ui overlay. - Fixed close flow to clear editorContainer, re-add the editor, and restore editor focus. - Updated Agent Hub activation tests and removed the probe test fixture. --- .../modes/controllers/selector-controller.ts | 21 +++++---- .../test/agent-hub-activate.test.ts | 29 +++++++----- .../coding-agent/test/zz-hub-probe.test.ts | 45 ------------------- 3 files changed, 31 insertions(+), 64 deletions(-) delete mode 100644 packages/coding-agent/test/zz-hub-probe.test.ts diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index 37983afe8..e17165bb4 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -1216,11 +1216,20 @@ export class SelectorController { ...this.ctx.keybindings.getKeys("app.session.observe"), ]; let hub: AgentHubOverlayComponent | undefined; - let overlayHandle: OverlayHandle | undefined; + // Render the hub inline in the editor slot — the same anchored region + // every other selector (model, session, tree, the `ask` tool) uses — + // rather than a floating overlay. A non-fullscreen overlay composited over + // a live transcript strands a stale copy in native scrollback every time a + // running subagent's progress grows the frame and scrolls the window; the + // hub is opened mid-run, so those copies stacked into a wall of duplicate + // "Agent Hub" frames bleeding the task tree behind them. As an editor-slot + // component it rides the normal append-only commit path: the transcript + // commits above it exactly once and the hub repaints in place. const done = () => { hub?.dispose(); - overlayHandle?.hide(); + this.ctx.editorContainer.clear(); + this.ctx.editorContainer.addChild(this.ctx.editor); this.ctx.ui.setFocus(this.ctx.editor); this.ctx.ui.requestRender(); }; @@ -1251,12 +1260,8 @@ export class SelectorController { return; } - overlayHandle = this.ctx.ui.showOverlay(hub, { - anchor: "bottom-center", - width: "100%", - maxHeight: "100%", - margin: 0, - }); + this.ctx.editorContainer.clear(); + this.ctx.editorContainer.addChild(hub); this.ctx.ui.setFocus(hub); this.ctx.ui.requestRender(); } diff --git a/packages/coding-agent/test/agent-hub-activate.test.ts b/packages/coding-agent/test/agent-hub-activate.test.ts index 339d18298..5aaa593d0 100644 --- a/packages/coding-agent/test/agent-hub-activate.test.ts +++ b/packages/coding-agent/test/agent-hub-activate.test.ts @@ -125,18 +125,21 @@ describe("Agent hub Enter activation", () => { const editor = {}; let capturedHub: AgentHubOverlayComponent | undefined; - let hideCalls = 0; + let editorRestoredCount = 0; const focusedIds: string[] = []; const focusResolved = Promise.withResolvers(); const editorFocused = Promise.withResolvers(); const focusTargets: unknown[] = []; + const editorContainer = { + clear: () => {}, + addChild: (child: unknown) => { + if (child === editor) editorRestoredCount++; + else capturedHub = child as AgentHubOverlayComponent; + }, + }; const ctx = { keybindings: { getKeys: () => [] }, ui: { - showOverlay: (component: AgentHubOverlayComponent) => { - capturedHub = component; - return { hide: () => hideCalls++ }; - }, setFocus: (target: unknown) => { focusTargets.push(target); if (target === editor) editorFocused.resolve(); @@ -144,6 +147,7 @@ describe("Agent hub Enter activation", () => { requestRender: () => {}, }, editor, + editorContainer, collabGuest: { agentRegistry: agents, hubRemote: undefined }, focusAgentSession: async (id: string) => { focusedIds.push(id); @@ -165,7 +169,7 @@ describe("Agent hub Enter activation", () => { await editorFocused.promise; expect(focusedIds).toEqual([AGENT_ID]); - expect(hideCalls).toBe(1); + expect(editorRestoredCount).toBe(1); expect(focusTargets.at(-1)).toBe(editor); capturedHub!.dispose(); }); @@ -182,17 +186,20 @@ describe("Agent hub double-← gating", () => { function setup(agents: AgentRegistry) { let shown: AgentHubOverlayComponent | undefined; + const editor = {}; const ctx = { keybindings: { getKeys: () => [] }, ui: { - showOverlay: (component: AgentHubOverlayComponent) => { - shown = component; - return { hide: () => {} }; - }, setFocus: () => {}, requestRender: () => {}, }, - editor: {}, + editor, + editorContainer: { + clear: () => {}, + addChild: (child: unknown) => { + if (child !== editor) shown = child as AgentHubOverlayComponent; + }, + }, collabGuest: { agentRegistry: agents, hubRemote: undefined }, focusAgentSession: async () => {}, session: { getToolByName: () => undefined, extensionRunner: undefined }, diff --git a/packages/coding-agent/test/zz-hub-probe.test.ts b/packages/coding-agent/test/zz-hub-probe.test.ts deleted file mode 100644 index 579e106de..000000000 --- a/packages/coding-agent/test/zz-hub-probe.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { beforeAll, describe, it } from "bun:test"; -import { IrcBus } from "@oh-my-pi/pi-coding-agent/irc/bus"; -import { AgentHubOverlayComponent } from "@oh-my-pi/pi-coding-agent/modes/components/agent-hub"; -import { SessionObserverRegistry } from "@oh-my-pi/pi-coding-agent/modes/session-observer-registry"; -import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry"; -import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; - -describe("hub probe", () => { - beforeAll(async () => { - await initTheme(); - }); - - it("dumps rendered rows", () => { - Object.defineProperty(process.stdout, "rows", { configurable: true, get: () => 40 }); - Object.defineProperty(process.stdout, "columns", { configurable: true, get: () => 120 }); - const agents = new AgentRegistry(); - const sess = {} as AgentSession; - for (const letter of ["A", "B", "C", "D", "E"]) { - const id = `Repro${letter}`; - agents.register({ - id, - displayName: `Advisor delivery bug reproducer (independent track ${letter})`, - kind: "sub", - parentId: id, // self-parent, mimicking the bug - session: sess, - status: "running", - }); - } - const hub = new AgentHubOverlayComponent({ - observers: new SessionObserverRegistry(), - hubKeys: [], - onDone: () => {}, - requestRender: () => {}, - registry: agents, - irc: new IrcBus(agents), - focusAgent: async () => {}, - }); - const lines = hub.render(120).map(l => Bun.stripANSI(l)); - console.error("===HUB RENDER START==="); - for (const l of lines) console.error(JSON.stringify(l)); - console.error("===HUB RENDER END==="); - hub.dispose(); - }); -});