Skip to content
Closed
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
2 changes: 1 addition & 1 deletion ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"src/lib/adapters/openshell/timeouts.ts": 36,
"src/lib/agent/defs.ts": 32,
"src/lib/cli/branding.ts": 85,
"src/lib/cli/nemoclaw-oclif-command.ts": 103,
"src/lib/cli/nemoclaw-oclif-command.ts": 109,
"src/lib/cli/terminal-style.ts": 45,
"src/lib/core/json-types.ts": 37,
"src/lib/core/ports.ts": 86,
Expand Down
135 changes: 135 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,141 @@ $$nemoclaw my-assistant doctor [--json]

</AgentOnly>

## CUA target lifecycle

These commands attach one CUA sandbox to one dedicated disposable desktop target.
The target must expose `browser`, `computer`, and `terminal` services.
The operator-owned adapter probes those services and returns their health in a lifecycle record.
NemoClaw validates that record, compares the immutable identities, and requires all three services to report healthy before it records the attachment.

Every target command also requires canonical CUA runtime-readiness state for the sandbox.
Until canonical onboarding records an available runtime, the commands return `lifecycle_unavailable`.

Target provisioning stays outside NemoClaw.
An operator-owned adapter controls the target and retains all cloud, host administration, SSH, VNC, and service credentials.
NemoClaw does not pass those credentials to the sandbox or store them in its registry.

The adapter must be an absolute executable path.
NemoClaw starts it without a shell, writes one `target-adapter-request` JSON object to standard input, and accepts one record from `schemas/cua-lifecycle.schema.json` on standard output.
The adapter must return a `target-attachment` record after success or a `failure` record after failure.
NemoClaw does not copy adapter standard error into public output.

Attachment also requires a secret-free JSON manifest that matches `schemas/cua-target-manifest.schema.json`.
The manifest contains immutable target, image, service-bundle, and protocol identities.
It must not contain endpoints, credentials, host names, instance IDs, transport handles, or administration data.
The manifest path must directly name a regular file no larger than 64 KiB; NemoClaw does not follow symbolic links.

```json
{
"schemaVersion": "1.0.0",
"kind": "target-manifest",
"identityDigest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"platform": "desktop-linux-amd64",
"image": {
"name": "desktop-image",
"version": "1.0.0",
"digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"owner": "target-owner"
},
"serviceBundle": {
"name": "desktop-services",
"version": "1.0.0",
"digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333",
"owner": "target-owner"
},
"capabilities": [
{ "id": "browser", "protocolVersion": "1.0.0" },
{ "id": "computer", "protocolVersion": "1.0.0" },
{ "id": "terminal", "protocolVersion": "1.0.0" }
]
}
```

All commands support `--json`.
Successful commands exit `0`.
Validation failures exit `2`, target or task conflicts exit `3`, unavailable lifecycle components exit `4`, and target health or compatibility failures exit `5`.
Failure output uses the versioned `failure` record and does not include raw adapter diagnostics.

### `$$nemoclaw <name> cua target attach`

Attach one target after its manifest, image, service bundle, and three capability checks match.
A worker that already has a target returns `target_conflict` without invoking the adapter.

```bash
$$nemoclaw my-cua cua target attach \
--adapter /absolute/path/to/target-adapter \
--target-manifest ./target-manifest.json \
--json
```

### `$$nemoclaw <name> cua target status`

Read the recorded secret-free attachment projection without invoking the adapter.
The output includes bounded target identity, capability protocol and health, and active-task state.
It contains no endpoint or credential material.

```bash
$$nemoclaw my-cua cua target status --json
```

The same bounded projection appears as `cuaTarget` in `$$nemoclaw <name> status --json`.
`$$nemoclaw <name> doctor` reports the recorded attachment state and capability health; it does not perform a live target probe.
Run `$$nemoclaw <name> cua target health --adapter <absolute-path>` for fresh validation.

### `$$nemoclaw <name> cua target health`

Recover fresh authority through the host adapter.
The command compares the observed target with the recorded identity and checks all three services.
It records `unreachable`, `incompatible`, or `replaced` without accepting the target when validation fails.

```bash
$$nemoclaw my-cua cua target health \
--adapter /absolute/path/to/target-adapter \
--json
```

### `$$nemoclaw <name> cua target reset`

Ask the adapter to reconstruct the disposable target, browser profile, and fixture state.
NemoClaw accepts the reset target only after its declared components and all three services pass.
A reset can produce a new target identity.
The command rejects reset while a task is active.

```bash
$$nemoclaw my-cua cua target reset \
--adapter /absolute/path/to/target-adapter \
--json
```

### `$$nemoclaw <name> cua target detach`

Ask the adapter to revoke target reachability.
NemoClaw clears the attachment projection only after the adapter returns a detached record.
The command rejects detach while a task is active.

```bash
$$nemoclaw my-cua cua target detach \
--adapter /absolute/path/to/target-adapter \
--json
```

### `$$nemoclaw <name> cua target destroy`

Ask the adapter to destroy the disposable target.
NemoClaw clears the attachment projection only after the adapter confirms that the target is detached.
The command rejects destroy while a task is active.

```bash
$$nemoclaw my-cua cua target destroy \
--adapter /absolute/path/to/target-adapter \
--json
```

Normal backups retain only the secret-free attachment projection.
They exclude the target, browser profile, mutable desktop state, adapter state, and administration material.
Recovery never reuses an attachment handle.
The host adapter obtains fresh authority and NemoClaw validates the immutable identities again.

