From dfe3d8ece5a572dafa9fac9b0e2c58a53f3b9ae2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 7 May 2026 00:32:22 -0700 Subject: [PATCH 1/5] refactor(cli): organize migrated workflow modules --- src/commands/internal/README.md | 16 ++++++++++++ src/commands/internal/dev/npm-link-or-shim.ts | 2 +- .../internal/installer/normalize-env.ts | 2 +- src/commands/internal/installer/plan.ts | 2 +- .../internal/uninstall/classify-shim.ts | 2 +- src/commands/internal/uninstall/plan.ts | 2 +- src/commands/internal/uninstall/run-plan.ts | 2 +- src/lib/actions/README.md | 26 +++++++++++++++++++ .../npm-link-or-shim.test.ts} | 4 +-- .../{dev-shim.ts => dev/npm-link-or-shim.ts} | 2 +- .../{dns.test.ts => dns/index.test.ts} | 4 +-- src/lib/actions/{dns.ts => dns/index.ts} | 6 ++--- .../plan.test.ts} | 2 +- .../{installer-plan.ts => installer/plan.ts} | 8 +++--- .../plan.test.ts} | 4 +-- .../{uninstall-plan.ts => uninstall/plan.ts} | 6 ++--- .../run-plan.test.ts} | 2 +- .../run-plan.ts} | 8 +++--- src/lib/adapters/README.md | 8 ++++++ src/lib/domain/README.md | 21 +++++++++++++++ .../npm-link-or-shim.test.ts} | 2 +- .../{dev-shim.ts => dev/npm-link-or-shim.ts} | 0 test/npm-link-or-shim.test.ts | 2 +- 23 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 src/commands/internal/README.md create mode 100644 src/lib/actions/README.md rename src/lib/actions/{dev-shim.test.ts => dev/npm-link-or-shim.test.ts} (97%) rename src/lib/actions/{dev-shim.ts => dev/npm-link-or-shim.ts} (99%) rename src/lib/actions/{dns.test.ts => dns/index.test.ts} (98%) rename src/lib/actions/{dns.ts => dns/index.ts} (99%) rename src/lib/actions/{installer-plan.test.ts => installer/plan.test.ts} (96%) rename src/lib/actions/{installer-plan.ts => installer/plan.ts} (95%) rename src/lib/actions/{uninstall-plan.test.ts => uninstall/plan.test.ts} (93%) rename src/lib/actions/{uninstall-plan.ts => uninstall/plan.ts} (92%) rename src/lib/actions/{uninstall-run-plan.test.ts => uninstall/run-plan.test.ts} (99%) rename src/lib/actions/{uninstall-run-plan.ts => uninstall/run-plan.ts} (98%) create mode 100644 src/lib/adapters/README.md create mode 100644 src/lib/domain/README.md rename src/lib/domain/{dev-shim.test.ts => dev/npm-link-or-shim.test.ts} (97%) rename src/lib/domain/{dev-shim.ts => dev/npm-link-or-shim.ts} (100%) diff --git a/src/commands/internal/README.md b/src/commands/internal/README.md new file mode 100644 index 00000000000..0d8bb5587cd --- /dev/null +++ b/src/commands/internal/README.md @@ -0,0 +1,16 @@ + + + +# Internal commands + +Hidden `nemoclaw internal ...` commands are compatibility entrypoints for repo-owned scripts and migration helpers. They are not a public API. + +Keep command files thin: + +```text +src/commands/internal//.ts + -> parse flags/args + -> call src/lib/actions//.ts +``` + +Behavior belongs in `src/lib/actions/**`, pure decisions belong in `src/lib/domain/**`, and process/filesystem/Docker/OpenShell boundaries belong in `src/lib/adapters/**`. diff --git a/src/commands/internal/dev/npm-link-or-shim.ts b/src/commands/internal/dev/npm-link-or-shim.ts index 1172285cf0b..7da82a7cd84 100644 --- a/src/commands/internal/dev/npm-link-or-shim.ts +++ b/src/commands/internal/dev/npm-link-or-shim.ts @@ -3,7 +3,7 @@ import { Flags } from "@oclif/core"; -import { runNpmLinkOrShim } from "../../../lib/actions/dev-shim"; +import { runNpmLinkOrShim } from "../../../lib/actions/dev/npm-link-or-shim"; import { NemoClawCommand } from "../../../lib/cli/nemoclaw-oclif-command"; export default class InternalDevNpmLinkOrShimCommand extends NemoClawCommand { diff --git a/src/commands/internal/installer/normalize-env.ts b/src/commands/internal/installer/normalize-env.ts index d5060b4afbb..2903093cbcf 100644 --- a/src/commands/internal/installer/normalize-env.ts +++ b/src/commands/internal/installer/normalize-env.ts @@ -3,7 +3,7 @@ import { Command, Flags } from "@oclif/core"; -import { normalizeInstallerEnv } from "../../../lib/actions/installer-plan"; +import { normalizeInstallerEnv } from "../../../lib/actions/installer/plan"; export default class InternalInstallerNormalizeEnvCommand extends Command { static hidden = true; diff --git a/src/commands/internal/installer/plan.ts b/src/commands/internal/installer/plan.ts index df8bb4c8cca..caffee124e4 100644 --- a/src/commands/internal/installer/plan.ts +++ b/src/commands/internal/installer/plan.ts @@ -3,7 +3,7 @@ import { Command, Flags } from "@oclif/core"; -import { buildInstallerPlan } from "../../../lib/actions/installer-plan"; +import { buildInstallerPlan } from "../../../lib/actions/installer/plan"; export default class InternalInstallerPlanCommand extends Command { static hidden = true; diff --git a/src/commands/internal/uninstall/classify-shim.ts b/src/commands/internal/uninstall/classify-shim.ts index ad4bbd9ead7..e771008abe2 100644 --- a/src/commands/internal/uninstall/classify-shim.ts +++ b/src/commands/internal/uninstall/classify-shim.ts @@ -3,7 +3,7 @@ import { Args, Command, Flags } from "@oclif/core"; -import { classifyShimPath } from "../../../lib/actions/uninstall-plan"; +import { classifyShimPath } from "../../../lib/actions/uninstall/plan"; export default class InternalUninstallClassifyShimCommand extends Command { static hidden = true; diff --git a/src/commands/internal/uninstall/plan.ts b/src/commands/internal/uninstall/plan.ts index 7bd887e5bcc..67bc4bc6f13 100644 --- a/src/commands/internal/uninstall/plan.ts +++ b/src/commands/internal/uninstall/plan.ts @@ -3,7 +3,7 @@ import { Command, Flags } from "@oclif/core"; -import { buildHostUninstallPlan } from "../../../lib/actions/uninstall-plan"; +import { buildHostUninstallPlan } from "../../../lib/actions/uninstall/plan"; export default class InternalUninstallPlanCommand extends Command { static hidden = true; diff --git a/src/commands/internal/uninstall/run-plan.ts b/src/commands/internal/uninstall/run-plan.ts index 9776095978a..8aea6162fe7 100644 --- a/src/commands/internal/uninstall/run-plan.ts +++ b/src/commands/internal/uninstall/run-plan.ts @@ -3,7 +3,7 @@ import { Command, Flags } from "@oclif/core"; -import { runUninstallPlan } from "../../../lib/actions/uninstall-run-plan"; +import { runUninstallPlan } from "../../../lib/actions/uninstall/run-plan"; export default class InternalUninstallRunPlanCommand extends Command { static hidden = true; diff --git a/src/lib/actions/README.md b/src/lib/actions/README.md new file mode 100644 index 00000000000..47b21f625fb --- /dev/null +++ b/src/lib/actions/README.md @@ -0,0 +1,26 @@ + + + +# Actions + +Actions orchestrate CLI workflows. They may compose domain helpers, adapters, state modules, and user-facing output, but they should stay independent of oclif command classes. + +Preferred layout for migrated workflows: + +```text +src/lib/actions//.ts +src/lib/actions//.test.ts +``` + +Examples: + +```text +dns/index.ts # internal dns fix-coredns/setup-proxy orchestration +dev/npm-link-or-shim.ts # prepare-time dev shim orchestration +installer/plan.ts # deterministic installer planning +uninstall/plan.ts # host uninstall planning +uninstall/run-plan.ts # uninstall plan application +sandbox/*.ts # public sandbox workflows +``` + +Use `src/lib/domain/**` for pure decisions and `src/lib/adapters/**` for process/filesystem/network boundaries. diff --git a/src/lib/actions/dev-shim.test.ts b/src/lib/actions/dev/npm-link-or-shim.test.ts similarity index 97% rename from src/lib/actions/dev-shim.test.ts rename to src/lib/actions/dev/npm-link-or-shim.test.ts index 247d55fc936..ddd72412f84 100644 --- a/src/lib/actions/dev-shim.test.ts +++ b/src/lib/actions/dev/npm-link-or-shim.test.ts @@ -6,8 +6,8 @@ import os from "node:os"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; -import { DEV_SHIM_MARKER } from "../domain/dev-shim"; -import { runNpmLinkOrShim } from "./dev-shim"; +import { DEV_SHIM_MARKER } from "../../domain/dev/npm-link-or-shim"; +import { runNpmLinkOrShim } from "./npm-link-or-shim"; function setupRepo(): { binPath: string; homeDir: string; repoDir: string; tmpDir: string } { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dev-shim-action-")); diff --git a/src/lib/actions/dev-shim.ts b/src/lib/actions/dev/npm-link-or-shim.ts similarity index 99% rename from src/lib/actions/dev-shim.ts rename to src/lib/actions/dev/npm-link-or-shim.ts index 49f909d490f..c39300f7268 100644 --- a/src/lib/actions/dev-shim.ts +++ b/src/lib/actions/dev/npm-link-or-shim.ts @@ -10,7 +10,7 @@ import { buildDevShimContents, classifyDevShim, pathContainsDirectory, -} from "../domain/dev-shim"; +} from "../../domain/dev/npm-link-or-shim"; export type ProcessResult = Pick, "stderr" | "stdout" | "status">; diff --git a/src/lib/actions/dns.test.ts b/src/lib/actions/dns/index.test.ts similarity index 98% rename from src/lib/actions/dns.test.ts rename to src/lib/actions/dns/index.test.ts index 9d64091971e..9331285083b 100644 --- a/src/lib/actions/dns.test.ts +++ b/src/lib/actions/dns/index.test.ts @@ -3,8 +3,8 @@ import { describe, expect, it, vi } from "vitest"; -import { runFixCoreDns, runSetupDnsProxy } from "../../../dist/lib/actions/dns.js"; -import type { CommandResult } from "./dns"; +import { runFixCoreDns, runSetupDnsProxy } from "../../../../dist/lib/actions/dns/index.js"; +import type { CommandResult } from "./index"; function ok(stdout = ""): CommandResult { return { status: 0, stdout, stderr: "" }; diff --git a/src/lib/actions/dns.ts b/src/lib/actions/dns/index.ts similarity index 99% rename from src/lib/actions/dns.ts rename to src/lib/actions/dns/index.ts index 4cd8f4f72ac..b2342db2ff9 100644 --- a/src/lib/actions/dns.ts +++ b/src/lib/actions/dns/index.ts @@ -6,7 +6,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { dockerSpawnSync } from "../adapters/docker/exec"; +import { dockerSpawnSync } from "../../adapters/docker/exec"; import { buildCoreDnsPatchJson, dockerHostRuntime, @@ -14,7 +14,7 @@ import { resolveCoreDnsUpstream, selectOpenshellClusterContainer, type ContainerRuntime, -} from "../domain/dns/coredns"; +} from "../../domain/dns/coredns"; import { buildDnsProxyPython, buildDnsReadyProbePython, @@ -24,7 +24,7 @@ import { parseVethGateway, selectSandboxNamespace, selectSandboxPod, -} from "../domain/dns/setup-proxy"; +} from "../../domain/dns/setup-proxy"; export type CommandResult = Pick, "stderr" | "stdout" | "status">; diff --git a/src/lib/actions/installer-plan.test.ts b/src/lib/actions/installer/plan.test.ts similarity index 96% rename from src/lib/actions/installer-plan.test.ts rename to src/lib/actions/installer/plan.test.ts index 4112e212bf8..bea14df98eb 100644 --- a/src/lib/actions/installer-plan.test.ts +++ b/src/lib/actions/installer/plan.test.ts @@ -5,7 +5,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; -import { buildInstallerPlan, normalizeInstallerEnv } from "./installer-plan"; +import { buildInstallerPlan, normalizeInstallerEnv } from "./plan"; const writableState = { exists: (targetPath: string) => targetPath !== "/missing", diff --git a/src/lib/actions/installer-plan.ts b/src/lib/actions/installer/plan.ts similarity index 95% rename from src/lib/actions/installer-plan.ts rename to src/lib/actions/installer/plan.ts index fc01e4509eb..0872700536f 100644 --- a/src/lib/actions/installer-plan.ts +++ b/src/lib/actions/installer/plan.ts @@ -7,14 +7,14 @@ import { pathWithPrependedEntries, type NpmLinkTargetState, type NpmLinkTargetWritableResult, -} from "../domain/installer/npm"; +} from "../../domain/installer/npm"; import { installerProviderHelpValues, normalizeInstallerProvider, type InstallerProvider, -} from "../domain/installer/provider"; -import { resolveInstallerVersion, resolveInstallRef, type InstallerRefEnv } from "../domain/installer/ref"; -import { checkInstallerRuntime, type RuntimeCheckResult } from "../domain/installer/version"; +} from "../../domain/installer/provider"; +import { resolveInstallerVersion, resolveInstallRef, type InstallerRefEnv } from "../../domain/installer/ref"; +import { checkInstallerRuntime, type RuntimeCheckResult } from "../../domain/installer/version"; export interface InstallerPlanEnv extends InstallerRefEnv { NEMOCLAW_PROVIDER?: string | undefined; diff --git a/src/lib/actions/uninstall-plan.test.ts b/src/lib/actions/uninstall/plan.test.ts similarity index 93% rename from src/lib/actions/uninstall-plan.test.ts rename to src/lib/actions/uninstall/plan.test.ts index c00764591b1..44d12e6bccf 100644 --- a/src/lib/actions/uninstall-plan.test.ts +++ b/src/lib/actions/uninstall/plan.test.ts @@ -6,8 +6,8 @@ import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { buildHostUninstallPlan, classifyShimPath } from "./uninstall-plan"; -import { flattenUninstallPlan } from "../domain/uninstall/plan"; +import { flattenUninstallPlan } from "../../domain/uninstall/plan"; +import { buildHostUninstallPlan, classifyShimPath } from "./plan"; describe("uninstall plan actions", () => { it("classifies an on-disk dev shim", () => { diff --git a/src/lib/actions/uninstall-plan.ts b/src/lib/actions/uninstall/plan.ts similarity index 92% rename from src/lib/actions/uninstall-plan.ts rename to src/lib/actions/uninstall/plan.ts index f9885ad76a1..ee01616dcaa 100644 --- a/src/lib/actions/uninstall-plan.ts +++ b/src/lib/actions/uninstall/plan.ts @@ -4,9 +4,9 @@ /* v8 ignore start -- covered by source-level unit tests; CLI coverage tracks dist integration. */ import fs from "node:fs"; -import { defaultUninstallPaths } from "../domain/uninstall/paths"; -import { buildUninstallPlan, type UninstallPlan, type UninstallPlanOptions } from "../domain/uninstall/plan"; -import { classifyNemoclawShim, type ShimClassification } from "../domain/uninstall/shims"; +import { defaultUninstallPaths } from "../../domain/uninstall/paths"; +import { buildUninstallPlan, type UninstallPlan, type UninstallPlanOptions } from "../../domain/uninstall/plan"; +import { classifyNemoclawShim, type ShimClassification } from "../../domain/uninstall/shims"; export interface FileSystemDeps { lstatSync?: typeof fs.lstatSync; diff --git a/src/lib/actions/uninstall-run-plan.test.ts b/src/lib/actions/uninstall/run-plan.test.ts similarity index 99% rename from src/lib/actions/uninstall-run-plan.test.ts rename to src/lib/actions/uninstall/run-plan.test.ts index e91fb71a1b4..b12183a452a 100644 --- a/src/lib/actions/uninstall-run-plan.test.ts +++ b/src/lib/actions/uninstall/run-plan.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; -import { buildRunPlan, runUninstallPlan, type RunResult } from "./uninstall-run-plan"; +import { buildRunPlan, runUninstallPlan, type RunResult } from "./run-plan"; function ok(stdout = ""): RunResult { return { status: 0, stdout, stderr: "" }; diff --git a/src/lib/actions/uninstall-run-plan.ts b/src/lib/actions/uninstall/run-plan.ts similarity index 98% rename from src/lib/actions/uninstall-run-plan.ts rename to src/lib/actions/uninstall/run-plan.ts index caa7ca6df7c..4b49b7d396d 100644 --- a/src/lib/actions/uninstall-run-plan.ts +++ b/src/lib/actions/uninstall/run-plan.ts @@ -7,10 +7,10 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { dockerSpawnSync } from "../adapters/docker/exec"; -import { defaultUninstallPaths, NEMOCLAW_OLLAMA_MODELS, NEMOCLAW_PROVIDERS, type UninstallPaths } from "../domain/uninstall/paths"; -import { buildUninstallPlan, type UninstallPlan } from "../domain/uninstall/plan"; -import { classifyShimPath, type FileSystemDeps } from "./uninstall-plan"; +import { dockerSpawnSync } from "../../adapters/docker/exec"; +import { defaultUninstallPaths, NEMOCLAW_OLLAMA_MODELS, NEMOCLAW_PROVIDERS, type UninstallPaths } from "../../domain/uninstall/paths"; +import { buildUninstallPlan, type UninstallPlan } from "../../domain/uninstall/plan"; +import { classifyShimPath, type FileSystemDeps } from "./plan"; export interface RunResult { status: number | null; diff --git a/src/lib/adapters/README.md b/src/lib/adapters/README.md new file mode 100644 index 00000000000..83ff8820ab0 --- /dev/null +++ b/src/lib/adapters/README.md @@ -0,0 +1,8 @@ + + + +# Adapters + +Adapters isolate process, filesystem, Docker, OpenShell, and other host-boundary calls from action and domain logic. + +Use adapters when code needs to cross a boundary that is hard to unit test directly. Actions can depend on adapters, but domain helpers should stay pure and receive already-collected inputs. diff --git a/src/lib/domain/README.md b/src/lib/domain/README.md new file mode 100644 index 00000000000..58e6bbd842d --- /dev/null +++ b/src/lib/domain/README.md @@ -0,0 +1,21 @@ + + + +# Domain helpers + +Domain modules contain pure policy and decision logic. They should not import oclif, spawn processes, read host state directly, or call Docker/OpenShell. + +Preferred layout: + +```text +src/lib/domain//.ts +src/lib/domain//.test.ts +``` + +Flat files are acceptable for small cross-cutting helpers, but workflow-specific logic should live under an area directory that matches the command/action stack when practical: + +```text +src/commands/internal//.ts +src/lib/actions//.ts +src/lib/domain//.ts +``` diff --git a/src/lib/domain/dev-shim.test.ts b/src/lib/domain/dev/npm-link-or-shim.test.ts similarity index 97% rename from src/lib/domain/dev-shim.test.ts rename to src/lib/domain/dev/npm-link-or-shim.test.ts index b255e76a098..a68bdaf5247 100644 --- a/src/lib/domain/dev-shim.test.ts +++ b/src/lib/domain/dev/npm-link-or-shim.test.ts @@ -8,7 +8,7 @@ import { buildDevShimContents, classifyDevShim, pathContainsDirectory, -} from "./dev-shim"; +} from "./npm-link-or-shim"; describe("dev shim domain helpers", () => { it("classifies absent, managed, and foreign shims", () => { diff --git a/src/lib/domain/dev-shim.ts b/src/lib/domain/dev/npm-link-or-shim.ts similarity index 100% rename from src/lib/domain/dev-shim.ts rename to src/lib/domain/dev/npm-link-or-shim.ts diff --git a/test/npm-link-or-shim.test.ts b/test/npm-link-or-shim.test.ts index 6fd6e96889e..308151dfd5e 100644 --- a/test/npm-link-or-shim.test.ts +++ b/test/npm-link-or-shim.test.ts @@ -7,7 +7,7 @@ import os from "node:os"; import path from "node:path"; import { describe, it, expect } from "vitest"; -import { DEV_SHIM_MARKER } from "../src/lib/domain/dev-shim"; +import { DEV_SHIM_MARKER } from "../src/lib/domain/dev/npm-link-or-shim"; const repoRoot = path.join(import.meta.dirname, ".."); const scriptUnderTest = path.join(repoRoot, "scripts", "npm-link-or-shim.sh"); From 9da1121fcbb4ec9cee3ac5f58747a4753628c0d3 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 7 May 2026 00:45:50 -0700 Subject: [PATCH 2/5] test(cli): isolate router and gpu platform tests --- src/lib/nim.test.ts | 51 ++++++++++++++++++++++++++++++-------------- test/onboard.test.ts | 19 +++++++++++++++-- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/lib/nim.test.ts b/src/lib/nim.test.ts index ee44cb00b38..d04688adf3c 100644 --- a/src/lib/nim.test.ts +++ b/src/lib/nim.test.ts @@ -154,6 +154,21 @@ describe("nim", () => { }); describe("detectGpu", () => { + function withGenericLinuxFirmware(fn: () => void): void { + const fs = require("fs"); + const origReadFileSync = fs.readFileSync; + fs.readFileSync = (p: string, ...args: unknown[]) => { + if (p === "/sys/class/dmi/id/product_name") return "Generic Linux Workstation"; + if (p === "/sys/firmware/devicetree/base/model") return ""; + return origReadFileSync(p, ...args); + }; + try { + fn(); + } finally { + fs.readFileSync = origReadFileSync; + } + } + it("returns object or null", () => { const gpu = nim.detectGpu(); if (gpu !== null) { @@ -330,15 +345,17 @@ describe("nim", () => { const { nimModule, restore } = loadNimWithMockedRunner(runCapture); try { - expect(nimModule.detectGpu()).toMatchObject({ - type: "nvidia", - name: "NVIDIA Jetson AGX Orin", - count: 1, - totalMemoryMB: 32768, - perGpuMB: 32768, - nimCapable: true, - unifiedMemory: true, - spark: false, + withGenericLinuxFirmware(() => { + expect(nimModule.detectGpu()).toMatchObject({ + type: "nvidia", + name: "NVIDIA Jetson AGX Orin", + count: 1, + totalMemoryMB: 32768, + perGpuMB: 32768, + nimCapable: true, + unifiedMemory: true, + spark: false, + }); }); } finally { restore(); @@ -356,13 +373,15 @@ describe("nim", () => { const { nimModule, restore } = loadNimWithMockedRunner(runCapture); try { - expect(nimModule.detectGpu()).toMatchObject({ - type: "nvidia", - name: "NVIDIA Xavier", - totalMemoryMB: 4096, - nimCapable: false, - unifiedMemory: true, - spark: false, + withGenericLinuxFirmware(() => { + expect(nimModule.detectGpu()).toMatchObject({ + type: "nvidia", + name: "NVIDIA Xavier", + totalMemoryMB: 4096, + nimCapable: false, + unifiedMemory: true, + spark: false, + }); }); } finally { restore(); diff --git a/test/onboard.test.ts b/test/onboard.test.ts index 8e813a2a857..081e2b0dd30 100644 --- a/test/onboard.test.ts +++ b/test/onboard.test.ts @@ -2429,6 +2429,7 @@ const { setupInference } = require(${onboardPath}); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-onboard-router-inference-")); const fakeBin = path.join(tmpDir, "bin"); const scriptPath = path.join(tmpDir, "setup-router-check.js"); + const routerPort = 44000 + (process.pid % 10000); const onboardPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "onboard.js")); const runnerPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "runner.js")); const registryPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "state", "registry.js")); @@ -2469,9 +2470,23 @@ const { setupInference } = require(${onboardPath}); ); const script = String.raw` +const fs = require("fs"); +const path = require("path"); const runner = require(${runnerPath}); const _n = (c) => (Array.isArray(c) ? c.join(" ") : String(c)).replace(/'/g, ""); const registry = require(${registryPath}); +const routerPort = ${routerPort}; +const blueprintPath = path.join(${JSON.stringify(repoRoot)}, "nemoclaw-blueprint", "blueprint.yaml"); +const originalReadFileSync = fs.readFileSync; +fs.readFileSync = (filePath, ...args) => { + const raw = originalReadFileSync(filePath, ...args); + if (filePath === blueprintPath || String(filePath) === blueprintPath) { + return String(raw) + .replace('endpoint: "http://localhost:4000/v1"', 'endpoint: "http://localhost:' + routerPort + '/v1"') + .replace("port: 4000", "port: " + routerPort); + } + return raw; +}; const commands = []; runner.run = (command, opts = {}) => { @@ -2505,7 +2520,7 @@ const { setupInference, getSandboxInferenceConfig } = require(${onboardPath}); "router-box", "nvidia-routed", "nvidia-router", - "http://host.openshell.internal:4000/v1", + "http://host.openshell.internal:" + routerPort + "/v1", "NVIDIA_API_KEY", ); console.log(JSON.stringify({ @@ -2542,7 +2557,7 @@ const { setupInference, getSandboxInferenceConfig } = require(${onboardPath}); assert.match(providerCommand.command, /--credential NVIDIA_API_KEY/); assert.match( providerCommand.command, - /OPENAI_BASE_URL=http:\/\/host\.openshell\.internal:4000\/v1/, + new RegExp(`OPENAI_BASE_URL=http:\\/\\/host\\.openshell\\.internal:${routerPort}\\/v1`), ); assert.doesNotMatch(providerCommand.command, /nvapi-router-secret/); assert.equal(providerCommand.env?.NVIDIA_API_KEY, "nvapi-router-secret"); From 94c8d1486030e1edde27fda4ae4a445408ff367a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 7 May 2026 08:57:15 -0700 Subject: [PATCH 3/5] docs(cli): document lib file placement map --- src/lib/README.md | 57 +++++++++++++++++++++++++++++++++++++ src/lib/core/README.md | 20 +++++++++++++ src/lib/inference/README.md | 25 ++++++++++++++++ src/lib/onboard/README.md | 28 ++++++++++++++++++ src/lib/sandbox/README.md | 18 ++++++++++++ src/lib/security/README.md | 17 +++++++++++ 6 files changed, 165 insertions(+) create mode 100644 src/lib/README.md create mode 100644 src/lib/core/README.md create mode 100644 src/lib/inference/README.md create mode 100644 src/lib/onboard/README.md create mode 100644 src/lib/sandbox/README.md create mode 100644 src/lib/security/README.md diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 00000000000..b7e9a4ece13 --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,57 @@ + + + +# `src/lib` layout + +`src/lib` is organized by architectural role first, then by product area. Use this map when adding new code or when moving legacy flat modules into clearer homes. + +## Primary layers + +```text +src/lib/commands/** oclif adapter classes and parser glue +src/lib/actions/** workflow orchestration and user-facing command behavior +src/lib/domain/** pure decisions, policy, classification, and planning +src/lib/adapters/** host boundaries: process, fs, Docker, OpenShell, HTTP +src/lib/state/** persisted local state and state-file I/O +src/lib/cli/** CLI framework, metadata, routing, and help infrastructure +src/lib/core/** tiny cross-cutting primitives with minimal dependencies +``` + +Layering rules: + +- Command classes should parse argv and call actions; they should not own product logic. +- Actions may compose domain helpers, adapters, state modules, and output. +- Domain helpers should stay pure and avoid direct process, filesystem, Docker, OpenShell, or network calls. +- Adapters isolate host-boundary calls so actions and tests can inject fakes. +- State modules own persisted local files and registry/session I/O. + +## Transitional feature folders + +Some older modules are still too large or coupled to split directly into actions/domain/adapters. Use these feature folders as intermediate homes while preserving behavior: + +```text +src/lib/agent/** agent definition, agent-specific onboarding, runtime helpers +src/lib/credentials/** credential storage and credential command support +src/lib/dashboard/** dashboard contract, health, and recovery helpers +src/lib/deploy/** deploy/build-image support that is not yet action-shaped +src/lib/diagnostics/** debug collection and diagnostic report helpers +src/lib/inference/** inference config, health probes, local runtime helpers +src/lib/messaging/** channel/messaging policy and message filtering helpers +src/lib/onboard/** onboarding support modules around the large legacy flow +src/lib/policy/** policy preset loading and application support +src/lib/runtime/** sandbox/runtime recovery helpers +src/lib/sandbox/** sandbox config, build, stream, and version support +src/lib/security/** redaction, secret patterns, and credential filtering +src/lib/shields/** shields orchestration, timers, and audit helpers +src/lib/tunnel/** local service/tunnel command support +``` + +Prefer small mechanical PRs that move one cluster at a time. High-import legacy files such as `onboard.ts`, `runner.ts`, `policies.ts`, `nim.ts`, and `services.ts` should either move late or keep temporary compatibility re-export files at their old paths. + +## Suggested migration sequence + +1. Document the target map and conventions before moving more code. +2. Move low-risk feature clusters such as `agent`, `dashboard`, `diagnostics`, and `shields`. +3. Move security/credentials/core helpers. +4. Move inference/model/local-runtime helpers. +5. Move onboarding support files before considering the large `onboard.ts` flow. diff --git a/src/lib/core/README.md b/src/lib/core/README.md new file mode 100644 index 00000000000..ae335e9fbd8 --- /dev/null +++ b/src/lib/core/README.md @@ -0,0 +1,20 @@ + + + +# Core helpers + +`src/lib/core` is for tiny cross-cutting primitives with minimal dependencies. These helpers should be safe to import from actions, domain modules, adapters, and CLI infrastructure. + +Good candidates: + +```text +version.ts +ports.ts +json-types.ts +errno.ts +wait.ts +url-utils.ts +shell-quote.ts +``` + +Keep product workflows out of this directory. If a helper starts depending on Docker, OpenShell, filesystem state, or a specific command workflow, move it to an adapter, action, domain area, or feature folder instead. diff --git a/src/lib/inference/README.md b/src/lib/inference/README.md new file mode 100644 index 00000000000..7f88e31744a --- /dev/null +++ b/src/lib/inference/README.md @@ -0,0 +1,25 @@ + + + +# Inference + +`src/lib/inference` is for model/provider configuration, inference health checks, local runtime support, and model catalog helpers. + +Suggested homes: + +```text +config.ts inference config parsing and normalization +health.ts inference endpoint health checks +local.ts local inference orchestration helpers +provider-models.ts provider model catalog support +model-prompts.ts prompt/model display helpers +nim.ts NIM catalog and lifecycle support +ollama/model-size.ts Ollama model size parsing +ollama/proxy.ts Ollama auth proxy support +ollama/windows.ts Windows Ollama support +vllm.ts vLLM support +web-search.ts web-search capability helpers +onboard-probes.ts onboarding-time inference validation probes +``` + +Longer term, pure inference decisions should move under `src/lib/domain/inference/**`, and HTTP/process boundaries should move under `src/lib/adapters/**`. diff --git a/src/lib/onboard/README.md b/src/lib/onboard/README.md new file mode 100644 index 00000000000..d54cba0b247 --- /dev/null +++ b/src/lib/onboard/README.md @@ -0,0 +1,28 @@ + + + +# Onboard support + +`src/lib/onboard` is the transitional home for onboarding support modules while the large legacy `src/lib/onboard.ts` flow is split over time. + +Good candidates: + +```text +types.ts +providers.ts +preflight.ts +usage-notice.ts +legacy-command.ts +``` + +Related modules may live outside this folder when their ownership is clearer: + +```text +src/lib/state/onboard-session.ts persisted onboarding session state +src/lib/inference/onboard-probes.ts inference validation probes used by onboarding +src/lib/inference/ollama/proxy.ts Ollama proxy lifecycle helpers +src/lib/inference/vllm.ts vLLM onboarding helpers +src/lib/inference/ollama/windows.ts Windows Ollama support +``` + +Do not move `src/lib/onboard.ts` casually. It is high-import and high-risk; if it moves, keep a compatibility re-export path or split it through focused behavior-preserving PRs. diff --git a/src/lib/sandbox/README.md b/src/lib/sandbox/README.md new file mode 100644 index 00000000000..3a1a4cc299f --- /dev/null +++ b/src/lib/sandbox/README.md @@ -0,0 +1,18 @@ + + + +# Sandbox support + +`src/lib/sandbox` is for sandbox configuration, build context, create-stream, version, and channel support that is not already owned by an action/domain module. + +Suggested homes: + +```text +config.ts sandbox config download/upload and mutation support +build-context.ts sandbox build context construction +create-stream.ts sandbox create progress parsing +version.ts sandbox/agent version helpers +channels.ts sandbox channel support, unless moved to messaging +``` + +Command workflows should continue to live under `src/lib/actions/sandbox/**`. Pure validation/classification helpers should live under `src/lib/domain/sandbox/**`. diff --git a/src/lib/security/README.md b/src/lib/security/README.md new file mode 100644 index 00000000000..3aeb1da673d --- /dev/null +++ b/src/lib/security/README.md @@ -0,0 +1,17 @@ + + + +# Security helpers + +`src/lib/security` is for reusable redaction, secret-pattern, credential-hash, and credential-filter helpers. + +Suggested homes: + +```text +credential-filter.ts +credential-hash.ts +redact.ts +secret-patterns.ts +``` + +Credential storage belongs under `src/lib/credentials/**`; security helpers should not own persistence or user prompts. From 9a949ef0162098c7426642c64d3083128a041f99 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 7 May 2026 09:08:38 -0700 Subject: [PATCH 4/5] refactor(cli): group agent dashboard diagnostics and shields modules --- .coderabbit.yaml | 2 +- bin/lib/agent-defs.js | 6 ++-- bin/lib/agent-onboard.js | 6 ++-- bin/lib/agent-runtime.js | 6 ++-- scripts/check-legacy-migrated-paths.ts | 2 +- scripts/ts-migration-assist.ts | 6 ++-- src/lib/actions/onboard.ts | 2 +- src/lib/actions/sandbox/rebuild.ts | 4 +-- .../base-image.test.ts} | 18 +++++------ .../defs.test.ts} | 2 +- src/lib/{agent-defs.ts => agent/defs.ts} | 4 +-- .../onboard.test.ts} | 8 ++--- .../{agent-onboard.ts => agent/onboard.ts} | 16 +++++----- .../runtime.test.ts} | 4 +-- .../{agent-runtime.ts => agent/runtime.ts} | 10 +++---- src/lib/commands/debug.ts | 8 ++--- .../simple-global-oclif-adapters.test.ts | 4 +-- .../contract.test.ts} | 2 +- .../contract.ts} | 4 +-- .../health.test.ts} | 4 +-- .../health.ts} | 2 +- .../recover.test.ts} | 4 +-- .../recover.ts} | 6 ++-- .../{ => diagnostics}/debug-command.test.ts | 2 +- src/lib/{ => diagnostics}/debug-command.ts | 0 src/lib/{ => diagnostics}/debug.test.ts | 2 +- src/lib/{ => diagnostics}/debug.ts | 12 ++++---- src/lib/onboard.ts | 10 +++---- src/lib/policies.ts | 2 +- src/lib/sandbox-config.ts | 4 +-- src/lib/sandbox-version.test.ts | 2 +- src/lib/sandbox-version.ts | 2 +- .../lib/shields/audit.test.ts | 0 .../{shields-audit.ts => shields/audit.ts} | 4 +-- .../lib/shields/index.test.ts | 30 ++++++++----------- src/lib/{shields.ts => shields/index.ts} | 14 ++++----- .../{shields-timer.ts => shields/timer.ts} | 8 ++--- src/lib/state/sandbox.ts | 4 +-- test/config-set-nested-ssrf.test.ts | 12 ++++---- test/onboard.test.ts | 8 ++--- test/repro-2681-group-writable.test.ts | 12 ++++---- test/secret-redaction.test.ts | 2 +- 42 files changed, 127 insertions(+), 133 deletions(-) rename src/lib/{agent-base-image.test.ts => agent/base-image.test.ts} (87%) rename src/lib/{agent-defs.test.ts => agent/defs.test.ts} (99%) rename src/lib/{agent-defs.ts => agent/defs.ts} (99%) rename src/lib/{agent-onboard.test.ts => agent/onboard.test.ts} (97%) rename src/lib/{agent-onboard.ts => agent/onboard.ts} (97%) rename src/lib/{agent-runtime.test.ts => agent/runtime.test.ts} (99%) rename src/lib/{agent-runtime.ts => agent/runtime.ts} (98%) rename src/lib/{dashboard-contract.test.ts => dashboard/contract.test.ts} (97%) rename src/lib/{dashboard-contract.ts => dashboard/contract.ts} (97%) rename src/lib/{dashboard-health.test.ts => dashboard/health.test.ts} (94%) rename src/lib/{dashboard-health.ts => dashboard/health.ts} (98%) rename src/lib/{dashboard-recover.test.ts => dashboard/recover.test.ts} (94%) rename src/lib/{dashboard-recover.ts => dashboard/recover.ts} (88%) rename src/lib/{ => diagnostics}/debug-command.test.ts (98%) rename src/lib/{ => diagnostics}/debug-command.ts (100%) rename src/lib/{ => diagnostics}/debug.test.ts (99%) rename src/lib/{ => diagnostics}/debug.ts (98%) rename test/shields-audit.test.ts => src/lib/shields/audit.test.ts (100%) rename src/lib/{shields-audit.ts => shields/audit.ts} (94%) rename test/shields.test.ts => src/lib/shields/index.test.ts (95%) rename src/lib/{shields.ts => shields/index.ts} (99%) rename src/lib/{shields-timer.ts => shields/timer.ts} (97%) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 2a7742140e9..3b5a58edbf7 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -251,7 +251,7 @@ reviews: gh workflow run nightly-e2e.yaml --ref -f jobs=snapshot-commands-e2e,rebuild-openclaw-e2e ``` - - path: "src/lib/shields*.ts" + - path: "src/lib/shields/**" instructions: | These files control shields down/up, config mutability, audit trail, and auto-restore timer. diff --git a/bin/lib/agent-defs.js b/bin/lib/agent-defs.js index 086c5bcc4ce..ce13367a1e1 100644 --- a/bin/lib/agent-defs.js +++ b/bin/lib/agent-defs.js @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // -// Thin re-export shim — the implementation lives in src/lib/agent-defs.ts, -// compiled to dist/lib/agent-defs.js. +// Thin re-export shim — the implementation lives in src/lib/agent/defs.ts, +// compiled to dist/lib/agent/defs.js. -module.exports = require("../../dist/lib/agent-defs"); +module.exports = require("../../dist/lib/agent/defs"); diff --git a/bin/lib/agent-onboard.js b/bin/lib/agent-onboard.js index 94cd467c8cf..70257c589af 100644 --- a/bin/lib/agent-onboard.js +++ b/bin/lib/agent-onboard.js @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // -// Thin re-export shim — the implementation lives in src/lib/agent-onboard.ts, -// compiled to dist/lib/agent-onboard.js. +// Thin re-export shim — the implementation lives in src/lib/agent/onboard.ts, +// compiled to dist/lib/agent/onboard.js. -module.exports = require("../../dist/lib/agent-onboard"); +module.exports = require("../../dist/lib/agent/onboard"); diff --git a/bin/lib/agent-runtime.js b/bin/lib/agent-runtime.js index 447eee0dc81..9ca1b64020e 100644 --- a/bin/lib/agent-runtime.js +++ b/bin/lib/agent-runtime.js @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // -// Thin re-export shim — the implementation lives in src/lib/agent-runtime.ts, -// compiled to dist/lib/agent-runtime.js. +// Thin re-export shim — the implementation lives in src/lib/agent/runtime.ts, +// compiled to dist/lib/agent/runtime.js. -module.exports = require("../../dist/lib/agent-runtime"); +module.exports = require("../../dist/lib/agent/runtime"); diff --git a/scripts/check-legacy-migrated-paths.ts b/scripts/check-legacy-migrated-paths.ts index 2368b01a7a1..8cffad1a7af 100644 --- a/scripts/check-legacy-migrated-paths.ts +++ b/scripts/check-legacy-migrated-paths.ts @@ -26,7 +26,7 @@ const RUNTIME_MOVES: Record = moveMap.runtimeMoves; const REMOVED_SHIM_MOVES: Record = { "bin/lib/chat-filter.js": "src/lib/chat-filter.ts", "bin/lib/config-io.js": "src/lib/state/config-io.ts", - "bin/lib/debug.js": "src/lib/debug.ts", + "bin/lib/debug.js": "src/lib/diagnostics/debug.ts", "bin/lib/inference-config.js": "src/lib/inference-config.ts", "bin/lib/local-inference.js": "src/lib/local-inference.ts", "bin/lib/nim.js": "src/lib/nim.ts", diff --git a/scripts/ts-migration-assist.ts b/scripts/ts-migration-assist.ts index 410283a00db..60633acdc62 100644 --- a/scripts/ts-migration-assist.ts +++ b/scripts/ts-migration-assist.ts @@ -48,9 +48,9 @@ const SPECIAL_REWRITES: Record> = { ['require("./lib/runtime-recovery")', 'require("../bin/lib/runtime-recovery")'], ['require("./lib/usage-notice")', 'require("../bin/lib/usage-notice")'], ['require("./lib/services")', 'require("../bin/lib/services")'], - ['require("./lib/debug")', 'require("../bin/lib/debug")'], - ['require("./lib/debug-command")', 'require("./lib/debug-command")'], - ['require("../dist/lib/debug-command")', 'require("./lib/debug-command")'], + ['require("./lib/debug")', 'require("./lib/diagnostics/debug")'], + ['require("./lib/debug-command")', 'require("./lib/diagnostics/debug-command")'], + ['require("../dist/lib/debug-command")', 'require("./lib/diagnostics/debug-command")'], ['require("../dist/lib/openshell")', 'require("./lib/openshell")'], ['require("../dist/lib/inventory-commands")', 'require("./lib/inventory-commands")'], ['require("../dist/lib/deploy")', 'require("./lib/deploy")'], diff --git a/src/lib/actions/onboard.ts b/src/lib/actions/onboard.ts index 133ff026ea4..c5a0b4963a2 100644 --- a/src/lib/actions/onboard.ts +++ b/src/lib/actions/onboard.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { listAgents } from "../agent-defs"; +import { listAgents } from "../agent/defs"; import { runDeprecatedOnboardAliasCommand, runOnboardCommand } from "../onboard-command"; import { NOTICE_ACCEPT_ENV, NOTICE_ACCEPT_FLAG } from "../usage-notice"; diff --git a/src/lib/actions/sandbox/rebuild.ts b/src/lib/actions/sandbox/rebuild.ts index 5313fc20125..6e582cacd38 100644 --- a/src/lib/actions/sandbox/rebuild.ts +++ b/src/lib/actions/sandbox/rebuild.ts @@ -18,8 +18,8 @@ const { LOCAL_INFERENCE_PROVIDERS, REMOTE_PROVIDER_CONFIG } = require("../../onb REMOTE_PROVIDER_CONFIG: Record; }; -import { loadAgent } from "../../agent-defs"; -import { ensureAgentBaseImage } from "../../agent-onboard"; +import { loadAgent } from "../../agent/defs"; +import { ensureAgentBaseImage } from "../../agent/onboard"; import { getSandboxDeleteOutcome } from "../../domain/sandbox/destroy"; import * as nim from "../../nim"; import type { Session } from "../../onboard-session"; diff --git a/src/lib/agent-base-image.test.ts b/src/lib/agent/base-image.test.ts similarity index 87% rename from src/lib/agent-base-image.test.ts rename to src/lib/agent/base-image.test.ts index 08c4d0b740a..987c46887be 100644 --- a/src/lib/agent-base-image.test.ts +++ b/src/lib/agent/base-image.test.ts @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { AgentDefinition } from "./agent-defs"; +import type { AgentDefinition } from "./defs"; -type AgentOnboardModule = typeof import("../../dist/lib/agent-onboard"); -type DockerImageModule = typeof import("../../dist/lib/adapters/docker/image"); -type DockerInspectModule = typeof import("../../dist/lib/adapters/docker/inspect"); +type AgentOnboardModule = typeof import("../../../dist/lib/agent/onboard"); +type DockerImageModule = typeof import("../../../dist/lib/adapters/docker/image"); +type DockerInspectModule = typeof import("../../../dist/lib/adapters/docker/inspect"); /** * Build a minimal Hermes agent manifest for base-image provisioning tests. @@ -56,14 +56,14 @@ function withMockedDocker( }) => T, ): T { // eslint-disable-next-line @typescript-eslint/no-require-imports - const dockerImageModule = require("../../dist/lib/adapters/docker/image") as DockerImageModule; + const dockerImageModule = require("../../../dist/lib/adapters/docker/image") as DockerImageModule; // eslint-disable-next-line @typescript-eslint/no-require-imports - const dockerInspectModule = require("../../dist/lib/adapters/docker/inspect") as DockerInspectModule; + const dockerInspectModule = require("../../../dist/lib/adapters/docker/inspect") as DockerInspectModule; // eslint-disable-next-line @typescript-eslint/no-require-imports - const runnerModule = require("../../dist/lib/runner") as { ROOT: string }; + const runnerModule = require("../../../dist/lib/runner") as { ROOT: string }; const originalDockerBuild = dockerImageModule.dockerBuild; const originalDockerImageInspect = dockerInspectModule.dockerImageInspect; - const agentOnboardModulePath = require.resolve("../../dist/lib/agent-onboard"); + const agentOnboardModulePath = require.resolve("../../../dist/lib/agent/onboard"); delete require.cache[agentOnboardModulePath]; const dockerBuildMock = vi.fn().mockReturnValue({ status: 0 }); @@ -74,7 +74,7 @@ function withMockedDocker( try { // eslint-disable-next-line @typescript-eslint/no-require-imports - const agentOnboardModule = require("../../dist/lib/agent-onboard") as AgentOnboardModule; + const agentOnboardModule = require("../../../dist/lib/agent/onboard") as AgentOnboardModule; return run({ ensureAgentBaseImage: agentOnboardModule.ensureAgentBaseImage, dockerBuildMock, diff --git a/src/lib/agent-defs.test.ts b/src/lib/agent/defs.test.ts similarity index 99% rename from src/lib/agent-defs.test.ts rename to src/lib/agent/defs.test.ts index e3329b99d7e..98ff8d24b71 100644 --- a/src/lib/agent-defs.test.ts +++ b/src/lib/agent/defs.test.ts @@ -11,7 +11,7 @@ import { getAgentChoices, loadAgent, resolveAgentName, -} from "../../dist/lib/agent-defs"; +} from "../../../dist/lib/agent/defs"; const tempAgentDirs: string[] = []; diff --git a/src/lib/agent-defs.ts b/src/lib/agent/defs.ts similarity index 99% rename from src/lib/agent-defs.ts rename to src/lib/agent/defs.ts index 5c44c441d02..895e621f9d6 100644 --- a/src/lib/agent-defs.ts +++ b/src/lib/agent/defs.ts @@ -7,8 +7,8 @@ import fs from "node:fs"; import path from "node:path"; -import { ROOT } from "./runner"; -import { DASHBOARD_PORT } from "./ports"; +import { ROOT } from "../runner"; +import { DASHBOARD_PORT } from "../ports"; export const AGENTS_DIR = path.join(ROOT, "agents"); diff --git a/src/lib/agent-onboard.test.ts b/src/lib/agent/onboard.test.ts similarity index 97% rename from src/lib/agent-onboard.test.ts rename to src/lib/agent/onboard.test.ts index d1b48d6ccc1..68ad674f299 100644 --- a/src/lib/agent-onboard.test.ts +++ b/src/lib/agent/onboard.test.ts @@ -5,8 +5,8 @@ import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from "vites import fs from "node:fs"; import path from "node:path"; // Import from compiled dist/ so coverage is attributed correctly. -import { printDashboardUi, verifyAgentBinaryAvailable } from "../../dist/lib/agent-onboard"; -import type { AgentDefinition } from "./agent-defs"; +import { printDashboardUi, verifyAgentBinaryAvailable } from "../../../dist/lib/agent/onboard"; +import type { AgentDefinition } from "./defs"; function makeAgent(overrides: Partial = {}): AgentDefinition { return { @@ -128,7 +128,7 @@ describe("printDashboardUi — regression for #2078 (port 8642 is not a chat UI) describe("handleAgentSetup guards", () => { it("fails onboarding instead of completing when the agent binary or health probe is missing", () => { - const source = fs.readFileSync(path.join(import.meta.dirname, "agent-onboard.ts"), "utf-8"); + const source = fs.readFileSync(path.join(import.meta.dirname, "onboard.ts"), "utf-8"); expect(source).toContain("verifyAgentBinaryAvailable"); expect(source).toContain("AGENT_BINARY_CHECK_PREFIX"); @@ -146,7 +146,7 @@ describe("handleAgentSetup guards", () => { }); it("accepts Hermes JSON health responses without substring false positives", () => { - const source = fs.readFileSync(path.join(import.meta.dirname, "agent-onboard.ts"), "utf-8"); + const source = fs.readFileSync(path.join(import.meta.dirname, "onboard.ts"), "utf-8"); expect(source).toContain("function isHealthProbeOk"); expect(source).toContain("JSON.parse(body)"); diff --git a/src/lib/agent-onboard.ts b/src/lib/agent/onboard.ts similarity index 97% rename from src/lib/agent-onboard.ts rename to src/lib/agent/onboard.ts index 45928a3014c..257bd6bc3e6 100644 --- a/src/lib/agent-onboard.ts +++ b/src/lib/agent/onboard.ts @@ -9,14 +9,14 @@ import fs from "fs"; import os from "os"; import path from "path"; -import { dockerBuild, dockerImageInspect } from "./adapters/docker"; -import { type AgentDefinition, loadAgent, resolveAgentName } from "./agent-defs"; -import { getAgentBranding } from "./branding"; -import { getProviderSelectionConfig } from "./inference-config"; -import type { JsonObject as LooseObject, JsonValue as LooseValue } from "./json-types"; -import * as onboardSession from "./onboard-session"; -import { ROOT, redact, run, shellQuote } from "./runner"; -import { sleepSeconds } from "./wait"; +import { dockerBuild, dockerImageInspect } from "../adapters/docker"; +import { getAgentBranding } from "../branding"; +import { getProviderSelectionConfig } from "../inference-config"; +import type { JsonObject as LooseObject, JsonValue as LooseValue } from "../json-types"; +import * as onboardSession from "../onboard-session"; +import { ROOT, redact, run, shellQuote } from "../runner"; +import { sleepSeconds } from "../wait"; +import { type AgentDefinition, loadAgent, resolveAgentName } from "./defs"; export interface OnboardContext { step: (current: number, total: number, message: string) => void; diff --git a/src/lib/agent-runtime.test.ts b/src/lib/agent/runtime.test.ts similarity index 99% rename from src/lib/agent-runtime.test.ts rename to src/lib/agent/runtime.test.ts index 51635c02453..1f41fd3492a 100644 --- a/src/lib/agent-runtime.test.ts +++ b/src/lib/agent/runtime.test.ts @@ -7,8 +7,8 @@ import { buildManualRecoveryCommand, buildOpenClawRecoveryScript, buildRecoveryScript, -} from "../../dist/lib/agent-runtime"; -import type { AgentDefinition } from "./agent-defs"; +} from "../../../dist/lib/agent/runtime"; +import type { AgentDefinition } from "./defs"; function makeAgent(overrides: Partial = {}): AgentDefinition { return { diff --git a/src/lib/agent-runtime.ts b/src/lib/agent/runtime.ts similarity index 98% rename from src/lib/agent-runtime.ts rename to src/lib/agent/runtime.ts index 76ca1a92ecd..85b3038cd38 100644 --- a/src/lib/agent-runtime.ts +++ b/src/lib/agent/runtime.ts @@ -7,11 +7,11 @@ // When the session agent is openclaw (or absent), all functions return // defaults that match the hardcoded OpenClaw values on main. -import * as registry from "./state/registry"; -import { DASHBOARD_PORT } from "./ports"; -import * as onboardSession from "./onboard-session"; -import { loadAgent, type AgentDefinition } from "./agent-defs"; -import { shellQuote } from "./runner"; +import { DASHBOARD_PORT } from "../ports"; +import { shellQuote } from "../runner"; +import * as onboardSession from "../onboard-session"; +import * as registry from "../state/registry"; +import { loadAgent, type AgentDefinition } from "./defs"; /** * Resolve the agent for a sandbox. Checks the per-sandbox registry first diff --git a/src/lib/commands/debug.ts b/src/lib/commands/debug.ts index 9b1662050b3..5f22b577ccc 100644 --- a/src/lib/commands/debug.ts +++ b/src/lib/commands/debug.ts @@ -4,10 +4,10 @@ import { Command, Flags } from "@oclif/core"; import { CLI_NAME } from "../branding"; -import { runDebug } from "../debug"; -import type { DebugOptions } from "../debug"; -import type { RunDebugCommandDeps } from "../debug-command"; -import { runDebugCommandWithOptions } from "../debug-command"; +import { runDebug } from "../diagnostics/debug"; +import type { DebugOptions } from "../diagnostics/debug"; +import type { RunDebugCommandDeps } from "../diagnostics/debug-command"; +import { runDebugCommandWithOptions } from "../diagnostics/debug-command"; import type { CaptureOpenshellResult } from "../adapters/openshell/client"; import { captureOpenshellCommand } from "../adapters/openshell/client"; import { OPENSHELL_PROBE_TIMEOUT_MS } from "../adapters/openshell/timeouts"; diff --git a/src/lib/commands/simple-global-oclif-adapters.test.ts b/src/lib/commands/simple-global-oclif-adapters.test.ts index b2ed49a2cf9..95f84bcc3fc 100644 --- a/src/lib/commands/simple-global-oclif-adapters.test.ts +++ b/src/lib/commands/simple-global-oclif-adapters.test.ts @@ -24,8 +24,8 @@ const mocks = vi.hoisted(() => ({ })); vi.mock("node:child_process", () => ({ spawnSync: mocks.spawnSync })); -vi.mock("../debug", () => ({ runDebug: vi.fn() })); -vi.mock("../debug-command", () => ({ +vi.mock("../diagnostics/debug", () => ({ runDebug: vi.fn() })); +vi.mock("../diagnostics/debug-command", () => ({ runDebugCommandWithOptions: mocks.runDebugCommandWithOptions, })); vi.mock("../gateway-token-command", () => ({ diff --git a/src/lib/dashboard-contract.test.ts b/src/lib/dashboard/contract.test.ts similarity index 97% rename from src/lib/dashboard-contract.test.ts rename to src/lib/dashboard/contract.test.ts index a82f5917c17..2f63e7a1887 100644 --- a/src/lib/dashboard-contract.test.ts +++ b/src/lib/dashboard/contract.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, it, expect } from "vitest"; -import { buildChain, buildControlUiUrls } from "../../dist/lib/dashboard-contract.js"; +import { buildChain, buildControlUiUrls } from "../../../dist/lib/dashboard/contract.js"; describe("buildChain", () => { it("returns default loopback chain with no arguments", () => { diff --git a/src/lib/dashboard-contract.ts b/src/lib/dashboard/contract.ts similarity index 97% rename from src/lib/dashboard-contract.ts rename to src/lib/dashboard/contract.ts index ea387a5b14c..8de884156d2 100644 --- a/src/lib/dashboard-contract.ts +++ b/src/lib/dashboard/contract.ts @@ -6,8 +6,8 @@ * Pure functions — no I/O, no process.env reads. */ -import { DASHBOARD_PORT } from "./ports"; -import { isLoopbackHostname } from "./url-utils"; +import { DASHBOARD_PORT } from "../ports"; +import { isLoopbackHostname } from "../url-utils"; export interface PlatformHints { chatUiUrl?: string; diff --git a/src/lib/dashboard-health.test.ts b/src/lib/dashboard/health.test.ts similarity index 94% rename from src/lib/dashboard-health.test.ts rename to src/lib/dashboard/health.test.ts index 5e9041f0a1d..a20e88757be 100644 --- a/src/lib/dashboard-health.test.ts +++ b/src/lib/dashboard/health.test.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, it, expect } from "vitest"; -import { verifyDashboardChain } from "../../dist/lib/dashboard-health.js"; -import { buildChain } from "../../dist/lib/dashboard-contract.js"; +import { verifyDashboardChain } from "../../../dist/lib/dashboard/health.js"; +import { buildChain } from "../../../dist/lib/dashboard/contract.js"; const chain = buildChain(); diff --git a/src/lib/dashboard-health.ts b/src/lib/dashboard/health.ts similarity index 98% rename from src/lib/dashboard-health.ts rename to src/lib/dashboard/health.ts index 53b43c86ad8..6529b7e6958 100644 --- a/src/lib/dashboard-health.ts +++ b/src/lib/dashboard/health.ts @@ -6,7 +6,7 @@ * chain and produces a per-link diagnosis. All deps injected. */ -import type { DashboardDeliveryChain } from "./dashboard-contract"; +import type { DashboardDeliveryChain } from "./contract"; export interface DashboardHealthDeps { executeSandboxCommand: (name: string, script: string) => { status: number; stdout: string } | null; diff --git a/src/lib/dashboard-recover.test.ts b/src/lib/dashboard/recover.test.ts similarity index 94% rename from src/lib/dashboard-recover.test.ts rename to src/lib/dashboard/recover.test.ts index b7a6432cdec..89dc75ebe22 100644 --- a/src/lib/dashboard-recover.test.ts +++ b/src/lib/dashboard/recover.test.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, it, expect, vi } from "vitest"; -import { recoverDashboardChain } from "../../dist/lib/dashboard-recover.js"; -import { buildChain } from "../../dist/lib/dashboard-contract.js"; +import { recoverDashboardChain } from "../../../dist/lib/dashboard/recover.js"; +import { buildChain } from "../../../dist/lib/dashboard/contract.js"; const chain = buildChain(); diff --git a/src/lib/dashboard-recover.ts b/src/lib/dashboard/recover.ts similarity index 88% rename from src/lib/dashboard-recover.ts rename to src/lib/dashboard/recover.ts index 83c1d8976f0..352f6e32151 100644 --- a/src/lib/dashboard-recover.ts +++ b/src/lib/dashboard/recover.ts @@ -5,9 +5,9 @@ * Dashboard chain recovery — link-aware, idempotent. All deps injected. */ -import type { DashboardDeliveryChain } from "./dashboard-contract"; -import type { DashboardHealthDeps, ChainStatus } from "./dashboard-health"; -import { verifyDashboardChain } from "./dashboard-health"; +import type { DashboardDeliveryChain } from "./contract"; +import type { DashboardHealthDeps, ChainStatus } from "./health"; +import { verifyDashboardChain } from "./health"; export interface DashboardRecoverDeps extends DashboardHealthDeps { restartGateway: (name: string, port: number, agent: unknown) => boolean; diff --git a/src/lib/debug-command.test.ts b/src/lib/diagnostics/debug-command.test.ts similarity index 98% rename from src/lib/debug-command.test.ts rename to src/lib/diagnostics/debug-command.test.ts index ef798d82c37..1419519884e 100644 --- a/src/lib/debug-command.test.ts +++ b/src/lib/diagnostics/debug-command.test.ts @@ -9,7 +9,7 @@ import { printDebugHelp, runDebugCommand, runDebugCommandWithOptions, -} from "../../dist/lib/debug-command"; +} from "../../../dist/lib/diagnostics/debug-command"; function exitWithCode(code: number): never { throw new Error(`exit:${code}`); diff --git a/src/lib/debug-command.ts b/src/lib/diagnostics/debug-command.ts similarity index 100% rename from src/lib/debug-command.ts rename to src/lib/diagnostics/debug-command.ts diff --git a/src/lib/debug.test.ts b/src/lib/diagnostics/debug.test.ts similarity index 99% rename from src/lib/debug.test.ts rename to src/lib/diagnostics/debug.test.ts index 1c7e38569eb..696ab0e0f65 100644 --- a/src/lib/debug.test.ts +++ b/src/lib/diagnostics/debug.test.ts @@ -6,7 +6,7 @@ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; // Import from compiled dist/ so coverage is attributed correctly. -import { createTarball, getDebugCompletionMessages, redact } from "../../dist/lib/debug"; +import { createTarball, getDebugCompletionMessages, redact } from "../../../dist/lib/diagnostics/debug"; describe("redact", () => { it("redacts NVIDIA_API_KEY=value patterns", () => { diff --git a/src/lib/debug.ts b/src/lib/diagnostics/debug.ts similarity index 98% rename from src/lib/debug.ts rename to src/lib/diagnostics/debug.ts index aabdc7dbf8f..b44b3f0b5c5 100644 --- a/src/lib/debug.ts +++ b/src/lib/diagnostics/debug.ts @@ -3,12 +3,12 @@ import { execFileSync, spawnSync } from "node:child_process"; import { existsSync, mkdtempSync, rmSync, unlinkSync, writeFileSync } from "node:fs"; -import { dockerExecFileSync } from "./adapters/docker/exec"; import { platform, tmpdir } from "node:os"; import { basename, dirname, join } from "node:path"; -import { DASHBOARD_PORT } from "./ports"; -import { listSandboxes } from "./state/registry"; +import { dockerExecFileSync } from "../adapters/docker/exec"; +import { DASHBOARD_PORT } from "../ports"; +import { listSandboxes } from "../state/registry"; // --------------------------------------------------------------------------- // Types @@ -54,7 +54,7 @@ function section(title: string): void { // Secret redaction — delegates to unified redact module (#2381). // --------------------------------------------------------------------------- -import { redactFull as redact } from "./redact"; +import { redactFull as redact } from "../redact"; export { redact }; // --------------------------------------------------------------------------- @@ -485,8 +485,8 @@ export function getDebugCompletionMessages(output?: string): string[] { export function runDebug(opts: DebugOptions = {}): void { const quick = opts.quick ?? false; const output = opts.output ?? ""; - // Compiled location: dist/lib/debug.js → repo root is 2 levels up - const repoDir = join(__dirname, "..", ".."); + // Compiled location: dist/lib/diagnostics/debug.js → repo root is 3 levels up + const repoDir = join(__dirname, "..", "..", ".."); // Resolve sandbox name let sandboxName = diff --git a/src/lib/onboard.ts b/src/lib/onboard.ts index a4db422d85f..1100635dba1 100644 --- a/src/lib/onboard.ts +++ b/src/lib/onboard.ts @@ -290,15 +290,15 @@ const { planHostRemediation, probeContainerDns, } = preflightUtils; -const agentOnboard = require("./agent-onboard"); -const agentDefs = require("./agent-defs"); +const agentOnboard = require("./agent/onboard"); +const agentDefs = require("./agent/defs"); const gatewayState: typeof import("./state/gateway") = require("./state/gateway"); const sandboxState: typeof import("./state/sandbox") = require("./state/sandbox"); const validation: typeof import("./validation") = require("./validation"); const urlUtils: typeof import("./url-utils") = require("./url-utils"); const buildContext = require("./build-context"); -const dashboardContract: typeof import("./dashboard-contract") = require("./dashboard-contract"); +const dashboardContract: typeof import("./dashboard/contract") = require("./dashboard/contract"); const httpProbe: typeof import("./http-probe") = require("./http-probe"); const modelPrompts: typeof import("./model-prompts") = require("./model-prompts"); const providerModels: typeof import("./provider-models") = require("./provider-models"); @@ -306,7 +306,7 @@ const sandboxCreateStream: typeof import("./sandbox-create-stream") = require(". const validationRecovery: typeof import("./validation-recovery") = require("./validation-recovery"); const webSearch: typeof import("./web-search") = require("./web-search"); -import type { AgentDefinition } from "./agent-defs"; +import type { AgentDefinition } from "./agent/defs"; import type { CurlProbeResult } from "./http-probe"; import type { GatewayInference, ProviderSelectionConfig } from "./inference-config"; import type { GpuInfo, ValidationResult } from "./local-inference"; @@ -8347,7 +8347,7 @@ function syncPresetSelection( const CONTROL_UI_PORT = DASHBOARD_PORT; -// Dashboard helpers — delegated to src/lib/dashboard-contract.ts +// Dashboard helpers — delegated to src/lib/dashboard/contract.ts const { buildChain, buildControlUiUrls } = dashboardContract; // Parses `openshell forward list` output and returns the sandbox currently diff --git a/src/lib/policies.ts b/src/lib/policies.ts index 0bcdf3ad9b1..4ba7f5dee3a 100644 --- a/src/lib/policies.ts +++ b/src/lib/policies.ts @@ -12,7 +12,7 @@ const readline = require("readline"); const YAML = require("yaml"); const { ROOT, run, runCapture } = require("./runner"); const registry = require("./state/registry"); -const { loadAgent } = require("./agent-defs"); +const { loadAgent } = require("./agent/defs"); const PRESETS_DIR = path.join(ROOT, "nemoclaw-blueprint", "policies", "presets"); diff --git a/src/lib/sandbox-config.ts b/src/lib/sandbox-config.ts index 6270c44a9d9..41f0d93db5e 100644 --- a/src/lib/sandbox-config.ts +++ b/src/lib/sandbox-config.ts @@ -22,7 +22,7 @@ const { validateName } = require("./runner"); const { dockerExecFileSync } = require("./adapters/docker/exec"); const credentialFilter: typeof import("./credential-filter") = require("./credential-filter"); const { stripCredentials, isConfigObject, isConfigValue, isCredentialField } = credentialFilter; -const { appendAuditEntry } = require("./shields-audit"); +const { appendAuditEntry } = require("./shields/audit"); const { isPrivateHostname, isPrivateIp } = require("./private-networks"); type ConfigObject = import("./credential-filter").ConfigObject; @@ -98,7 +98,7 @@ function resolveAgentConfig(sandboxName: string): AgentConfigTarget { const entry = registry.getSandbox(sandboxName); if (!entry || !entry.agent) return DEFAULT_AGENT_CONFIG; - const agentDefs = require("./agent-defs"); + const agentDefs = require("./agent/defs"); const agent = agentDefs.loadAgent(entry.agent); const cfg = agent.configPaths; diff --git a/src/lib/sandbox-version.test.ts b/src/lib/sandbox-version.test.ts index fe1f7f770d4..b181c7fdcc8 100644 --- a/src/lib/sandbox-version.test.ts +++ b/src/lib/sandbox-version.test.ts @@ -31,7 +31,7 @@ vi.mock("./adapters/openshell/client.js", () => ({ captureOpenshellCommand: vi.fn(), })); -vi.mock("./agent-defs.js", () => ({ +vi.mock("./agent/defs.js", () => ({ loadAgent: vi.fn((name: string) => ({ name, displayName: name === "openclaw" ? "OpenClaw" : "Hermes Agent", diff --git a/src/lib/sandbox-version.ts b/src/lib/sandbox-version.ts index 2904f274cc0..958adc10057 100644 --- a/src/lib/sandbox-version.ts +++ b/src/lib/sandbox-version.ts @@ -15,7 +15,7 @@ import path from "path"; import { parseVersionFromText, versionGte } from "./adapters/openshell/client.js"; import * as registry from "./state/registry.js"; -import { loadAgent } from "./agent-defs.js"; +import { loadAgent } from "./agent/defs.js"; import { resolveOpenshell } from "./adapters/openshell/resolve.js"; import { captureOpenshellCommand } from "./adapters/openshell/client.js"; import { OPENSHELL_PROBE_TIMEOUT_MS } from "./adapters/openshell/timeouts.js"; diff --git a/test/shields-audit.test.ts b/src/lib/shields/audit.test.ts similarity index 100% rename from test/shields-audit.test.ts rename to src/lib/shields/audit.test.ts diff --git a/src/lib/shields-audit.ts b/src/lib/shields/audit.ts similarity index 94% rename from src/lib/shields-audit.ts rename to src/lib/shields/audit.ts index cc21cd79be0..f2f7365cb11 100644 --- a/src/lib/shields-audit.ts +++ b/src/lib/shields/audit.ts @@ -11,8 +11,8 @@ import { appendFileSync } from "node:fs"; import { join } from "node:path"; -import { ensureConfigDir } from "./state/config-io"; -import { redactFull } from "./redact"; +import { redactFull } from "../redact"; +import { ensureConfigDir } from "../state/config-io"; const AUDIT_DIR = join(process.env.HOME ?? "/tmp", ".nemoclaw", "state"); const AUDIT_FILE = join(AUDIT_DIR, "shields-audit.jsonl"); diff --git a/test/shields.test.ts b/src/lib/shields/index.test.ts similarity index 95% rename from test/shields.test.ts rename to src/lib/shields/index.test.ts index 7c07b265cc5..83c509ca317 100644 --- a/test/shields.test.ts +++ b/src/lib/shields/index.test.ts @@ -9,7 +9,7 @@ import os from "node:os"; // The shields module uses CJS require("./runner") etc., which vitest resolves // relative to src/lib/. We mock the absolute paths that vitest will resolve. -vi.mock("../../src/lib/runner", () => ({ +vi.mock("../runner", () => ({ run: vi.fn(() => ({ status: 0 })), runCapture: vi.fn(() => "version: 1\nnetwork_policies:\n test: {}"), validateName: vi.fn((name) => name), @@ -18,7 +18,7 @@ vi.mock("../../src/lib/runner", () => ({ ROOT: "/mock/root", })); -vi.mock("../../src/lib/policies", () => ({ +vi.mock("../policies", () => ({ buildPolicyGetCommand: vi.fn((name) => ["openshell", "policy", "get", "--full", name]), buildPolicySetCommand: vi.fn((file, name) => [ "openshell", @@ -33,7 +33,7 @@ vi.mock("../../src/lib/policies", () => ({ PERMISSIVE_POLICY_PATH: "/mock/permissive.yaml", })); -vi.mock("../../src/lib/sandbox-config", () => ({ +vi.mock("../sandbox-config", () => ({ resolveAgentConfig: vi.fn(() => ({ agentName: "openclaw", configPath: "/sandbox/.openclaw/openclaw.json", @@ -43,7 +43,7 @@ vi.mock("../../src/lib/sandbox-config", () => ({ })), })); -vi.mock("../../src/lib/shields-audit", () => ({ +vi.mock("./audit", () => ({ appendAuditEntry: vi.fn(), })); @@ -76,29 +76,29 @@ describe("shields — unit logic", () => { // Since the CJS require resolution issue makes direct import flaky, // test the TypeScript duration module instead. it("parses minutes", async () => { - const { parseDuration } = await import("../src/lib/domain/duration.js"); + const { parseDuration } = await import("../domain/duration.js"); expect(parseDuration("5m")).toBe(300); expect(parseDuration("30m")).toBe(1800); }); it("parses seconds", async () => { - const { parseDuration } = await import("../src/lib/domain/duration.js"); + const { parseDuration } = await import("../domain/duration.js"); expect(parseDuration("90s")).toBe(90); }); it("treats bare numbers as seconds", async () => { - const { parseDuration } = await import("../src/lib/domain/duration.js"); + const { parseDuration } = await import("../domain/duration.js"); expect(parseDuration("300")).toBe(300); }); it("rejects durations exceeding 30 minutes", async () => { - const { parseDuration } = await import("../src/lib/domain/duration.js"); + const { parseDuration } = await import("../domain/duration.js"); expect(() => parseDuration("31m")).toThrow("exceeds maximum"); expect(() => parseDuration("1h")).toThrow("exceeds maximum"); }); it("rejects invalid input", async () => { - const { parseDuration } = await import("../src/lib/domain/duration.js"); + const { parseDuration } = await import("../domain/duration.js"); expect(() => parseDuration("abc")).toThrow("Invalid duration"); }); }); @@ -266,7 +266,7 @@ describe("shields — unit logic", () => { // ------------------------------------------------------------------- describe("NC-2227-02: three-state shields model", () => { it("deriveShieldsMode encodes the fresh, locked, unlocked, and legacy-state cases", async () => { - const { deriveShieldsMode } = await import("../dist/lib/shields.js"); + const { deriveShieldsMode } = await import("../../../dist/lib/shields/index.js"); expect(deriveShieldsMode({}, false)).toBe("mutable_default"); expect(deriveShieldsMode({ shieldsDown: true }, true)).toBe("temporarily_unlocked"); @@ -281,10 +281,7 @@ describe("shields — unit logic", () => { // ------------------------------------------------------------------- describe("NC-2227-04: sandbox-state.ts tar commands do not follow symlinks", () => { function getSourceCode(): string { - return fs.readFileSync( - path.join(import.meta.dirname, "..", "src", "lib", "state", "sandbox.ts"), - "utf-8", - ); + return fs.readFileSync(path.join(import.meta.dirname, "..", "state", "sandbox.ts"), "utf-8"); } it("backup tar command does not use -h flag (no symlink following)", () => { @@ -386,10 +383,7 @@ describe("NC-2227-04: sandbox-state.ts tar commands do not follow symlinks", () // ------------------------------------------------------------------- describe("NC-2227-05: shields.ts locks state directories", () => { function getSourceCode(): string { - return fs.readFileSync( - path.join(import.meta.dirname, "..", "src", "lib", "shields.ts"), - "utf-8", - ); + return fs.readFileSync(path.join(import.meta.dirname, "index.ts"), "utf-8"); } it("HIGH_RISK_STATE_DIRS constant includes executable state and workspace entry points", () => { diff --git a/src/lib/shields.ts b/src/lib/shields/index.ts similarity index 99% rename from src/lib/shields.ts rename to src/lib/shields/index.ts index fb84e767e26..cc95b5d18e1 100644 --- a/src/lib/shields.ts +++ b/src/lib/shields/index.ts @@ -13,17 +13,17 @@ const fs = require("fs"); const path = require("path"); const { fork } = require("child_process"); -const { run, runCapture, validateName, shellQuote } = require("./runner"); -const { dockerExecFileSync } = require("./adapters/docker/exec"); +const { run, runCapture, validateName, shellQuote } = require("../runner"); +const { dockerExecFileSync } = require("../adapters/docker/exec"); const { buildPolicyGetCommand, buildPolicySetCommand, parseCurrentPolicy, PERMISSIVE_POLICY_PATH, -} = require("./policies"); -const { parseDuration, MAX_SECONDS, DEFAULT_SECONDS } = require("./domain/duration"); -const { appendAuditEntry } = require("./shields-audit"); -const { resolveAgentConfig } = require("./sandbox-config"); +} = require("../policies"); +const { parseDuration, MAX_SECONDS, DEFAULT_SECONDS } = require("../domain/duration"); +const { appendAuditEntry } = require("./audit"); +const { resolveAgentConfig } = require("../sandbox-config"); const STATE_DIR = path.join(process.env.HOME ?? "/tmp", ".nemoclaw", "state"); @@ -691,7 +691,7 @@ function shieldsDown(sandboxName: string, opts: ShieldsDownOpts = {}): void { // can take minutes (policy apply + kubectl chmod), so a relative timeout // passed at fork time would fire too early. const restoreAt = new Date(Date.now() + timeoutSeconds * 1000); - const timerScript = path.join(__dirname, "shields-timer.ts"); + const timerScript = path.join(__dirname, "timer.ts"); const timerScriptJs = timerScript.replace(/\.ts$/, ".js"); const actualScript = fs.existsSync(timerScriptJs) ? timerScriptJs : timerScript; diff --git a/src/lib/shields-timer.ts b/src/lib/shields/timer.ts similarity index 97% rename from src/lib/shields-timer.ts rename to src/lib/shields/timer.ts index 331de47401c..a0b8917114e 100644 --- a/src/lib/shields-timer.ts +++ b/src/lib/shields/timer.ts @@ -11,10 +11,10 @@ import fs from "node:fs"; import path from "node:path"; -import { buildPolicySetCommand } from "./policies"; -import { run } from "./runner"; -import { DEFAULT_AGENT_CONFIG, resolveAgentConfig } from "./sandbox-config"; -import { lockAgentConfig } from "./shields"; +import { buildPolicySetCommand } from "../policies"; +import { run } from "../runner"; +import { DEFAULT_AGENT_CONFIG, resolveAgentConfig } from "../sandbox-config"; +import { lockAgentConfig } from "./index"; type UnknownRecord = { [key: string]: unknown }; diff --git a/src/lib/state/sandbox.ts b/src/lib/state/sandbox.ts index ecaefa974c0..5e976f525f7 100644 --- a/src/lib/state/sandbox.ts +++ b/src/lib/state/sandbox.ts @@ -26,8 +26,8 @@ import os from "node:os"; import path from "node:path"; import * as registry from "./registry.js"; -import { loadAgent } from "../agent-defs.js"; -import type { AgentStateFile } from "../agent-defs.js"; +import { loadAgent } from "../agent/defs.js"; +import type { AgentStateFile } from "../agent/defs.js"; import { resolveOpenshell } from "../adapters/openshell/resolve.js"; import { captureOpenshellCommand } from "../adapters/openshell/client.js"; import { sanitizeConfigFile, isSensitiveFile } from "../credential-filter.js"; diff --git a/test/config-set-nested-ssrf.test.ts b/test/config-set-nested-ssrf.test.ts index 4bdc7f4833a..b935a754094 100644 --- a/test/config-set-nested-ssrf.test.ts +++ b/test/config-set-nested-ssrf.test.ts @@ -11,7 +11,7 @@ describe("config set nested URL SSRF enforcement", () => { it("rejects nested object/array URL values that target private hosts", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; @@ -86,7 +86,7 @@ describe("config set nested URL SSRF enforcement", () => { it("validates the key before doing URL or DNS validation", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; @@ -165,7 +165,7 @@ describe("config set nested URL SSRF enforcement", () => { it("accepts nested object/array URL values when all are public", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; @@ -240,7 +240,7 @@ describe("config set nested URL SSRF enforcement", () => { it("ignores nested non-http URL-like strings and does not crash", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; @@ -316,7 +316,7 @@ describe("config set nested URL SSRF enforcement", () => { it("recognizes mixed-case http and https schemes in nested values", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; @@ -391,7 +391,7 @@ describe("config set nested URL SSRF enforcement", () => { it("redacts credentials, query strings, and fragments in validation errors", async () => { const sandboxConfigPath = require.resolve("../dist/lib/sandbox-config"); const openshellPath = require.resolve("../dist/lib/adapters/openshell/client"); - const shieldsAuditPath = require.resolve("../dist/lib/shields-audit"); + const shieldsAuditPath = require.resolve("../dist/lib/shields/audit"); const priorSandboxConfig = require.cache[sandboxConfigPath]; const priorOpenshell = require.cache[openshellPath]; diff --git a/test/onboard.test.ts b/test/onboard.test.ts index 081e2b0dd30..36920647c4a 100644 --- a/test/onboard.test.ts +++ b/test/onboard.test.ts @@ -9,9 +9,9 @@ import path from "node:path"; import { pathToFileURL } from "node:url"; import { describe, expect, it } from "vitest"; -import type { AgentDefinition } from "../dist/lib/agent-defs.js"; -import { loadAgent } from "../dist/lib/agent-defs.js"; -import { buildChain, buildControlUiUrls } from "../dist/lib/dashboard-contract.js"; +import type { AgentDefinition } from "../dist/lib/agent/defs.js"; +import { loadAgent } from "../dist/lib/agent/defs.js"; +import { buildChain, buildControlUiUrls } from "../dist/lib/dashboard/contract.js"; import { NAME_ALLOWED_FORMAT } from "../dist/lib/name-validation.js"; import { stageOptimizedSandboxBuildContext } from "../dist/lib/sandbox-build-context.js"; @@ -1441,7 +1441,7 @@ describe("onboard helpers", () => { const scriptPath = path.join(tmpDir, "web-search-prompt-check.cjs"); const onboardPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "onboard.js")); const credentialsPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "credentials.js")); - const agentDefsPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "agent-defs.js")); + const agentDefsPath = JSON.stringify(path.join(repoRoot, "dist", "lib", "agent", "defs.js")); const script = ` let promptCalls = 0; diff --git a/test/repro-2681-group-writable.test.ts b/test/repro-2681-group-writable.test.ts index 834caa4eeb0..089219383a0 100644 --- a/test/repro-2681-group-writable.test.ts +++ b/test/repro-2681-group-writable.test.ts @@ -46,7 +46,7 @@ function withMockedDockerExecFileSync(calls: string[][], run: () => T): T { dockerExecFileSync: (args: readonly string[]) => string; }; const originalDockerExecFileSync = dockerExecModule.dockerExecFileSync; - const shieldsModulePath = require.resolve("../dist/lib/shields.js"); + const shieldsModulePath = require.resolve("../dist/lib/shields/index.js"); delete require.cache[shieldsModulePath]; dockerExecModule.dockerExecFileSync = vi.fn((args: readonly string[]) => { @@ -133,7 +133,7 @@ describe("Issue #2681 — mutable OpenClaw config permissions", () => { const commands: string[][] = []; withMockedDockerExecFileSync(commands, () => { // eslint-disable-next-line @typescript-eslint/no-require-imports - const { unlockAgentConfig } = require("../dist/lib/shields.js") as { + const { unlockAgentConfig } = require("../dist/lib/shields/index.js") as { unlockAgentConfig: ( sandboxName: string, target: { @@ -173,7 +173,7 @@ const Module = require("node:module"); const originalLoad = Module._load; const calls = []; Module._load = function patchedLoad(request, parent, isMain) { - if (request === "./adapters/docker/exec") { + if (request === "../adapters/docker/exec") { return { dockerExecFileSync(args) { const separator = args.indexOf("--"); @@ -193,7 +193,7 @@ Module._load = function patchedLoad(request, parent, isMain) { } return originalLoad.call(this, request, parent, isMain); }; -const { lockAgentConfig } = require("./dist/lib/shields.js"); +const { lockAgentConfig } = require("./dist/lib/shields/index.js"); lockAgentConfig("sandbox-pod", { agentName: "openclaw", configPath: "/sandbox/.openclaw/openclaw.json", @@ -235,7 +235,7 @@ const Module = require("node:module"); const originalLoad = Module._load; const calls = []; Module._load = function patchedLoad(request, parent, isMain) { - if (request === "./adapters/docker/exec") { + if (request === "../adapters/docker/exec") { return { dockerExecFileSync(args) { const separator = args.indexOf("--"); @@ -255,7 +255,7 @@ Module._load = function patchedLoad(request, parent, isMain) { } return originalLoad.call(this, request, parent, isMain); }; -const { lockAgentConfig } = require("./dist/lib/shields.js"); +const { lockAgentConfig } = require("./dist/lib/shields/index.js"); lockAgentConfig("sandbox-pod", { agentName: "openclaw", configPath: "/sandbox/.openclaw/openclaw.json", diff --git a/test/secret-redaction.test.ts b/test/secret-redaction.test.ts index 8ab5f8d77de..74042b7cfbf 100644 --- a/test/secret-redaction.test.ts +++ b/test/secret-redaction.test.ts @@ -7,7 +7,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { spawnSync } from "node:child_process"; import { SECRET_PATTERNS } from "../src/lib/secret-patterns"; -import { redact as debugRedact } from "../src/lib/debug"; +import { redact as debugRedact } from "../src/lib/diagnostics/debug"; import { redactSensitiveText } from "../src/lib/onboard-session"; // runner.ts uses CJS exports — import via dist import { createRequire } from "node:module"; From a463d885d7119f069a475c1aa798a3d15f409726 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Thu, 7 May 2026 11:14:53 -0700 Subject: [PATCH 5/5] fix(cli): align deployment verification dashboard imports --- src/lib/verify-deployment.test.ts | 2 +- src/lib/verify-deployment.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/verify-deployment.test.ts b/src/lib/verify-deployment.test.ts index f94bf093ab5..39882b6e4fe 100644 --- a/src/lib/verify-deployment.test.ts +++ b/src/lib/verify-deployment.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest"; import { verifyDeployment, formatVerificationDiagnostics } from "../../dist/lib/verify-deployment.js"; -import { buildChain } from "../../dist/lib/dashboard-contract.js"; +import { buildChain } from "../../dist/lib/dashboard/contract.js"; const chain = buildChain(); diff --git a/src/lib/verify-deployment.ts b/src/lib/verify-deployment.ts index 7f6de07c141..0a7326aa9f4 100644 --- a/src/lib/verify-deployment.ts +++ b/src/lib/verify-deployment.ts @@ -17,7 +17,7 @@ * "Health Offline" in the dashboard. */ -import type { DashboardDeliveryChain } from "./dashboard-contract"; +import type { DashboardDeliveryChain } from "./dashboard/contract"; // ── Types ────────────────────────────────────────────────────────────