From 86c92cb4ce3bd166ad37a8c6226e8cf59659bb2a Mon Sep 17 00:00:00 2001 From: Yaohua-Leo Date: Fri, 20 Mar 2026 20:06:30 +0800 Subject: [PATCH 1/5] fix(cli): align nodes list and status to show consistent paired node count (#50847) - Always call node.list API to get paired status - Use node.list paired nodes as fallback when node.pair.list returns empty - Fixes inconsistent display between 'nodes status' and 'nodes list' commands Fixes #50847 --- src/cli/nodes-cli/register.status.ts | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 03e00cbbec4..bef812a9a5d 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -304,30 +304,28 @@ export function registerNodesStatusCommands(nodes: Command) { await runNodesCommand("list", async () => { const connectedOnly = Boolean(opts.connected); const sinceMs = parseSinceMs(opts.lastConnected, "Invalid --last-connected"); - const result = await callGatewayCli("node.pair.list", opts, {}); - const { pending, paired } = parsePairingList(result); + const nodeListResult = await callGatewayCli("node.list", opts, {}); + const pairingListResult = await callGatewayCli("node.pair.list", opts, {}); + const { pending, paired } = parsePairingList(pairingListResult); const { heading, muted, warn } = getNodesTheme(); const tableWidth = getTerminalTableWidth(); const now = Date.now(); - const hasFilters = connectedOnly || sinceMs !== undefined; - const pendingRows = hasFilters ? [] : pending; - const connectedById = hasFilters - ? new Map( - parseNodeList(await callGatewayCli("node.list", opts, {})).map((node) => [ - node.nodeId, - node, - ]), - ) - : null; + + const liveNodesById = new Map( + parseNodeList(nodeListResult).map((node) => [node.nodeId, node]), + ); + + const pendingRows = connectedOnly || sinceMs !== undefined ? [] : pending; + const filteredPaired = paired.filter((node) => { if (connectedOnly) { - const live = connectedById?.get(node.nodeId); + const live = liveNodesById.get(node.nodeId); if (!live?.connected) { return false; } } if (sinceMs !== undefined) { - const live = connectedById?.get(node.nodeId); + const live = liveNodesById.get(node.nodeId); const lastConnectedAtMs = typeof node.lastConnectedAtMs === "number" ? node.lastConnectedAtMs @@ -343,8 +341,12 @@ export function registerNodesStatusCommands(nodes: Command) { } return true; }); + + const liveNodes = parseNodeList(nodeListResult); + const livePairedNodes = liveNodes.filter((n) => n.paired); + const totalPairedCount = paired.length > 0 ? paired.length : livePairedNodes.length; const filteredLabel = - hasFilters && filteredPaired.length !== paired.length ? ` (of ${paired.length})` : ""; + filteredPaired.length !== totalPairedCount ? ` (of ${totalPairedCount})` : ""; defaultRuntime.log( `Pending: ${pendingRows.length} · Paired: ${filteredPaired.length}${filteredLabel}`, ); @@ -370,7 +372,7 @@ export function registerNodesStatusCommands(nodes: Command) { if (filteredPaired.length > 0) { const pairedRows = filteredPaired.map((n) => { - const live = connectedById?.get(n.nodeId); + const live = liveNodesById.get(n.nodeId); const lastConnectedAtMs = typeof n.lastConnectedAtMs === "number" ? n.lastConnectedAtMs From e5d1ae72e61a3649079b24cd6f0f918a40753d5f Mon Sep 17 00:00:00 2001 From: Yaohua-Leo Date: Fri, 20 Mar 2026 20:15:45 +0800 Subject: [PATCH 2/5] fix(cli): address review feedback - remove redundant parse and fix filteredLabel guard --- src/cli/nodes-cli/register.status.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index bef812a9a5d..5cba488b4d1 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -342,11 +342,11 @@ export function registerNodesStatusCommands(nodes: Command) { return true; }); - const liveNodes = parseNodeList(nodeListResult); - const livePairedNodes = liveNodes.filter((n) => n.paired); + const hasFilters = connectedOnly || sinceMs !== undefined; + const livePairedNodes = Array.from(liveNodesById.values()).filter((n) => n.paired); const totalPairedCount = paired.length > 0 ? paired.length : livePairedNodes.length; const filteredLabel = - filteredPaired.length !== totalPairedCount ? ` (of ${totalPairedCount})` : ""; + hasFilters && filteredPaired.length !== totalPairedCount ? ` (of ${totalPairedCount})` : ""; defaultRuntime.log( `Pending: ${pendingRows.length} · Paired: ${filteredPaired.length}${filteredLabel}`, ); From b5a9b9929925053d4d62cebde97f0717913525ae Mon Sep 17 00:00:00 2001 From: Yaohua-Leo Date: Fri, 20 Mar 2026 20:26:17 +0800 Subject: [PATCH 3/5] style(cli): fix formatting for filteredLabel ternary --- src/cli/nodes-cli/register.status.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 5cba488b4d1..0d6e6528f6f 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -345,8 +345,9 @@ export function registerNodesStatusCommands(nodes: Command) { const hasFilters = connectedOnly || sinceMs !== undefined; const livePairedNodes = Array.from(liveNodesById.values()).filter((n) => n.paired); const totalPairedCount = paired.length > 0 ? paired.length : livePairedNodes.length; - const filteredLabel = - hasFilters && filteredPaired.length !== totalPairedCount ? ` (of ${totalPairedCount})` : ""; + const filteredLabel = hasFilters && filteredPaired.length !== totalPairedCount + ? ` (of ${totalPairedCount})` + : ""; defaultRuntime.log( `Pending: ${pendingRows.length} · Paired: ${filteredPaired.length}${filteredLabel}`, ); From 71851ff56e1d184e1d00581a468562a6288eaa1f Mon Sep 17 00:00:00 2001 From: LehaoLin Date: Sat, 21 Mar 2026 00:55:33 +0800 Subject: [PATCH 4/5] fix(gateway): reject 'list' and 'status' as invalid model refs (#51126) When sessions.patch receives model='list' or model='status' from Control UI, it now returns a clear error message instead of failing with a confusing 'model not allowed: openai-codex/list' error. These are chat command aliases, not model identifiers. Fixes #51126 --- src/gateway/sessions-patch.test.ts | 15 +++++++++++++++ src/gateway/sessions-patch.ts | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/gateway/sessions-patch.test.ts b/src/gateway/sessions-patch.test.ts index 478e360ecaf..67057d1fcc2 100644 --- a/src/gateway/sessions-patch.test.ts +++ b/src/gateway/sessions-patch.test.ts @@ -273,6 +273,21 @@ describe("gateway sessions patch", () => { expect(entry.modelOverride).toBe("claude-sonnet-4-6"); }); + test.each([ + { name: "list", model: "list" }, + { name: "LIST (uppercase)", model: "LIST" }, + { name: "List (mixed case)", model: "List" }, + { name: "status", model: "status" }, + { name: "STATUS (uppercase)", model: "STATUS" }, + { name: "Status (mixed case)", model: "Status" }, + ])("rejects model command alias '$name' as invalid model", async ({ model }) => { + const result = await runPatch({ + patch: { key: MAIN_SESSION_KEY, model }, + }); + expectPatchError(result, `invalid model`); + expectPatchError(result, `command alias`); + }); + test("sets spawnDepth for subagent sessions", async () => { const entry = expectPatchOk( await runPatch({ diff --git a/src/gateway/sessions-patch.ts b/src/gateway/sessions-patch.ts index 18b542302f6..e07977253fb 100644 --- a/src/gateway/sessions-patch.ts +++ b/src/gateway/sessions-patch.ts @@ -389,6 +389,11 @@ export async function applySessionsPatchToStore(params: { if (!trimmed) { return invalid("invalid model: empty"); } + if (trimmed.toLowerCase() === "list" || trimmed.toLowerCase() === "status") { + return invalid( + `invalid model: "${trimmed}" is a command alias, not a model. Use /models list or /model status in chat instead.`, + ); + } if (!params.loadGatewayModelCatalog) { return { ok: false, From 81f3f30dfcfcb10312e048edff6940225266e286 Mon Sep 17 00:00:00 2001 From: LehaoLin Date: Sat, 21 Mar 2026 01:05:42 +0800 Subject: [PATCH 5/5] style(cli): fix formatting for filteredLabel ternary --- src/cli/nodes-cli/register.status.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 0d6e6528f6f..666102f5ddb 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -345,9 +345,10 @@ export function registerNodesStatusCommands(nodes: Command) { const hasFilters = connectedOnly || sinceMs !== undefined; const livePairedNodes = Array.from(liveNodesById.values()).filter((n) => n.paired); const totalPairedCount = paired.length > 0 ? paired.length : livePairedNodes.length; - const filteredLabel = hasFilters && filteredPaired.length !== totalPairedCount - ? ` (of ${totalPairedCount})` - : ""; + const filteredLabel = + hasFilters && filteredPaired.length !== totalPairedCount + ? ` (of ${totalPairedCount})` + : ""; defaultRuntime.log( `Pending: ${pendingRows.length} · Paired: ${filteredPaired.length}${filteredLabel}`, );