diff --git a/docs/reference/commands-nemohermes.mdx b/docs/reference/commands-nemohermes.mdx index f3d1f58472c..b8d065cae39 100644 --- a/docs/reference/commands-nemohermes.mdx +++ b/docs/reference/commands-nemohermes.mdx @@ -930,6 +930,18 @@ nemohermes my-assistant skill remove my-skill Use the skill name from the `SKILL.md` frontmatter, not the local directory name. Skill names must contain only alphanumeric characters, dots, hyphens, and underscores, and cannot be `.` or `..`. +### `nemohermes agents list` + +List the OpenClaw agents configured in the sandbox. +This is a thin pass-through to `openclaw agents list` via `openshell sandbox exec`; the OpenClaw CLI owns the gateway `agents.list` call, output formatting, and binding summaries. +Flags accepted by the in-sandbox CLI (`--json`, `--bindings`) are forwarded verbatim. + +```bash +nemohermes my-assistant agents list +nemohermes my-assistant agents list --json +nemohermes my-assistant agents list --bindings +``` + ### `nemohermes agents add` Run the OpenClaw interactive add wizard inside the sandbox. diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx index d3864003d8d..4de971295c5 100644 --- a/docs/reference/commands.mdx +++ b/docs/reference/commands.mdx @@ -1134,6 +1134,18 @@ $$nemoclaw my-assistant skill remove my-skill Use the skill name from the `SKILL.md` frontmatter, not the local directory name. Skill names must contain only alphanumeric characters, dots, hyphens, and underscores, and cannot be `.` or `..`. +### `$$nemoclaw agents list` + +List the OpenClaw agents configured in the sandbox. +This is a thin pass-through to `openclaw agents list` via `openshell sandbox exec`; the OpenClaw CLI owns the gateway `agents.list` call, output formatting, and binding summaries. +Flags accepted by the in-sandbox CLI (`--json`, `--bindings`) are forwarded verbatim. + +```bash +$$nemoclaw my-assistant agents list +$$nemoclaw my-assistant agents list --json +$$nemoclaw my-assistant agents list --bindings +``` + ### `$$nemoclaw agents add` Run the OpenClaw interactive add wizard inside the sandbox. diff --git a/src/commands/sandbox/agents.ts b/src/commands/sandbox/agents.ts index 47e738c39cb..6e19ce46e09 100644 --- a/src/commands/sandbox/agents.ts +++ b/src/commands/sandbox/agents.ts @@ -9,10 +9,11 @@ export default class SandboxAgentsCommand extends NemoClawCommand { static strict = false; static summary = "Manage OpenClaw agents inside a sandbox"; static description = - "Parent for the `agents` subcommand group (`add`, `delete`). The parent has no runnable default — invoking `nemoclaw agents` with no subcommand or with flags only renders this help screen instead of dispatching a fabricated `sandbox:agents:--` command id."; + "Parent for the `agents` subcommand group (`add`, `delete`, `list`). The parent has no runnable default — invoking `nemoclaw agents` with no subcommand or with flags only renders this help screen instead of dispatching a fabricated `sandbox:agents:--` command id."; static usage = [" [openclaw-agents-flags...]"]; static examples = [ "<%= config.bin %> sandbox agents alpha --help", + "<%= config.bin %> sandbox agents list alpha --json", "<%= config.bin %> sandbox agents add alpha work --model gpt-4o", "<%= config.bin %> sandbox agents delete alpha work --force --json", ]; diff --git a/src/commands/sandbox/agents/list.ts b/src/commands/sandbox/agents/list.ts new file mode 100644 index 00000000000..c2f373b6f91 --- /dev/null +++ b/src/commands/sandbox/agents/list.ts @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + hasAgentsPassthroughHelpToken, + printAgentsPassthroughHelp, + runAgentsPassthrough, +} from "../../../lib/actions/sandbox/agents/passthrough"; +import { NemoClawCommand } from "../../../lib/cli/nemoclaw-oclif-command"; + +export default class SandboxAgentsListCommand extends NemoClawCommand { + static id = "sandbox:agents:list"; + static strict = false; + static summary = "List OpenClaw agents configured in a sandbox"; + static description = + "Pass through to `openclaw agents list` in the sandbox. Runs the OpenClaw lister via `openshell sandbox exec`; all OpenClaw flags (e.g. `--json`, `--bindings`) are forwarded verbatim."; + static usage = [" [openclaw-agents-list-flags...]"]; + static examples = [ + "<%= config.bin %> sandbox agents list alpha", + "<%= config.bin %> sandbox agents list alpha --json", + "<%= config.bin %> sandbox agents list alpha --bindings", + ]; + + public async run(): Promise { + this.parsed = true; + const [sandboxName, ...extraArgs] = this.argv; + if (!sandboxName || sandboxName.trim() === "" || sandboxName === "--help" || sandboxName === "-h") { + printAgentsPassthroughHelp("list"); + return; + } + if (hasAgentsPassthroughHelpToken(extraArgs)) { + printAgentsPassthroughHelp("list"); + return; + } + await runAgentsPassthrough(sandboxName, { verb: "list", extraArgs }); + } +} diff --git a/src/lib/actions/sandbox/agents/passthrough.ts b/src/lib/actions/sandbox/agents/passthrough.ts index e631320a14a..6ee1bad8818 100644 --- a/src/lib/actions/sandbox/agents/passthrough.ts +++ b/src/lib/actions/sandbox/agents/passthrough.ts @@ -5,7 +5,7 @@ import { CLI_NAME } from "../../../cli/branding"; import { execSandbox } from "../exec"; import { ensureLiveSandboxOrExit } from "../gateway-state"; -export type AgentsPassthroughVerb = "add" | "delete"; +export type AgentsPassthroughVerb = "add" | "delete" | "list"; export interface AgentsPassthroughOptions { verb: AgentsPassthroughVerb; @@ -46,6 +46,7 @@ export function printAgentsParentHelp(): void { console.log(" Subcommands:"); console.log(" add Add an OpenClaw agent in the sandbox."); console.log(" delete Delete an OpenClaw agent in the sandbox."); + console.log(" list List OpenClaw agents configured in the sandbox."); console.log(""); } diff --git a/src/lib/cli/command-registry.test.ts b/src/lib/cli/command-registry.test.ts index 41d3d7c83c8..67b98cc6c04 100644 --- a/src/lib/cli/command-registry.test.ts +++ b/src/lib/cli/command-registry.test.ts @@ -55,11 +55,11 @@ describe("command-registry", () => { }); describe("sandboxCommands()", () => { - it("should return exactly 43 entries", () => { - // 37 visible + 6 hidden (shields×3 + config get/set/rotate-token). - // 37 visible includes the sessions group (root + list + reset + delete) - // and the agents pair (add + delete). - expect(sandboxCommands()).toHaveLength(43); + it("should return exactly 44 entries", () => { + // 38 visible + 6 hidden (shields×3 + config get/set/rotate-token). + // 38 visible includes the sessions group (root + list + reset + delete) + // and the agents trio (add + delete + list). + expect(sandboxCommands()).toHaveLength(44); }); it("every entry has scope sandbox", () => { diff --git a/src/lib/cli/public-display-agents.ts b/src/lib/cli/public-display-agents.ts index dc1d5d9ab79..3dd03c3bee0 100644 --- a/src/lib/cli/public-display-agents.ts +++ b/src/lib/cli/public-display-agents.ts @@ -4,6 +4,14 @@ import type { PublicDisplayLayout } from "./public-display-layout"; export const SANDBOX_AGENTS_DISPLAY_LAYOUT: Record = { + "sandbox:agents:list": [ + { + group: "Sandbox Management", + order: 16.4, + flags: "[openclaw-agents-list-flags...]", + description: "List OpenClaw agents configured in the sandbox", + }, + ], "sandbox:agents:add": [ { group: "Sandbox Management", diff --git a/test/e2e/test-sessions-agents-cli.sh b/test/e2e/test-sessions-agents-cli.sh index 8ab393c0c4c..87175261847 100755 --- a/test/e2e/test-sessions-agents-cli.sh +++ b/test/e2e/test-sessions-agents-cli.sh @@ -20,6 +20,8 @@ # TC-SESS-04: `nemoclaw sessions list --json` after reset # TC-AGENT-01: `nemoclaw agents add work --model gpt-4o` # (passthrough wizard; --non-interactive bypass) +# TC-AGENT-03: `nemoclaw agents list --json` +# (passthrough lister; OpenClaw owns gateway dispatch) # TC-AGENT-02: `nemoclaw agents delete work --force --json` # (passthrough delete; OpenClaw owns workspace removal) # TC-SESS-05: `nemoclaw sessions delete ` on a non-main session @@ -405,6 +407,47 @@ test_sessions_delete_non_main() { pass "TC-SESS-05: sessions delete ${key} succeeded and the key is gone" } +test_agents_list_passthrough() { + section "TC-AGENT-03: agents list --json (passthrough)" + local out + if ! out="$(nemoclaw "$SANDBOX_NAME" agents list --json 2>&1)"; then + fail "TC-AGENT-03: agents list --json exited non-zero" + info "$out" + return 1 + fi + if ! is_valid_json "$out"; then + fail "TC-AGENT-03: agents list --json did not return parseable JSON" + info "$out" + return 1 + fi + # The previously added secondary agent must appear in the listing. A pure + # exit-status check does not prove the passthrough reached the gateway. + # Mirror `is_valid_json`'s prefix-strip so Node warning lines that escape + # `NODE_NO_WARNINGS=1` (via openshell sub-invocations) don't break the parse. + if ! printf '%s' "$out" | TARGET="$TEST_AGENT_ID" python3 -c " +import json, os, sys +raw = sys.stdin.read() +offset = -1 +cursor = 0 +for line in raw.splitlines(keepends=True): + if line.startswith('{') or line.startswith('['): + offset = cursor + break + cursor += len(line) +if offset < 0: + sys.exit(1) +data = json.loads(raw[offset:]) +entries = data if isinstance(data, list) else data.get('agents', []) +target = os.environ['TARGET'] +sys.exit(0 if any(entry.get('id') == target for entry in entries) else 1) +" 2>/dev/null; then + fail "TC-AGENT-03: agent '${TEST_AGENT_ID}' not present in agents list --json output" + info "$out" + return 1 + fi + pass "TC-AGENT-03: agents list --json surfaced agent '${TEST_AGENT_ID}'" +} + test_agents_delete_passthrough() { section "TC-AGENT-02: agents delete ${TEST_AGENT_ID} --force --json" local out @@ -449,6 +492,7 @@ fi # Agents add/delete and non-main session delete are feature-critical for this # CLI surface — any failure must fail the whole job rather than be skipped. test_agents_add_passthrough +test_agents_list_passthrough seed_agent_session approve_pending_pairing_requests test_sessions_delete_non_main