Skip to content

Commit 2fef079

Browse files
authored
Merge branch 'main' into codex/e2e-shields-direct-stat
2 parents 9b63db6 + cd80922 commit 2fef079

13 files changed

Lines changed: 511 additions & 33 deletions

.github/workflows/base-image.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ on:
2222
# takes effect immediately after merge instead of waiting for another tag.
2323
- ".github/workflows/base-image.yaml"
2424
- ".github/workflows/managed-images.yaml"
25+
- "test/e2e/live/managed-image-activation-e2e.test.ts"
26+
- "test/e2e/live/managed-image-activation-e2e-helpers.ts"
2527
- ".github/actions/ci-reviewed-npm-audit/**"
2628
- ".github/actions/build-base-image-platform/**"
2729
- ".github/actions/publish-base-image-manifest/**"
@@ -33,6 +35,7 @@ on:
3335
- "nemoclaw/**"
3436
- "nemoclaw-blueprint/**"
3537
- "scripts/**"
38+
- "test/e2e/live/managed-image-activation-e2e*.ts"
3639
- "src/lib/actions/sandbox/openshell-child-visible-credentials.v*.json"
3740
- "src/lib/core/json-types.ts"
3841
- "src/lib/core/ports.ts"

docs/reference/commands.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,10 @@ Use `--no-sandbox-gpu`, `--no-gpu`, or `NEMOCLAW_SANDBOX_GPU=0` when you want to
951951
List all registered sandboxes with their model, provider, and policy presets.
952952
Pass `--json` for machine-readable output that includes a `schemaVersion`, the default sandbox, recovery metadata, and the sandbox inventory.
953953
Each sandbox row reports `activeSessionCount` as a nonnegative integer when the SSH-session probe is available and `null` when it is unavailable.
954+
Each sandbox row reports `agent` as a string in both text and JSON output, never `null`.
955+
The row reports `openclaw` when the registry records no agent for the sandbox.
956+
The row reports `unknown` for a sandbox that `$$nemoclaw list` recovers from the live OpenShell gateway.
957+
The gateway sandbox list does not expose the agent.
954958
The row does not include the former derived `connected` boolean.
955959
Sandboxes with an active SSH session are marked with a `` indicator so you can tell at a glance which sandbox you are already connected to in another terminal.
956960
<AgentOnly variant="openclaw,hermes">
@@ -1578,6 +1582,7 @@ When the live shared route differs, text output prints both routes and JSON outp
15781582
When `routeDrift.canConnect` is `false`, `connect` cannot safely restore the recorded route because provider-global identity differs or required route or gateway metadata is incomplete.
15791583
Refer to [Use Shared Gateway Routes](../inference/manage-inference/use-shared-gateway-routes) for the route-sharing workflow.
15801584
`openshellDriver` and `openshellVersion` are always strings (falling back to `"unknown"` when the registry has no value), so consumers can rely on `typeof` checks.
1585+
`agent` is always a string and reports `openclaw` when the registry records no agent for the sandbox.
15811586
`failureLayer` is `null` when no preflight failure was detected and otherwise one of `docker_unreachable`, `sandbox_container_stopped`, or `sandbox_dashboard_port_conflict`; when set, `inferenceHealth` is suppressed to `null` so automation does not see a stale remote-provider healthy status during a local outage.
15821587
`dockerPaused` is `true` when NemoClaw detects that the Docker-driver sandbox container is paused.
15831588
In that case, text output keeps OpenShell's authoritative phase but prints a `docker unpause <container>` recovery hint instead of sending you directly to rebuild.
@@ -3587,6 +3592,9 @@ For gateway-based messaging agents, it also reports messaging overlap warnings w
35873592
</AgentOnly>
35883593
Use `$$nemoclaw <name> status` when you need one sandbox's live health and recovery guidance.
35893594
Pass `--json` for machine-readable output with registered sandboxes, service state, inference routes, and health details.
3595+
Each JSON sandbox row reports `agent` as a string, never `null`.
3596+
The row reports `openclaw` when the registry records no agent for the sandbox.
3597+
This command reads the registry without gateway recovery, so it never reports `unknown`.
35903598
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.
35913599
Host-service PID lookup honors `NEMOCLAW_SANDBOX_NAME`, then `NEMOCLAW_SANDBOX`, then `SANDBOX_NAME`, then the registry default.
35923600

