fix(tui): preserve agent hub persisted gating

This commit is contained in:
can1357
2026-07-05 13:35:33 +02:00
parent 507e0fae4d
commit f3e372e7bf
3 changed files with 47 additions and 16 deletions
@@ -270,9 +270,9 @@ export class AgentHubOverlayComponent extends Container {
}
/**
* Whether the table view has no agents to show (every registered agent except
* Main, after the persisted-subagent scan in the constructor). The double-←
* gesture reads this to stay inert when there is nothing to open.
* Whether the current table view has no agents to show (every registered agent
* except Main). Persisted historical rows may arrive later; callers that need
* those included must wait for {@link persistedSubagentsReady} first.
*/
get isEmpty(): boolean {
return this.#rows.length === 0;
@@ -1426,18 +1426,27 @@ export class SelectorController {
sessionFile: this.ctx.sessionManager.getSessionFile() ?? null,
});
// The double-← gesture passes requireContent so it stays inert when there
// are no subagents to show; the explicit hub/observe keys still open the
// empty roster. The freshly built hub already ran the persisted-subagent
// scan, so its row count is the authoritative "is there anything to show".
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 (options?.requireContent && hub.isEmpty) {
hub.dispose();
return;
}
this.ctx.editorContainer.clear();
this.ctx.editorContainer.addChild(hub);
this.ctx.ui.setFocus(hub);
this.ctx.ui.requestRender();
};
if (options?.requireContent && hub.isEmpty) {
hub.dispose();
void hub.persistedSubagentsReady.then(showReadyHub);
return;
}
this.ctx.editorContainer.clear();
this.ctx.editorContainer.addChild(hub);
this.ctx.ui.setFocus(hub);
this.ctx.ui.requestRender();
showReadyHub();
}
}
@@ -188,8 +188,9 @@ describe("Agent hub double-← gating", () => {
resetSettingsForTest();
});
function setup(agents: AgentRegistry) {
function setup(agents: AgentRegistry, sessionFile: string | null = null) {
let shown: AgentHubOverlayComponent | undefined;
const shownReady = Promise.withResolvers<AgentHubOverlayComponent>();
const editor = {};
const ctx = {
keybindings: { getKeys: () => [] },
@@ -201,17 +202,20 @@ describe("Agent hub double-← gating", () => {
editorContainer: {
clear: () => {},
addChild: (child: unknown) => {
if (child !== editor) shown = child as AgentHubOverlayComponent;
if (child !== editor) {
shown = child as AgentHubOverlayComponent;
shownReady.resolve(shown);
}
},
},
collabGuest: { agentRegistry: agents, hubRemote: undefined },
focusAgentSession: async () => {},
session: { getToolByName: () => undefined, extensionRunner: undefined },
sessionManager: { getCwd: () => TEST_CWD, getSessionFile: () => null },
sessionManager: { getCwd: () => TEST_CWD, getSessionFile: () => sessionFile },
hideThinkingBlock: false,
};
const controller = new SelectorController(ctx as unknown as InteractiveModeContext);
return { controller, shown: () => shown };
return { controller, shown: () => shown, shownReady: shownReady.promise };
}
function registerWorker(agents: AgentRegistry) {
@@ -254,6 +258,24 @@ describe("Agent hub double-← gating", () => {
shown()!.dispose();
});
it("requireContent opens the hub after persisted subagents load", async () => {
using tempDir = TempDir.createSync("@omp-agent-hub-require-content-");
const sessionFile = path.join(tempDir.path(), "main.jsonl");
const workerSessionFile = path.join(tempDir.path(), "main", "Worker.jsonl");
await Bun.write(sessionFile, "");
await Bun.write(workerSessionFile, "");
const agents = new AgentRegistry();
const { controller, shown, shownReady } = setup(agents, sessionFile);
controller.showAgentHub(new SessionObserverRegistry(), { requireContent: true });
expect(shown()).toBeUndefined();
const shownHub = await shownReady;
expect(shownHub).toBeDefined();
expect(agents.get("Worker")?.sessionFile).toBe(workerSessionFile);
shownHub!.dispose();
});
it("the explicit hub key opens the empty roster even with no subagents", () => {
const agents = new AgentRegistry();
const { controller, shown } = setup(agents);