Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
89 changes: 3 additions & 86 deletions ci/source-architecture-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"defaultMax": 20,
"maxByFile": {
"src/lib/actions/inference-set.ts": 32,
"src/lib/actions/sandbox/connect.ts": 39,
"src/lib/actions/sandbox/connect.ts": 38,
"src/lib/actions/sandbox/destroy.ts": 27,
"src/lib/actions/sandbox/doctor.ts": 29,
"src/lib/actions/sandbox/policy-channel.ts": 28,
Expand All @@ -49,97 +49,14 @@
"src/lib/inference/vllm.ts": 23,
"src/lib/onboard.ts": 224,
"src/lib/onboard/machine/handlers/sandbox.ts": 21,
"src/lib/sandbox/config.ts": 23,
"src/lib/sandbox/config.ts": 22,
"src/lib/shields/index.ts": 23
}
},
"allowedCycles": [
[
"src/lib/actions/global.ts",
"src/lib/actions/maintenance.ts",
"src/lib/actions/onboard.ts",
"src/lib/actions/sandbox/backup-shields-window.ts",
"src/lib/actions/sandbox/connect-boundary-refusal.ts",
"src/lib/actions/sandbox/connect-hermes-light-skin.ts",
"src/lib/actions/sandbox/connect.ts",
"src/lib/actions/sandbox/destroy-execution.ts",
"src/lib/actions/sandbox/destroy-preflight.ts",
"src/lib/actions/sandbox/destroy.ts",
"src/lib/actions/sandbox/gateway-restart.ts",
"src/lib/actions/sandbox/gateway-state.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents-capability.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents-command.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents-inspection.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents-registration.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents-teardown.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-deepagents.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-inspection.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-openclaw.ts",
"src/lib/actions/sandbox/mcp-bridge-adapter-teardown.ts",
"src/lib/actions/sandbox/mcp-bridge-adapters.ts",
"src/lib/actions/sandbox/mcp-bridge-add-restart.ts",
"src/lib/actions/sandbox/mcp-bridge-destroy-preflight.ts",
"src/lib/actions/sandbox/mcp-bridge-destroy.ts",
"src/lib/actions/sandbox/mcp-bridge-hermes-reconciliation.ts",
"src/lib/actions/sandbox/mcp-bridge-provider-attachments.ts",
"src/lib/actions/sandbox/mcp-bridge-provider-inspection.ts",
"src/lib/actions/sandbox/mcp-bridge-provider-mutation.ts",
"src/lib/actions/sandbox/mcp-bridge-provider-readiness.ts",
"src/lib/actions/sandbox/mcp-bridge-provider.ts",
"src/lib/actions/sandbox/mcp-bridge-rebuild-exec-unavailable.ts",
"src/lib/actions/sandbox/mcp-bridge-rebuild.ts",
"src/lib/actions/sandbox/mcp-bridge-recovery.ts",
"src/lib/actions/sandbox/mcp-bridge-remove.ts",
"src/lib/actions/sandbox/mcp-bridge-resolution-probe.ts",
"src/lib/actions/sandbox/mcp-bridge-restart.ts",
"src/lib/actions/sandbox/mcp-bridge-runtime-capabilities.ts",
"src/lib/actions/sandbox/mcp-bridge-state.ts",
"src/lib/actions/sandbox/mcp-bridge-status.ts",
"src/lib/actions/sandbox/mcp-bridge-tool-discovery.ts",
"src/lib/actions/sandbox/mcp-bridge.ts",
"src/lib/actions/sandbox/policy-context-refresh.ts",
"src/lib/actions/sandbox/policy-explain.ts",
"src/lib/actions/sandbox/process-recovery.ts",
"src/lib/actions/sandbox/rebuild-backup-phase.ts",
"src/lib/actions/sandbox/rebuild-config-hash.ts",
"src/lib/actions/sandbox/rebuild-credential-preflight.ts",
"src/lib/actions/sandbox/rebuild-dcode-orchestrator.ts",
"src/lib/actions/sandbox/rebuild-dcode-preflight.ts",
"src/lib/actions/sandbox/rebuild-destroy-phase.ts",
"src/lib/actions/sandbox/rebuild-flow-helpers.ts",
"src/lib/actions/sandbox/rebuild-hermes-post-restore.ts",
"src/lib/actions/sandbox/rebuild-inference-preflight.ts",
"src/lib/actions/sandbox/rebuild-mcp-phase.ts",
"src/lib/actions/sandbox/rebuild-onboard-dependencies.ts",
"src/lib/actions/sandbox/rebuild-pipeline.ts",
"src/lib/actions/sandbox/rebuild-post-restore-phase.ts",
"src/lib/actions/sandbox/rebuild-preflight-phase.ts",
"src/lib/actions/sandbox/rebuild-preflight-target-phase.ts",
"src/lib/actions/sandbox/rebuild-recreate-phase.ts",
"src/lib/actions/sandbox/rebuild-shields-phase.ts",
"src/lib/actions/sandbox/rebuild-shields.ts",
"src/lib/actions/sandbox/rebuild-target-config.ts",
"src/lib/actions/sandbox/rebuild-target-preflight.ts",
"src/lib/actions/sandbox/rebuild-target-runtime.ts",
"src/lib/actions/sandbox/rebuild.ts",
"src/lib/actions/sandbox/reconcile-session-models.ts",
"src/lib/actions/upgrade-sandboxes.ts",
"src/lib/gateway-runtime-action.ts",
"src/lib/onboard.ts",
"src/lib/onboard/credential-provider-registration.ts",
"src/lib/onboard/finalization-deps.ts",
"src/lib/onboard/inference-providers/hermes.ts",
"src/lib/onboard/inference-providers/index.ts",
"src/lib/onboard/policy-context-seed.ts",
"src/lib/onboard/policy-resume-selection.ts",
"src/lib/onboard/policy-selection.ts",
"src/lib/onboard/setup-inference.ts",
"src/lib/openshell-sandbox-list.ts",
"src/lib/sandbox/config.ts",
"src/lib/shields/index.ts",
"src/lib/tunnel/allowed-origins.ts",
"src/lib/tunnel/services.ts"
"src/lib/onboard/policy-selection.ts"
]
],
"maxRootFiles": {
Expand Down
4 changes: 3 additions & 1 deletion src/commands/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ vi.mock("../lib/credentials/store", () => ({
}));
vi.mock("../lib/actions/global", () => ({
recoverNamedGatewayRuntime: mocks.recoverNamedGatewayRuntime,
runOpenshellProviderCommand: mocks.runOpenshellProviderCommand,
recordExtraProvider: mocks.recordExtraProvider,
forgetExtraProvider: mocks.forgetExtraProvider,
}));
vi.mock("../lib/adapters/openshell/provider-command", () => ({
runOpenshellProviderCommand: mocks.runOpenshellProviderCommand,
}));
vi.mock("../lib/onboard/gateway-teardown-authority", () => ({
resolveGatewayCredentialMutationAuthority: mocks.resolveGatewayCredentialMutationAuthority,
}));
Expand Down
2 changes: 1 addition & 1 deletion src/commands/credentials/list.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { runOpenshellProviderCommand } from "../../lib/actions/global";
import { runOpenshellProviderCommand } from "../../lib/adapters/openshell/provider-command";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
import { OPENSHELL_OPERATION_TIMEOUT_MS } from "../../lib/adapters/openshell/timeouts";
import { CLI_NAME } from "../../lib/cli/branding";
import { NemoClawCommand } from "../../lib/cli/nemoclaw-oclif-command";
Expand Down
3 changes: 2 additions & 1 deletion src/commands/credentials/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0

import { Args } from "@oclif/core";
import { forgetExtraProvider, runOpenshellProviderCommand } from "../../lib/actions/global";
import { forgetExtraProvider } from "../../lib/actions/global";
import { runOpenshellProviderCommand } from "../../lib/adapters/openshell/provider-command";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
import { OPENSHELL_OPERATION_TIMEOUT_MS } from "../../lib/adapters/openshell/timeouts";
import { CLI_NAME } from "../../lib/cli/branding";
import { yesFlag } from "../../lib/cli/common-flags";
Expand Down
4 changes: 2 additions & 2 deletions src/lib/actions/credentials-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import fs from "node:fs";
import path from "node:path";

import { runOpenshellProviderCommand } from "../adapters/openshell/provider-command";
import { OPENSHELL_OPERATION_TIMEOUT_MS } from "../adapters/openshell/timeouts";
import { CLI_NAME } from "../cli/branding";
import {
Expand All @@ -13,7 +13,7 @@ import {
import { redact } from "../security/redact";
import { SECRET_PATTERNS } from "../security/secret-patterns";
import { ROOT } from "../state/paths";
import { recordExtraProvider, runOpenshellProviderCommand } from "./global";
import { recordExtraProvider } from "./global";

export type CredentialsAddInput = {
provider: string;
Expand Down
19 changes: 2 additions & 17 deletions src/lib/actions/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const mocks = vi.hoisted(() => ({
recoverNamedGatewayRuntime: vi.fn().mockResolvedValue({ recovered: true }),
runDeployAction: vi.fn().mockResolvedValue(undefined),
runOnboardAction: vi.fn().mockResolvedValue(undefined),
runOpenshell: vi.fn(() => ({ status: 0 })),
version: vi.fn(),
}));

Expand All @@ -25,7 +24,6 @@ vi.mock("./maintenance", () => ({
vi.mock("./onboard", () => ({
runOnboardAction: mocks.runOnboardAction,
}));
vi.mock("../adapters/openshell/runtime", () => ({ runOpenshell: mocks.runOpenshell }));
vi.mock("./root-help", () => ({ help: mocks.help, version: mocks.version }));

import {
Expand All @@ -34,7 +32,6 @@ import {
runDeployAction,
runGarbageCollectImagesAction,
runOnboardAction,
runOpenshellProviderCommand,
runUpgradeSandboxesAction,
setGlobalCliActionRuntimeHooksForTest,
showRootHelp,
Expand Down Expand Up @@ -63,36 +60,24 @@ describe("global cli action facade", () => {
expect(mocks.version).toHaveBeenCalledWith();
});

it("uses injected runtime hooks for gateway recovery, OpenShell, and upgrades", async () => {
it("uses injected runtime hooks for gateway recovery and upgrades", async () => {
const recoverHook = vi.fn().mockResolvedValue({ recovered: false });
const runOpenshellHook = vi.fn(() => ({ status: 0 }));
const upgradeHook = vi.fn().mockResolvedValue(undefined);
setGlobalCliActionRuntimeHooksForTest({
recoverNamedGatewayRuntime: recoverHook,
runOpenshell: runOpenshellHook as never,
upgradeSandboxes: upgradeHook,
});

await expect(recoverNamedGatewayRuntime()).resolves.toEqual({ recovered: false });
runOpenshellProviderCommand(["provider", "list"], { timeout: 100 });
await runUpgradeSandboxesAction({ check: true });

expect(recoverHook).toHaveBeenCalledWith();
expect(runOpenshellHook).toHaveBeenCalledWith(
["provider", "list"],
expect.objectContaining({ timeout: 100, replaceEnv: true, env: expect.any(Object) }),
);
expect(upgradeHook).toHaveBeenCalledWith({ check: true });
});

it("falls back to default runtime hooks", async () => {
it("uses default gateway recovery without an injected hook", async () => {
await expect(recoverNamedGatewayRuntime()).resolves.toEqual({ recovered: true });
runOpenshellProviderCommand(["provider", "list"]);

expect(mocks.recoverNamedGatewayRuntime).toHaveBeenCalledWith();
expect(mocks.runOpenshell).toHaveBeenCalledWith(
["provider", "list"],
expect.objectContaining({ replaceEnv: true, env: expect.any(Object) }),
);
});
});
28 changes: 0 additions & 28 deletions src/lib/actions/global.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { runOpenshell } from "../adapters/openshell/runtime";
import {
type GarbageCollectImagesOptions,
type UpgradeSandboxesOptions,
} from "../domain/lifecycle/options";
import { recoverNamedGatewayRuntime as recoverNamedGatewayRuntimeAction } from "../gateway-runtime-action";
import type { OnboardFlags } from "../onboard/command-support";
import { buildSubprocessEnv } from "../subprocess-env";
import { runDeployAction as executeDeployAction } from "./deploy";
import {
backupAll as executeBackupAllAction,
Expand All @@ -21,7 +19,6 @@ type GatewayRecovery = { recovered: boolean };

type GlobalCliActionRuntimeHooks = {
recoverNamedGatewayRuntime?: () => Promise<GatewayRecovery>;
runOpenshell?: typeof runOpenshell;
upgradeSandboxes?: (options?: string[] | UpgradeSandboxesOptions) => Promise<void>;
recordExtraProvider?: (name: string) => boolean;
forgetExtraProvider?: (name: string) => boolean;
Expand Down Expand Up @@ -79,31 +76,6 @@ export async function recoverNamedGatewayRuntime(): Promise<GatewayRecovery> {
return recoverNamedGatewayRuntimeAction();
}

export function runOpenshellProviderCommand(
args: string[],
opts?: {
env?: Record<string, string | undefined>;
ignoreError?: boolean;
stdio?: import("node:child_process").StdioOptions;
timeout?: number;
},
) {
const explicitEnv = Object.fromEntries(
Object.entries(opts?.env ?? {}).filter(
(entry): entry is [string, string] => entry[1] !== undefined,
),
);
const providerOpts = {
...opts,
env: buildSubprocessEnv(explicitEnv),
replaceEnv: true,
};
if (typeof runtimeHooks.runOpenshell === "function") {
return runtimeHooks.runOpenshell(args, providerOpts);
}
return runOpenshell(args, providerOpts);
}

export function recordExtraProvider(name: string): boolean {
if (typeof runtimeHooks.recordExtraProvider === "function") {
return runtimeHooks.recordExtraProvider(name);
Expand Down
18 changes: 18 additions & 0 deletions src/lib/actions/sandbox/auto-pair-approval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ import path from "node:path";

import { shellQuote } from "../../core/shell-quote";
import { ROOT } from "../../state/paths";
import {
CONNECT_AUTO_PAIR_APPROVE_TIMEOUT_S,
CONNECT_AUTO_PAIR_LIST_TIMEOUT_S,
CONNECT_AUTO_PAIR_MAX_APPROVALS,
CONNECT_AUTO_PAIR_TIMEOUT_MS,
} from "./connect-autopair-budget";

// Bound the in-sandbox work: 2s list + 1s × MAX_APPROVALS attempts plus
// shell/python startup slack fits inside the outer spawnSync cap, so a wedged
Expand All @@ -53,6 +59,13 @@ export const AUTO_PAIR_APPROVAL_TIMEOUT_MS = 12_000;
const AUTO_PAIR_LIST_TIMEOUT_S = 2;
const AUTO_PAIR_APPROVE_TIMEOUT_S = 1;

const CONNECT_AUTO_PAIR_BUDGET = {
maxApprovals: CONNECT_AUTO_PAIR_MAX_APPROVALS,
listTimeoutS: CONNECT_AUTO_PAIR_LIST_TIMEOUT_S,
approveTimeoutS: CONNECT_AUTO_PAIR_APPROVE_TIMEOUT_S,
timeoutMs: CONNECT_AUTO_PAIR_TIMEOUT_MS,
} as const;

// Per-surface budget overrides. The connect/probe/finalization surfaces (#4504)
// supply a tighter budget — a single realistic pending CLI/webchat scope
// upgrade (maxApprovals = 1) on the watcher's 10s approve budget with a 15s
Expand Down Expand Up @@ -457,3 +470,8 @@ export function runSandboxAutoPairApprovalPass(
};
}
}

/** Run the approval pass with the shared connect, probe, and finalization budget. */
export function runConnectAutoPairApprovalPass(sandboxName: string): void {
runSandboxAutoPairApprovalPass(sandboxName, { budget: CONNECT_AUTO_PAIR_BUDGET });
}
4 changes: 2 additions & 2 deletions src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe("connectSandbox flow", () => {
);
expect(harness.checkAndRecoverSpy).toHaveBeenCalledWith("alpha");
expect(harness.ensureOllamaAuthProxySpy).toHaveBeenCalledTimes(1);
expect(harness.runAutoPairSpy).toHaveBeenCalledWith("alpha", expect.any(Object));
expect(harness.runAutoPairSpy).toHaveBeenCalledWith("alpha");
expect(harness.spawnSyncSpy).toHaveBeenCalledWith(
"openshell",
["sandbox", "connect", "alpha"],
Expand Down Expand Up @@ -461,7 +461,7 @@ describe("connectSandbox flow", () => {
await expect(harness.connectSandbox("alpha", { probeOnly: true })).resolves.toBeUndefined();

expect(harness.checkAndRecoverSpy).toHaveBeenCalledWith("alpha", { quiet: true });
expect(harness.runAutoPairSpy).toHaveBeenCalledWith("alpha", expect.any(Object));
expect(harness.runAutoPairSpy).toHaveBeenCalledWith("alpha");
expect(harness.spawnSyncSpy).not.toHaveBeenCalledWith(
"openshell",
["sandbox", "connect", "alpha"],
Expand Down
28 changes: 3 additions & 25 deletions src/lib/actions/sandbox/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,7 @@ import {
getActiveSandboxSessions,
} from "../../state/sandbox-session";
import { runSetupDnsProxy } from "../dns";
import { runSandboxAutoPairApprovalPass } from "./auto-pair-approval";
import {
CONNECT_AUTO_PAIR_APPROVE_TIMEOUT_S,
CONNECT_AUTO_PAIR_LIST_TIMEOUT_S,
CONNECT_AUTO_PAIR_MAX_APPROVALS,
CONNECT_AUTO_PAIR_TIMEOUT_MS,
} from "./connect-autopair-budget";
import { runConnectAutoPairApprovalPass } from "./auto-pair-approval";
import {
exitOnMcpReconciliationRefusal,
exitOnSecretBoundaryRefusal,
Expand Down Expand Up @@ -87,6 +81,8 @@ import {
import { runTerminalAgentConnectProbe } from "./terminal-connect-probe";
import { applyOpenShellVmDnsMonkeypatch, shouldApplyVmDnsMonkeypatch } from "./vm-dns-monkeypatch";

export { runConnectAutoPairApprovalPass };

export type SandboxConnectOptions = {
probeOnly?: boolean;
};
Expand Down Expand Up @@ -855,24 +851,6 @@ async function ensureSandboxInferenceRouteOrExit(
return result.sandbox;
}

// Connect/probe/finalization budget for the shared auto-pair approval pass
// (#4504). The bounded single-request budget, timeout rationale, and invariant
// live in the dependency-free ./connect-autopair-budget leaf so tests assert the
// real values without importing this heavy module. The doctor recovery surface
// (#4616) keeps the wider default budget in ./auto-pair-approval.
const CONNECT_AUTO_PAIR_BUDGET = {
maxApprovals: CONNECT_AUTO_PAIR_MAX_APPROVALS,
listTimeoutS: CONNECT_AUTO_PAIR_LIST_TIMEOUT_S,
approveTimeoutS: CONNECT_AUTO_PAIR_APPROVE_TIMEOUT_S,
timeoutMs: CONNECT_AUTO_PAIR_TIMEOUT_MS,
} as const;

// Thin wrapper so the connect/probe/finalization surfaces share one budget
// without each caller restating it. Best-effort; never throws (#4263/#4504).
export function runConnectAutoPairApprovalPass(sandboxName: string): void {
runSandboxAutoPairApprovalPass(sandboxName, { budget: CONNECT_AUTO_PAIR_BUDGET });
}

function maybeEnsureHermesToolGatewayBroker(sb: SandboxEntry | null): void {
if (
!sb ||
Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/mcp-bridge-adapter-hermes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { runOpenshellProviderCommand } from "../../actions/global";
import { runOpenshellProviderCommand } from "../../adapters/openshell/provider-command";
import { waitUntil } from "../../core/wait";
import { isShieldsDown } from "../../shields";
import type { McpBridgeEntry } from "../../state/registry";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ vi.mock("./process-recovery", () => ({
executeGatewaySupervisorAction: mocks.executeGatewaySupervisorAction,
}));

vi.mock("../../actions/global", () => ({
vi.mock("../../adapters/openshell/provider-command", () => ({
runOpenshellProviderCommand: mocks.runOpenshellProviderCommand,
}));

Expand Down
Loading
Loading