Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
f8a9a58
refactor(onboard): lower cognitive complexity ratchet to 245
cv Jun 14, 2026
65d7578
refactor(rebuild): lower cognitive complexity ratchet to 244
cv Jun 14, 2026
433ba8a
refactor(rebuild): lower cognitive complexity ratchet to 243
cv Jun 14, 2026
30e2b10
refactor(onboard): move setupNim validation helpers
cv Jun 14, 2026
bd07191
Merge branch 'ratchet/245-setup-nim-cognitive-complexity' into ratche…
cv Jun 14, 2026
fda1c05
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 14, 2026
cdc58d7
test(rebuild): cover interactive confirmation
cv Jun 14, 2026
2879093
refactor(rebuild): lower cognitive complexity ratchet to 225
cv Jun 15, 2026
df63d1a
refactor(rebuild): lower cognitive complexity ratchet to 224
cv Jun 15, 2026
494c641
refactor(rebuild): lower cognitive complexity ratchet to 215
cv Jun 15, 2026
2f250de
refactor(rebuild): lower cognitive complexity ratchet to 186
cv Jun 15, 2026
843e3a4
refactor(snapshot): lower cognitive complexity ratchet to 185
cv Jun 15, 2026
8b83cbf
chore(lint): lower cognitive complexity ratchet to 184 (#5431)
cv Jun 15, 2026
462d092
refactor(cli): lower cognitive complexity ratchet below 150
cv Jun 15, 2026
557f501
refactor(onboard): keep snapshot ratchet step net-neutral
cv Jun 15, 2026
bdf7d76
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
56f41a5
fix(onboard): preserve Ollama tool override state
cv Jun 15, 2026
3224708
test(onboard): cover Ollama and rebuild ratchet helpers
cv Jun 15, 2026
ccf1053
refactor(rebuild): move ratchet helpers out of hotspot
cv Jun 15, 2026
5749645
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
cv Jun 15, 2026
7778b05
Merge branch 'main' into ratchet/245-setup-nim-cognitive-complexity
cv Jun 15, 2026
b451a5b
fix(onboard): preserve local NIM fallback state
cv Jun 15, 2026
9e3ddf5
Merge branch 'ratchet/245-setup-nim-cognitive-complexity' into ratche…
cv Jun 15, 2026
08743b0
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 15, 2026
17913db
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
18a4325
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
cf9cc11
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
f26ffe1
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
af4f61a
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
74968a3
refactor(rebuild): isolate messaging config hydration
cv Jun 15, 2026
6754806
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
ed3170a
test(onboard): cover setupNim selection validator state
cv Jun 15, 2026
4ef8d6f
Merge branch 'ratchet/245-setup-nim-cognitive-complexity' into ratche…
cv Jun 15, 2026
3df8744
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 15, 2026
8d3daaa
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
c8f5794
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
4499f83
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
463e1d7
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
fd21dbe
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
7688a6b
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
9194819
Merge remote-tracking branch 'origin/main' into ratchet/244-rebuild-c…
cv Jun 15, 2026
00724d3
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 15, 2026
57dafc8
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
374e4a8
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
6758a6d
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
e6b39bb
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
fa2c734
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
d62040e
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
ebe72ba
test(onboard): align sandbox registration fixture
cv Jun 15, 2026
a23904d
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 15, 2026
af81f07
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
d9ad1a0
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
b2ab482
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
56b891e
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
e51f221
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
18845b5
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
cb69607
test(rebuild): cover active session warning
cv Jun 15, 2026
86b83a0
Merge branch 'ratchet/244-rebuild-cognitive-complexity' into ratchet/…
cv Jun 15, 2026
7b61d67
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
e512f8f
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
3926914
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
bcddede
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
b9d6824
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
590b02b
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
94402c3
Merge remote-tracking branch 'origin/main' into ratchet/243-rebuild-c…
cv Jun 15, 2026
98902a7
Merge branch 'ratchet/243-rebuild-confirm-cognitive-complexity' into …
cv Jun 15, 2026
e28e8e7
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
ac58322
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
fccda94
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
a57b88a
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
6d627ab
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
04da8e5
Merge remote-tracking branch 'origin/main' into ratchet/225-rebuild-s…
cv Jun 15, 2026
d066d12
Merge branch 'ratchet/225-rebuild-single-agent-guard' into ratchet/22…
cv Jun 15, 2026
3fe883f
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
17320b6
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
a753a58
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
309ecbd
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
d6d5bfe
Merge remote-tracking branch 'origin/main' into ratchet/224-rebuild-r…
cv Jun 15, 2026
a679c21
Merge branch 'ratchet/224-rebuild-registry-guard' into ratchet/215-re…
cv Jun 15, 2026
cf2e341
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
3033a95
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
7880f41
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
51492b1
Merge remote-tracking branch 'origin/main' into ratchet/215-rebuild-v…
cv Jun 15, 2026
af54450
Merge branch 'ratchet/215-rebuild-version-header' into ratchet/186-re…
cv Jun 15, 2026
8198b5e
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
cdd9980
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
8ec4bd6
fix(rebuild): target legacy local credential bypass
cv Jun 15, 2026
b69ad81
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
a3c033b
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
dfe58bf
test(rebuild): cover messaging staging preflight
cv Jun 15, 2026
74a5e0e
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
b184d43
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
6d17d33
Merge remote-tracking branch 'origin/main' into ratchet/186-rebuild-c…
cv Jun 15, 2026
c2a115a
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
de33d7b
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
8eca2e2
chore(rebuild): apply formatter
cv Jun 15, 2026
a7b3277
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
e39f764
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
1d8239e
fix(rebuild): fail closed on missing remote session credential
cv Jun 15, 2026
bc918e9
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
8b26f97
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
804dc16
fix(rebuild): use registry provider for credential preflight
cv Jun 15, 2026
7043fd0
Merge branch 'ratchet/186-rebuild-credential-preflight' into ratchet/…
cv Jun 15, 2026
57aff7b
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
5c9776f
Merge remote-tracking branch 'origin/main' into ratchet/185-snapshot-…
cv Jun 15, 2026
089fe2e
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
af3257b
test(snapshot): cover create failure branches
cv Jun 15, 2026
9dc40bf
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 15, 2026
0e2b7e7
Merge remote-tracking branch 'origin/main' into ratchet/185-snapshot-…
cv Jun 16, 2026
89e70f7
Merge branch 'ratchet/185-snapshot-onboard-tie' into ratchet/149-rebu…
cv Jun 16, 2026
985c9ef
Merge remote-tracking branch 'origin/main' into ratchet/149-rebuild-s…
cv Jun 16, 2026
28bf3f9
chore(snapshot): apply formatter after merge
cv Jun 16, 2026
267373e
fix(onboard): fail closed on stale Ollama fallback state
cv Jun 16, 2026
6dab241
fix(snapshot): reuse preflighted source image
cv Jun 16, 2026
89f1d4d
test(snapshot): cover warning-only restore reconciliation
cv Jun 16, 2026
7e9afd2
fix(snapshot): keep custom policies out of preset reconcile
cv Jun 16, 2026
51d9dc1
chore(ci): rerun after snapshot test cleanup
cv Jun 16, 2026
e6b8636
Merge remote-tracking branch 'origin/main' into ratchet/149-rebuild-s…
cv Jun 16, 2026
6eab9bb
fix(test): avoid duplicate snapshot backup mock
cv Jun 16, 2026
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 biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"noExcessiveCognitiveComplexity": {
"level": "error",
"options": {
"maxAllowedComplexity": 184
"maxAllowedComplexity": 149
}
}
},
Expand Down
224 changes: 224 additions & 0 deletions src/lib/actions/sandbox/rebuild-flow-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import {
detectOpenShellStateRpcResultIssue,
printOpenShellStateRpcIssue,
} from "../../adapters/openshell/gateway-drift";
import { ensureAgentBaseImage } from "../../agent/onboard";
import { RD as _RD, G, R, YW } from "../../cli/terminal-style";
import { getNamedGatewayLifecycleState } from "../../gateway-runtime-action";
import {
captureSandboxListWithGatewayRecovery,
printSandboxListFailureWithRecoveryContext,
} from "../../openshell-sandbox-list";
import { parseLiveSandboxNames } from "../../runtime-recovery";
import * as shields from "../../shields";
import * as registry from "../../state/registry";
import * as sandboxState from "../../state/sandbox";
import { loadAgent } from "../../agent/defs";
import { CLI_NAME } from "../../cli/branding";
import { resolveSandboxGatewayName } from "../../onboard/gateway-binding";
import {
getReconciledSandboxGatewayState,
printGatewayLifecycleHint,
printWrongGatewayActiveGuidance,
} from "./gateway-state";
import { openRebuildShieldsWindow, type RebuildShieldsWindow } from "./rebuild-shields";

