Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions docs/reference/commands-nemohermes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> 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 <name> agents add`

Run the OpenClaw interactive add wizard inside the sandbox.
Expand Down
12 changes: 12 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> 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 <name> agents add`

Run the OpenClaw interactive add wizard inside the sandbox.
Expand Down
3 changes: 2 additions & 1 deletion src/commands/sandbox/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> agents` with no subcommand or with flags only renders this help screen instead of dispatching a fabricated `sandbox:agents:--<flag>` command id.";
"Parent for the `agents` subcommand group (`add`, `delete`, `list`). The parent has no runnable default — invoking `nemoclaw <name> agents` with no subcommand or with flags only renders this help screen instead of dispatching a fabricated `sandbox:agents:--<flag>` command id.";
static usage = ["<name> <subcommand> [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",
];
Expand Down
37 changes: 37 additions & 0 deletions src/commands/sandbox/agents/list.ts
Original file line number Diff line number Diff line change
@@ -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 = ["<name> [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<void> {
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 });
}
}
3 changes: 2 additions & 1 deletion src/lib/actions/sandbox/agents/passthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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("");
}

Expand Down
10 changes: 5 additions & 5 deletions src/lib/cli/command-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
8 changes: 8 additions & 0 deletions src/lib/cli/public-display-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
import type { PublicDisplayLayout } from "./public-display-layout";

export const SANDBOX_AGENTS_DISPLAY_LAYOUT: Record<string, readonly PublicDisplayLayout[]> = {
"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",
Expand Down
44 changes: 44 additions & 0 deletions test/e2e/test-sessions-agents-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
# TC-SESS-04: `nemoclaw <name> sessions list --json` after reset
# TC-AGENT-01: `nemoclaw <name> agents add work --model gpt-4o`
# (passthrough wizard; --non-interactive bypass)
# TC-AGENT-03: `nemoclaw <name> agents list --json`
# (passthrough lister; OpenClaw owns gateway dispatch)
# TC-AGENT-02: `nemoclaw <name> agents delete work --force --json`
# (passthrough delete; OpenClaw owns workspace removal)
# TC-SESS-05: `nemoclaw <name> sessions delete <key>` on a non-main session
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading