Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1385,7 +1385,7 @@ When sandbox GPU passthrough is enabled, the `Sandbox GPU` line includes the las
It reports `(CUDA verified)`, `(CUDA unverified)`, or `(last CUDA proof failed: <label>)` so automation and operators can distinguish configured GPU passthrough from proven CUDA access.
Failed proofs include remediation guidance for the detected platform.

A `Connected` line reports whether the sandbox has any active SSH sessions and, if so, how many.
An `SSH sessions` line reports how many active SSH sessions the sandbox has, or `none`; the line is omitted when the session probe is unavailable.
<AgentOnly variant="openclaw,hermes">
The sandbox list in the status output includes the dashboard port suffix for sandboxes with a recorded dashboard port.
</AgentOnly>
Expand Down Expand Up @@ -3097,7 +3097,7 @@ For gateway-based messaging agents, it also reports messaging overlap warnings.
</AgentOnly>
Use `$$nemoclaw <name> status` when you need one sandbox's live health and recovery guidance.
Pass `--json` for machine-readable output with registered sandboxes, service state, inference routes, and health details.
For each listed sandbox, the text output includes the configured inference provider and model plus whether an active SSH session is connected.
For each listed sandbox, the text output includes the configured inference provider and model plus the number of active SSH sessions when the session probe is available.
Host-service PID lookup honors `NEMOCLAW_SANDBOX_NAME`, then `NEMOCLAW_SANDBOX`, then `SANDBOX_NAME`, then the registry default.