### `$$nemoclaw <name> exec`

Run a command non-interactively inside a running sandbox through the OpenShell exec endpoint.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@
"nemoclaw/package.json",
"nemoclaw-blueprint/",
"schemas/network-policy.schema.json",
"schemas/cua-lifecycle.schema.json",
"schemas/cua-target-manifest.schema.json",
"schemas/sandbox-policy.schema.json",
"scripts/",
"docs/resources/local-credential-form.html",
Expand Down
115 changes: 115 additions & 0 deletions schemas/cua-target-manifest.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/NVIDIA/NemoClaw/schemas/cua-target-manifest.schema.json",
"$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0",
"title": "NemoClaw CUA target manifest",
"description": "Secret-free immutable identities required before a host-side adapter may attach a disposable desktop target.",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"kind",
"identityDigest",
"platform",
"image",
"serviceBundle",
"capabilities"
],
"properties": {
"schemaVersion": {
"type": "string",
"pattern": "^1\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
},
"kind": {
"const": "target-manifest"
},
"identityDigest": {
"$ref": "#/$defs/digest"
},
"platform": {
"$ref": "#/$defs/safeSelector"
},
"image": {
"$ref": "#/$defs/componentIdentity"
},
"serviceBundle": {
"$ref": "#/$defs/componentIdentity"
},
"capabilities": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"$ref": "#/$defs/capabilityIdentity"
}
}
},
"$defs": {
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"safeId": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
},
"safeSelector": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]*$"
},
"componentIdentity": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"version",
"digest",
"owner"
],
"properties": {
"name": {
"$ref": "#/$defs/safeId"
},
"version": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"digest": {
"$ref": "#/$defs/digest"
},
"owner": {
"type": "string",
"minLength": 1,
"maxLength": 128
}
}
},
"capabilityIdentity": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"protocolVersion"
],
"properties": {
"id": {
"enum": [
"browser",
"computer",
"terminal"
]
},
"protocolVersion": {
"type": "string",
"minLength": 1,
"maxLength": 128
}
}
}
}
}
54 changes: 54 additions & 0 deletions src/commands/sandbox/cua/target/attach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Args, Flags } from "@oclif/core";
import { NemoClawCommand } from "../../../../lib/cli/nemoclaw-oclif-command";
import { executeCuaTargetCommand, renderCuaTargetResult } from "../../../../lib/cua/target-command";

export default class SandboxCuaTargetAttachCommand extends NemoClawCommand {
static enableJsonFlag = true;
static id = "sandbox:cua:target:attach";
static strict = true;
static summary = "Attach and verify one disposable CUA desktop target";
static description =
"Use a host-side adapter to attach one target after immutable identity and browser, computer, and terminal health checks pass.";
static examples = [
"<%= config.bin %> sandbox cua target attach alpha --adapter /opt/cua-target-adapter --target-manifest ./target.json",
];
static usage = ["<name> --adapter <absolute-path> --target-manifest <path> [--json]"];
static args = {
sandboxName: Args.string({
name: "sandbox",
description: "Sandbox name",
required: true,
}),
};
static flags = {
adapter: Flags.string({
description: "Absolute path to the operator-owned CUA target adapter",
required: true,
}),
"target-manifest": Flags.string({
description: "Secret-free JSON manifest containing expected target identities",
required: true,
}),
};

public async run(): Promise<unknown> {
const { args, flags } = await this.parse(SandboxCuaTargetAttachCommand);
const rendered = renderCuaTargetResult(
"target.attach",
executeCuaTargetCommand({
operation: "target.attach",
sandboxName: args.sandboxName,
adapterPath: flags.adapter,
manifestPath: flags["target-manifest"],
}),
this.jsonEnabled(),
);
this.setExitCode(rendered.exitCode);
if (rendered.error) console.error(rendered.error);
if (rendered.message) this.log(rendered.message);
return rendered.output;
}
}
49 changes: 49 additions & 0 deletions src/commands/sandbox/cua/target/destroy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { Args, Flags } from "@oclif/core";
import { NemoClawCommand } from "../../../../lib/cli/nemoclaw-oclif-command";
import { executeCuaTargetCommand, renderCuaTargetResult } from "../../../../lib/cua/target-command";

export default class SandboxCuaTargetDestroyCommand extends NemoClawCommand {
static enableJsonFlag = true;
static id = "sandbox:cua:target:destroy";
static strict = true;
static summary = "Destroy the disposable CUA target and clear attachment state";
static description =
"Ask the host-side adapter to destroy the target before NemoClaw clears its secret-free attachment projection.";
static examples = [
"<%= config.bin %> sandbox cua target destroy alpha --adapter /opt/cua-target-adapter",
];
static usage = ["<name> --adapter <absolute-path> [--json]"];
static args = {
sandboxName: Args.string({
name: "sandbox",
description: "Sandbox name",
required: true,
}),
};
static flags = {
adapter: Flags.string({
description: "Absolute path to the operator-owned CUA target adapter",
required: true,
}),
};

public async run(): Promise<unknown> {
const { args, flags } = await this.parse(SandboxCuaTargetDestroyCommand);
const rendered = renderCuaTargetResult(
"target.destroy",
executeCuaTargetCommand({
operation: "target.destroy",
sandboxName: args.sandboxName,
adapterPath: flags.adapter,
}),
this.jsonEnabled(),
);
this.setExitCode(rendered.exitCode);
if (rendered.error) console.error(rendered.error);
if (rendered.message) this.log(rendered.message);
return rendered.output;
}
}
Loading
Loading