diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 03e00cbbec4..666102f5ddb 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,14 @@ export function registerNodesStatusCommands(nodes: Command) { } return true; }); + + 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 !== paired.length ? ` (of ${paired.length})` : ""; + hasFilters && filteredPaired.length !== totalPairedCount + ? ` (of ${totalPairedCount})` + : ""; defaultRuntime.log( `Pending: ${pendingRows.length} ยท Paired: ${filteredPaired.length}${filteredLabel}`, ); @@ -370,7 +374,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 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,