```bash
Expand Down
14 changes: 12 additions & 2 deletions src/lib/actions/sandbox/status-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,7 @@ describe("showSandboxStatus flow", () => {
expect(output).toContain("Host GPU: yes");
expect(output).toContain("last CUDA proof failed: cuInit");
expect(output).toContain("CUDA initialization failed");
expect(output).toContain("Connected:");
expect(output).toContain("2 sessions");
expect(output).toContain("SSH sessions: 2");
expect(output).toContain("Permissions: mutable default");
expect(output).toContain("Update:");
expect(output).toContain("Recovered NemoClaw gateway runtime via gateway reattach.");
Expand All @@ -149,6 +148,17 @@ describe("showSandboxStatus flow", () => {
expect(exitSpy).not.toHaveBeenCalled();
});

it("reports zero SSH sessions as 'none' without connection-negative language (#7805)", async () => {
const harness = createStatusFlowHarness();
harness.getActiveSandboxSessionsSpy.mockReturnValue({ detected: true, sessions: [] });

await expect(harness.showSandboxStatus("alpha")).resolves.toBeUndefined();

const output = harness.logSpy.mock.calls.map((call) => String(call[0])).join("\n");
expect(output).toContain("SSH sessions: none");
expect(output).not.toContain("Connected: no");
});

it("reports active baseline exclusions and their support impact (#7178)", async () => {
const harness = createStatusFlowHarness({
sandboxEntry: {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/actions/sandbox/status-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ function printActiveSessions(sandboxName: string): void {
const sessionResult = getActiveSandboxSessions(sandboxName, createSessionDeps(openshell));
if (!sessionResult.detected) return;
const count = sessionResult.sessions.length;
const connected = count > 0 ? `${G}yes${R} (${count} session${count > 1 ? "s" : ""})` : "no";
console.log(` Connected: ${connected}`);
const sessions = count > 0 ? `${G}${count}${R}` : "none";
console.log(` SSH sessions: ${sessions}`);
} catch {
// Session detection is informational; an unavailable OpenShell client must
// not suppress the primary sandbox and gateway health report.
Expand Down
19 changes: 9 additions & 10 deletions src/lib/inventory/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ describe("inventory commands", () => {
agent: "openclaw",
isDefault: true,
activeSessionCount: 1,
connected: true,
},
],
});
Expand Down Expand Up @@ -1090,7 +1089,7 @@ describe("inventory commands", () => {
expect(lines).toContain(" Inference: live-provider / live-model");
});

it("emits a Connected line per sandbox when getActiveSessionCount is provided (#2604)", () => {
it("emits an SSH sessions line per sandbox when getActiveSessionCount is provided (#2604)", () => {
const lines: string[] = [];
showStatusCommand({
listSandboxes: () => ({
Expand All @@ -1106,11 +1105,11 @@ describe("inventory commands", () => {
log: (message = "") => lines.push(message),
});

expect(lines).toContain(" Connected: yes (2 sessions)");
expect(lines).toContain(" Connected: no");
expect(lines).toContain(" SSH sessions: 2");
expect(lines).toContain(" SSH sessions: none");
});

it("renders `1 session` (singular) when the active count is exactly one (#2604)", () => {
it("renders the exact active count when exactly one session (#2604)", () => {
const lines: string[] = [];
showStatusCommand({
listSandboxes: () => ({
Expand All @@ -1123,10 +1122,10 @@ describe("inventory commands", () => {
log: (message = "") => lines.push(message),
});

expect(lines).toContain(" Connected: yes (1 session)");
expect(lines).toContain(" SSH sessions: 1");
});

it("omits the Connected line when getActiveSessionCount returns null (probe unavailable)", () => {
it("omits the SSH sessions line when getActiveSessionCount returns null (probe unavailable)", () => {
const lines: string[] = [];
showStatusCommand({
listSandboxes: () => ({
Expand All @@ -1139,10 +1138,10 @@ describe("inventory commands", () => {
log: (message = "") => lines.push(message),
});

expect(lines.some((l) => l.includes("Connected:"))).toBe(false);
expect(lines.some((l) => l.includes("SSH sessions:"))).toBe(false);
});

it("omits the Connected line when the dep is not wired", () => {
it("omits the SSH sessions line when the dep is not wired", () => {
const lines: string[] = [];
showStatusCommand({
listSandboxes: () => ({
Expand All @@ -1154,7 +1153,7 @@ describe("inventory commands", () => {
log: (message = "") => lines.push(message),
});

expect(lines.some((l) => l.includes("Connected:"))).toBe(false);
expect(lines.some((l) => l.includes("SSH sessions:"))).toBe(false);
});

it("emits a gateway-down diagnostic and sets process.exitCode when the gateway is unhealthy (#3386)", () => {
Expand Down
21 changes: 8 additions & 13 deletions src/lib/inventory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export interface SandboxInventoryRow {
dashboardPort?: number | null;
isDefault: boolean;
activeSessionCount: number | null;
connected: boolean;
// #5714: row recovered display-only from the live gateway. Its agent/GPU/
// inference state is unknown (the gateway sandbox list does not expose it),
// so the renderer shows "unknown" rather than asserting OpenClaw/CPU defaults.
Expand Down Expand Up @@ -130,7 +129,7 @@ export interface ShowStatusCommandDeps {
getServiceStatuses?: (options: { sandboxName?: string }) => StatusServiceRow[];
/**
* Active SSH-session count for a sandbox. When provided, `showStatusCommand`
* emits a `Connected:` line under each sandbox row. Returns null when the
* emits an `SSH sessions:` line under each sandbox row. Returns null when the
* probe is not available (e.g. no openshell binary); the line is omitted in
* that case. #2604.
*/
Expand Down Expand Up @@ -233,7 +232,6 @@ function buildSandboxInventoryRow(
...(sandbox.dashboardPort != null ? { dashboardPort: sandbox.dashboardPort } : {}),
isDefault: sandbox.name === defaultSandbox,
activeSessionCount,
connected: activeSessionCount !== null && activeSessionCount > 0,
...(sandbox.recoveredFromGateway ? { recoveredFromGateway: true } : {}),
...(sandbox.recoveredFromGateway ? { livePhase: sandbox.livePhase ?? null } : {}),
};
Expand Down Expand Up @@ -346,13 +344,13 @@ export function renderSandboxInventoryText(
? "sandbox GPU"
: "CPU sandbox";
const presets = sandbox.policies.length > 0 ? sandbox.policies.join(", ") : "none";
const connected = sandbox.connected ? " ●" : "";
const sessionDot = (sandbox.activeSessionCount ?? 0) > 0 ? " ●" : "";
const agent = sandbox.agent || "openclaw";
// #5714: for a gateway-recovered row, surface the trusted live PHASE
// (e.g. Ready) from `openshell sandbox list` so `list` agrees with
// `nemoclaw <name> status`; normal registry rows have no live phase.
const phase = sandbox.recoveredFromGateway ? ` phase: ${sandbox.livePhase || "unknown"}` : "";
log(` ${sandbox.name}${def}${connected}`);
log(` ${sandbox.name}${def}${sessionDot}`);
log(
` agent: ${agent} model: ${model} provider: ${provider} ${gpu}${phase} policies: ${presets}`,
Comment thread
cjagwani marked this conversation as resolved.
);
Expand Down Expand Up @@ -536,21 +534,18 @@ export function showStatusCommand(deps: ShowStatusCommandDeps): void {
if (isDefault && liveModel && liveModel !== inference.model) {
log(` (onboarded: ${inference.model || "unknown"})`);
}
// #2604: surface the configured Inference (provider/model) and
// Connected (active-session count) as labeled fields. Bare
// `nemoclaw status` previously only had the model in parens above —
// users had to run `nemoclaw <name> status` to see provider and
// connection state.
// #2604: surface the configured Inference (provider/model) and the
// SSH-session count as labeled fields. Bare `nemoclaw status` previously
// only had the model in parens above — users had to run
// `nemoclaw <name> status` to see provider and session state.
if (provider || model) {
const parts = [provider, model].filter(Boolean).join(" / ");
log(` Inference: ${parts}`);
}
if (deps.getActiveSessionCount) {
const count = deps.getActiveSessionCount(sb.name);
if (count !== null) {
log(
` Connected: ${count > 0 ? `yes (${count} session${count > 1 ? "s" : ""})` : "no"}`,
);
log(` SSH sessions: ${count > 0 ? count : "none"}`);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion test/cli/list-inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ describe("CLI dispatch", () => {
agent: "openclaw",
isDefault: true,
activeSessionCount: 1,
connected: true,
hostGpuDetected: false,
sandboxGpuEnabled: true,
sandboxGpuMode: null,
Expand Down
Loading