refactor(coding-agent): polish agent hub menu
This commit is contained in:
@@ -110,6 +110,23 @@ const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL)
|
||||
|
||||
export class SelectorController {
|
||||
constructor(private ctx: InteractiveModeContext) {}
|
||||
/**
|
||||
* Mount a primary fullscreen menu through the one polished modal path shared
|
||||
* by Settings, Model Hub, and Agent Hub.
|
||||
*/
|
||||
#showFullscreenMenu(component: Component): OverlayHandle {
|
||||
const handle = this.ctx.ui.showOverlay(component, {
|
||||
anchor: "bottom-center",
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
margin: 0,
|
||||
fullscreen: true,
|
||||
});
|
||||
this.ctx.ui.setFocus(component);
|
||||
this.ctx.ui.requestRender();
|
||||
return handle;
|
||||
}
|
||||
|
||||
#defaultRoleMutationTail = Promise.resolve();
|
||||
|
||||
async #acquireDefaultRoleMutation(): Promise<() => void> {
|
||||
@@ -242,15 +259,7 @@ export class SelectorController {
|
||||
},
|
||||
},
|
||||
);
|
||||
overlayHandle = this.ctx.ui.showOverlay(selector, {
|
||||
anchor: "bottom-center",
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
margin: 0,
|
||||
fullscreen: true,
|
||||
});
|
||||
this.ctx.ui.setFocus(selector);
|
||||
this.ctx.ui.requestRender();
|
||||
overlayHandle = this.#showFullscreenMenu(selector);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1023,15 +1032,7 @@ export class SelectorController {
|
||||
initialProviderId: hubOptions.initialProviderId,
|
||||
},
|
||||
);
|
||||
overlayHandle = this.ctx.ui.showOverlay(hub, {
|
||||
anchor: "bottom-center",
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
margin: 0,
|
||||
fullscreen: true,
|
||||
});
|
||||
this.ctx.ui.setFocus(hub);
|
||||
this.ctx.ui.requestRender();
|
||||
overlayHandle = this.#showFullscreenMenu(hub);
|
||||
}
|
||||
|
||||
/** /login round-trip for a locked provider; reopen the hub on that provider only after a successful login. */
|
||||
@@ -2000,26 +2001,19 @@ export class SelectorController {
|
||||
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
||||
...this.ctx.keybindings.getKeys("app.session.observe"),
|
||||
];
|
||||
let hub: AgentHubOverlayComponent | undefined;
|
||||
let overlayHandle: OverlayHandle | undefined;
|
||||
let closed = false;
|
||||
|
||||
// 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();
|
||||
this.ctx.editorContainer.clear();
|
||||
this.ctx.editorContainer.addChild(this.ctx.editor);
|
||||
this.ctx.ui.setFocus(this.ctx.editor);
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
hub.dispose();
|
||||
overlayHandle?.hide();
|
||||
this.focusActiveEditorArea();
|
||||
this.ctx.ui.requestRender();
|
||||
};
|
||||
|
||||
hub = new AgentHubOverlayComponent({
|
||||
const hub = new AgentHubOverlayComponent({
|
||||
observers,
|
||||
hubKeys,
|
||||
expandKeys: this.ctx.keybindings.getKeys("app.tools.expand"),
|
||||
@@ -2038,30 +2032,24 @@ export class SelectorController {
|
||||
});
|
||||
|
||||
const showReadyHub = () => {
|
||||
// The double-← gesture passes requireContent so it stays inert when
|
||||
// neither live nor persisted subagents are available. Persisted rows now
|
||||
// load asynchronously, so defer the gate until that scan has refreshed the
|
||||
// hub instead of treating the initial empty table as authoritative.
|
||||
if (closed) return;
|
||||
// The double-← gesture stays inert when neither live nor persisted
|
||||
// subagents are available, so wait for discovery before making the gate.
|
||||
if (options?.requireContent && hub.isEmpty) {
|
||||
hub.dispose();
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctx.editorContainer.clear();
|
||||
this.ctx.editorContainer.addChild(hub);
|
||||
this.ctx.ui.setFocus(hub);
|
||||
// When the hub was raised by the editor's double-← gesture, prime its own
|
||||
// close detector so the *next* single ← dismisses it — the two taps that
|
||||
// opened it were consumed by the editor's detector (issue #4780).
|
||||
// Prime the detector before the first frame when the editor's double-←
|
||||
// gesture opened the hub, so the next single ← dismisses it.
|
||||
if (options?.armCloseTap) hub.armCloseTap();
|
||||
this.ctx.ui.requestRender();
|
||||
overlayHandle = this.#showFullscreenMenu(hub);
|
||||
};
|
||||
|
||||
if (options?.requireContent && hub.isEmpty) {
|
||||
void hub.persistedSubagentsReady.then(showReadyHub);
|
||||
return;
|
||||
} else {
|
||||
showReadyHub();
|
||||
}
|
||||
|
||||
showReadyHub();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user