export type RebuildSandboxEntry = registry.SandboxEntry & { agents?: unknown[] };

export type RebuildLiveState = {
staleRecovery: boolean;
staleRegistrySnapshot: ReturnType<typeof registry.load> | null;
};

export async function resolveRebuildLiveState(
sandboxName: string,
sb: RebuildSandboxEntry,
log: (msg: string) => void,
bail: (msg: string, code?: number) => never,
): Promise<RebuildLiveState | null> {
const recordedGateway = resolveSandboxGatewayName(sb);
log(`Checking sandbox liveness on ${recordedGateway}: openshell sandbox list`);
const liveRecovery = await captureSandboxListWithGatewayRecovery({
gatewayName: recordedGateway,
});
const isLive = liveRecovery.result;
log(
`openshell sandbox list exit=${isLive.status}, output=${(isLive.output || "").substring(0, 200)}`,
);
const liveListIssue = detectOpenShellStateRpcResultIssue(isLive);
if (liveListIssue) {
printOpenShellStateRpcIssue(liveListIssue, {
action: `rebuilding sandbox '${sandboxName}'`,
command: `${CLI_NAME} ${sandboxName} rebuild`,
});
bail("OpenShell gateway schema mismatch.");
return null;
}
if (isLive.status !== 0) {
printSandboxListFailureWithRecoveryContext(liveRecovery);
bail("Failed to query running sandboxes from OpenShell.", isLive.status || 1);
return null;
}

const liveNames = parseLiveSandboxNames(isLive.output || "");
log(`Live sandboxes: ${Array.from(liveNames).join(", ") || "(none)"}`);
if (liveNames.has(sandboxName)) return { staleRecovery: false, staleRegistrySnapshot: null };

const reconciled = await getReconciledSandboxGatewayState(sandboxName);
if (reconciled.state === "present") {
const lifecycle = getNamedGatewayLifecycleState(recordedGateway);
if (lifecycle.state !== "healthy_named") {
printWrongGatewayActiveGuidance(
sandboxName,
lifecycle.activeGateway,
console.error,
"rebuild --yes",
);
bail(
`Could not confirm '${sandboxName}' against gateway '${recordedGateway}' (gateway '${lifecycle.activeGateway ?? "unknown"}' is active).`,
);
return null;
}
log("Sandbox live on the healthy named gateway; using normal rebuild path");
return { staleRecovery: false, staleRegistrySnapshot: null };
}

if (reconciled.state === "missing") {
// Source boundary: the local registry is the durable NemoClaw intent record,
// while OpenShell owns live sandbox presence. A missing live sandbox on a
// healthy named gateway can come from external deletion or failed prior
// provisioning, so rebuild recovers from registry metadata instead of
// treating the preserved local entry as corrupt. Keep until OpenShell exposes
// an atomic recreate-from-registry recovery API.
console.log("");
console.log(
` ${YW}⚠${R} Sandbox '${sandboxName}' is registered locally but absent from the live OpenShell gateway.`,
);
console.log(
" No live workspace state to back up — recreating from the preserved registry metadata.",
);
log(
"Stale-sandbox recovery: live sandbox missing on healthy named gateway; skipping backup/restore and recreating from registry metadata",
);
return {
staleRecovery: true,
staleRegistrySnapshot: JSON.parse(JSON.stringify(registry.load())),
};
}

if (reconciled.state === "gateway_schema_mismatch") {
console.error(reconciled.output);
bail("OpenShell gateway schema mismatch.");
return null;
}

if (reconciled.state === "wrong_gateway_active") {
printWrongGatewayActiveGuidance(
sandboxName,
reconciled.activeGateway,
console.error,
"rebuild --yes",
);
} else {
console.error(
` Sandbox '${sandboxName}' is not visible on gateway '${recordedGateway}' and its live state could not be confirmed.`,
);
console.error(" Your local registry entry has been preserved — nothing was removed.");
printGatewayLifecycleHint(reconciled.output || "", sandboxName, console.error);
}
bail(`Could not confirm live state of '${sandboxName}' (gateway not in a known-good state).`);
return null;
}