scripts/install.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3094,15 +3094,15 @@ run_installer_host_preflight() {
30943094
)
30953095
),
30963096
];
3097+
const findingIds = admission.admitted ? [] : stableIds(admission.findingIds);
3098+
const capabilityIds = admission.admitted ? [] : stableIds(admission.capabilityIds);
30973099
if (host.runtime && host.runtime !== "unknown") {
30983100
infoLines.push(`Detected container runtime: ${host.runtime}`);
30993101
}
31003102
if (host.isWsl) {
31013103
infoLines.push("Running under WSL");
31023104
}
31033105
if (!admission.admitted) {
3104-
const findingIds = stableIds(admission.findingIds);
3105-
const capabilityIds = stableIds(admission.capabilityIds);
31063106
if (findingIds.length > 0) {
31073107
actionLines.push(`Admission finding IDs: ${findingIds.join(", ")}`);
31083108
}
@@ -3123,15 +3123,15 @@ run_installer_host_preflight() {
31233123
finding,
31243124
])
31253125
);
3126-
for (const findingId of admission.findingIds || []) {
3126+
for (const findingId of findingIds) {
31273127
const finding = findingById.get(findingId);
31283128
actionLines.push(
31293129
finding?.summary
31303130
? `- ${finding.summary}`
31313131
: `- Readiness finding: ${findingId}`
31323132
);
31333133
}
3134-
for (const capabilityId of admission.capabilityIds || []) {
3134+
for (const capabilityId of capabilityIds) {
31353135
actionLines.push(
31363136
`- NemoClaw could not confirm the required readiness capability ${capabilityId}.`
31373137
);

src/lib/inventory/index.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export interface SandboxInventoryRow {
8585
openshellDriver: string | null;
8686
openshellVersion: string | null;
8787
policies: string[];
88-
agent: string | null;
88+
agent: string;
8989
dashboardPort?: number | null;
9090
isDefault: boolean;
9191
activeSessionCount: number | null;
@@ -166,7 +166,7 @@ export interface StatusSandboxRow {
166166
openshellDriver: string | null;
167167
openshellVersion: string | null;
168168
policies: string[];
169-
agent: string | null;
169+
agent: string;
170170
dashboardPort?: number | null;
171171
isDefault: boolean;
172172
}
@@ -195,6 +195,21 @@ function safeStatusString(value: string | null | undefined): string | null {
195195
return redactFull(value);
196196
}
197197

198+
/**
199+
* Resolve the agent every inventory surface reports. The registry stores `null`
200+
* or omits `agent` for an OpenClaw sandbox, so text and JSON must resolve that
201+
* marker here or they report different agents for the same sandbox.
202+
*
203+
* #5714: a sandbox recovered display-only from the live gateway has an unknown
204+
* agent (the gateway sandbox list does not expose it). Surface "unknown" rather
205+
* than the OpenClaw default, which would misrepresent a Hermes or Deep Agents
206+
* Code sandbox as OpenClaw.
207+
*/
208+
function resolveDisplayAgent(sandbox: SandboxEntry): string {
209+
if (sandbox.agent) return sandbox.agent;
210+
return sandbox.recoveredFromGateway ? "unknown" : "openclaw";
211+
}
212+
198213
/**
199214
* Project a stored or recovered {@link SandboxEntry} into a display row,
200215
* resolving inference/GPU fields and marking gateway-recovered rows so unknown
@@ -224,11 +239,7 @@ function buildSandboxInventoryRow(
224239
openshellDriver: safeStatusString(sandbox.openshellDriver || null),
225240
openshellVersion: safeStatusString(sandbox.openshellVersion || null),
226241
policies: Array.isArray(sandbox.policies) ? sandbox.policies : [],
227-
// #5714: a sandbox recovered display-only from the live gateway has an
228-
// unknown agent (the gateway sandbox list does not expose it). Surface
229-
// "unknown" instead of letting the renderer's `|| "openclaw"` default
230-
// misrepresent a Deep Agents/Hermes sandbox as OpenClaw.
231-
agent: sandbox.agent || (sandbox.recoveredFromGateway ? "unknown" : null),
242+
agent: resolveDisplayAgent(sandbox),
232243
...(sandbox.dashboardPort != null ? { dashboardPort: sandbox.dashboardPort } : {}),
233244
isDefault: sandbox.name === defaultSandbox,
234245
activeSessionCount,
@@ -345,7 +356,7 @@ export function renderSandboxInventoryText(
345356
: "CPU sandbox";
346357
const presets = sandbox.policies.length > 0 ? sandbox.policies.join(", ") : "none";
347358
const sessionDot = (sandbox.activeSessionCount ?? 0) > 0 ? " ●" : "";
348-
const agent = sandbox.agent || "openclaw";
359+
const agent = sandbox.agent;
349360
// #5714: for a gateway-recovered row, surface the trusted live PHASE
350361
// (e.g. Ready) from `openshell sandbox list` so `list` agrees with
351362
// `nemoclaw <name> status`; normal registry rows have no live phase.
@@ -409,7 +420,7 @@ function buildStatusSandboxRow(
409420
.filter((policy): policy is string => typeof policy === "string")
410421
.map((policy) => safeStatusString(policy) || policy)
411422
: [],
412-
agent: safeStatusString(sandbox.agent || null),
423+
agent: redactFull(resolveDisplayAgent(sandbox)),
413424
...(dashboardPort != null ? { dashboardPort } : {}),
414425
isDefault,
415426
};

src/lib/onboard/lifecycle-contracts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ The schema and sanitation authority is `Session` plus `normalizeSession`/`filter
240240
| Field group | Fields | Writer/owner and state meaning |
241241
|---|---|---|
242242
| Session envelope | `version`, `sessionId`, `mode`, `startedAt`, `updatedAt`, `status`, `resumable` | `createSession`, save/update helpers, and completion/failure paths. Values are always known after creation. |
243-
| Progress and recovery | `lastStepStarted`, `lastCompletedStep`, `failure`, `steps`, `machine`, `sandboxPromptProgress`, `stagedCredentialProviders`, `checkpoint` | Step helpers record step-progress bookkeeping and context updates accepted by `filterSafeUpdates`. `OnboardRuntime` owns machine transitions, terminal state, and machine events. Explicit session recovery and the process-exit failure backstop are separate recovery boundaries. The OpenClaw sandbox handler owns prompt-group completion markers. `stagedCredentialProviders` contains only names registered before sandbox setup so OpenClaw resume can require both durable ownership and an exact live binding. Provider-effect replay requires the receipt provider set to match the providers selected by the current web search configuration or messaging plan. Each persisted and live provider name, provider type, and credential key must match before the handler skips registration. After a successful replay, the handler replaces obsolete bindings owned by that effect group before sandbox creation and preserves bindings owned by the other provider effect group. A marker is trusted only when its matching persisted value is present and valid, including an explicit `null` where supported. `checkpoint` is the dedicated versioned resume contract: a secret-free tri-state decision record plus durable sandbox identity, effect-group receipts, and logical web-search and messaging provider bindings, serialized alongside the session under its own `schemaVersion` with fail-closed handling of an unknown future version. The primary inference provider binding remains owned and revalidated by the provider and inference phases instead of entering this checkpoint ledger. |
243+
| Progress and recovery | `lastStepStarted`, `lastCompletedStep`, `failure`, `steps`, `machine`, `sandboxPromptProgress`, `stagedCredentialProviders`, `checkpoint` | Step helpers record step-progress bookkeeping and context updates accepted by `filterSafeUpdates`. `OnboardRuntime` owns machine transitions, terminal state, and machine events. Explicit session recovery and the process-exit failure backstop are separate recovery boundaries. The OpenClaw sandbox handler owns prompt-group completion markers. `stagedCredentialProviders` contains only names registered before sandbox setup so OpenClaw resume can require both durable ownership and an exact live binding. A recreate journal handed to this run by the driver that owns the replacement — matching sandbox name and target-intent fingerprint, and past the delete boundary at `deleted` — is the equivalent ownership proof for a replacement that reset the session and can no longer read a host credential, and it stays paired with the same exact live binding check. A journal merely resident in the session is not that proof, because nothing binds it to this run: one survives a failed attempt, and one is opened straight at `deleted` when the sandbox is already missing. Provider-effect replay requires the receipt provider set to match the providers selected by the current web search configuration or messaging plan. Each persisted and live provider name, provider type, and credential key must match before the handler skips registration. After a successful replay, the handler replaces obsolete bindings owned by that effect group before sandbox creation and preserves bindings owned by the other provider effect group. A marker is trusted only when its matching persisted value is present and valid, including an explicit `null` where supported. `checkpoint` is the dedicated versioned resume contract: a secret-free tri-state decision record plus durable sandbox identity, effect-group receipts, and logical web-search and messaging provider bindings, serialized alongside the session under its own `schemaVersion` with fail-closed handling of an unknown future version. The primary inference provider binding remains owned and revalidated by the provider and inference phases instead of entering this checkpoint ledger. |
244244
| Target identity | `agent`, `sandboxName`, `metadata.gatewayName`, `metadata.fromDockerfile` | Onboard selection, sandbox handler/registration, and rebuild session preparation. A completed sandbox step or valid `sandboxPromptProgress.sandboxName` marker is the trust gate for a recorded name. |
245245
| Inference intent | `provider`, `model`, `endpointUrl`, `credentialEnv`, `preferredInferenceApi`, `compatibleEndpointReasoning`, `nimContainer`, `webSearchConfig` | Provider/inference handlers and `runInferenceSet`. Known credential state is an environment-variable name or presence metadata, never the value. `redactUrl` masks userinfo and fragments, redacts values under sensitive parameter names, and redacts canonical token-shaped values even under benign parameter names. |
246246
| Agent and policy intent | `hermesAuthMethod`, `toolDisclosure`, `hermesToolGateways`, `policyPresets` | Agent setup and policy handling. Channel commands update matching-session `policyPresets` only best-effort. Nullable fields conflate unset, declined, and cleared where the CLI makes those distinctions. |

0 commit comments

Comments
 (0)