export function openRebuildShieldsWindowForState(
sandboxName: string,
staleRecovery: boolean,
): { rebuildShieldsWindow: RebuildShieldsWindow | null; staleSandboxWasLocked: boolean } {
if (staleRecovery) {
return {
staleSandboxWasLocked: !shields.isShieldsDown(sandboxName),
rebuildShieldsWindow: { relocked: false, wasLocked: false },
};
}
return {
staleSandboxWasLocked: false,
rebuildShieldsWindow: openRebuildShieldsWindow(sandboxName, CLI_NAME),
};
}

export function ensureRebuildAgentBaseImage(
rebuildAgent: string | null,
bail: (msg: string, code?: number) => never,
): boolean {
if (!rebuildAgent) return true;
const agentDef = loadAgent(rebuildAgent);
try {
ensureAgentBaseImage(agentDef, { forceBaseImageRebuild: true });
return true;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.error("");
console.error(` ${_RD}Rebuild preflight failed:${R} agent base image could not be built.`);
console.error(` ${message}`);
console.error("");
console.error(" Sandbox is untouched — no data was lost.");
bail(message);
return false;
}
}

export function backupSandboxStateForRebuild(
sandboxName: string,
sb: RebuildSandboxEntry,
staleRecovery: boolean,
log: (msg: string) => void,
relockShieldsIfNeeded: (sandboxStillExists: boolean) => boolean,
bail: (msg: string, code?: number) => never,
): sandboxState.RebuildManifest | null | undefined {
if (staleRecovery) return null;

console.log(" Backing up sandbox state...");
log(`Agent type: ${sb.agent || "openclaw"}, stateDirs from manifest`);
const backup = sandboxState.backupSandboxState(sandboxName);
log(
`Backup result: success=${backup.success}, backed=${backup.backedUpDirs.join(",")}; files=${backup.backedUpFiles.join(",")}, failed=${backup.failedDirs.join(",")}; failedFiles=${backup.failedFiles.join(",")}`,
);
const hasAnyBackup = backup.backedUpDirs.length > 0 || backup.backedUpFiles.length > 0;
if (!backup.success && !hasAnyBackup) {
console.error(" Failed to back up sandbox state.");
if (backup.failedDirs.length > 0) console.error(` Failed: ${backup.failedDirs.join(", ")}`);
if (backup.failedFiles.length > 0)
console.error(` Failed files: ${backup.failedFiles.join(", ")}`);
console.error(" Aborting rebuild to prevent data loss.");
relockShieldsIfNeeded(true);
bail("Failed to back up sandbox state.");
return undefined;
}
const backupManifest = backup.manifest ?? null;
if (!backupManifest) {
console.error(" Failed to record backup metadata.");
console.error(" Aborting rebuild to prevent data loss.");
relockShieldsIfNeeded(true);
bail("Failed to record backup metadata.");
return undefined;
}
if (!backup.success) {
console.warn(
` ${YW}⚠${R} Partial backup: ${backup.backedUpDirs.length} dirs and ${backup.backedUpFiles.length} files OK; ${backup.failedDirs.length} dirs and ${backup.failedFiles.length} files failed`,
);
if (backup.failedDirs.length > 0)
console.warn(` Failed dirs: ${backup.failedDirs.join(", ")}`);
if (backup.failedFiles.length > 0)
console.warn(` Failed files: ${backup.failedFiles.join(", ")}`);
console.warn(" Rebuild will continue — failed state could not be preserved.");
} else {
console.log(
` ${G}✓${R} State backed up (${backup.backedUpDirs.length} directories, ${backup.backedUpFiles.length} files)`,
);
}
console.log(` Backup: ${backupManifest.backupPath}`);
return backupManifest;
}
Loading