Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions agents/langchain-deepagents-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ COPY nemoclaw-blueprint/ /opt/nemoclaw-blueprint/
# hadolint ignore=DL4006
RUN chmod 444 /opt/nemoclaw-deepagents-code/generate-config.ts /opt/nemoclaw-deepagents-code/managed-dcode-runtime.py /opt/nemoclaw-deepagents-code/patch-managed-deepagents-code.py /opt/nemoclaw-deepagents-code/validate-nemotron-ultra-profile.py /opt/nemoclaw-deepagents-code/progressive_tool_disclosure.py /opt/nemoclaw-deepagents-code/nemoclaw_observability.py /opt/nemoclaw-deepagents-code/validate-progressive-tool-disclosure.py /opt/nemoclaw-deepagents-code/validate-observability.py \
&& chmod 755 /usr/local/bin/nemoclaw-start /usr/local/lib/nemoclaw/dcode-wrapper.sh /usr/local/lib/nemoclaw/dcode-launcher.sh \
&& install -o root -g root -m 0755 /usr/local/lib/nemoclaw/dcode-launcher.sh /usr/local/lib/nemoclaw/dcode-managed-exec \
&& test -f /usr/local/lib/nemoclaw/dcode-managed-exec \
&& test ! -L /usr/local/lib/nemoclaw/dcode-managed-exec \
&& test "$(stat -c '%u:%g:%a' /usr/local/lib/nemoclaw/dcode-managed-exec)" = "0:0:755" \
&& cmp -s /usr/local/lib/nemoclaw/dcode-launcher.sh /usr/local/lib/nemoclaw/dcode-managed-exec \
&& chmod -R a+rX /opt/nemoclaw-blueprint \
&& test "$(find /opt/nemoclaw-deepagents-profile-plugin -type f -print | LC_ALL=C sort)" = "$(printf '%s\n' '/opt/nemoclaw-deepagents-profile-plugin/pyproject.toml' '/opt/nemoclaw-deepagents-profile-plugin/src/nemoclaw_deepagents_profile/__init__.py')" \
&& printf '%s %s\n' '75ff7e7a5142cad4305126ccb1b8fc756306e82d4c559ddbc624012fb54ebfc4' '/opt/nemoclaw-deepagents-profile-plugin/src/nemoclaw_deepagents_profile/__init__.py' '7ba7b77bd6f889cc861eddbe3e38fc1f4433a85b7bc2a9b516e19a19a37a7686' '/opt/nemoclaw-deepagents-profile-plugin/pyproject.toml' | sha256sum -c - \
Expand Down Expand Up @@ -109,6 +114,7 @@ RUN install -d -m 0755 /usr/local/share/nemoclaw \
&& test "$empty_prompt_status" -eq 2 \
&& test "$(cat "$empty_prompt_log")" = "NemoClaw: empty non-interactive prompt for -n; provide prompt text." \
&& rm -f "$empty_prompt_log" \
&& /usr/local/lib/nemoclaw/dcode-managed-exec /usr/bin/true \
&& /usr/local/bin/dcode --version \
&& /usr/local/bin/dcode.real --version \
&& /usr/local/bin/deepagents-code --version
Expand Down
13 changes: 13 additions & 0 deletions agents/langchain-deepagents-code/dcode-launcher.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ done < <(compgen -A variable NEMOCLAW_DCODE_AUTO_APPROVAL || true)
unset _nemoclaw_auto_approval_env

readonly MANAGED_DCODE_WRAPPER="/usr/local/lib/nemoclaw/dcode-wrapper.sh"
readonly MANAGED_EXEC_LAUNCHER="/usr/local/lib/nemoclaw/dcode-managed-exec"
readonly MANAGED_OBSERVABILITY_MARKER="/tmp/nemoclaw-observability-enabled"
export HOME=/sandbox
export PATH="/usr/local/bin:/opt/venv/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin"
Expand Down Expand Up @@ -119,4 +120,16 @@ export http_proxy="$_PROXY_URL"
export https_proxy="$_PROXY_URL"
export no_proxy="$_NO_PROXY_VAL"

# Diagnostics need this launcher's image-baked proxy normalization and optional
# observability bit, but must not invoke the stateful sandbox entrypoint. Keep
# the mode bound to a separate root-owned regular-file install so older images
# fail before launching anything, then exact-exec without shell evaluation.
if [ "$0" = "$MANAGED_EXEC_LAUNCHER" ]; then
if [ "$#" -eq 0 ]; then
printf '%s\n' 'dcode-managed-exec requires a command.' >&2
exit 64
fi
exec "$@"
fi

exec "$MANAGED_DCODE_WRAPPER" "$@"
2 changes: 1 addition & 1 deletion src/lib/actions/sandbox/connect-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ describe("connectSandbox flow", () => {
"--name",
"alpha",
"--",
"/usr/local/bin/nemoclaw-start",
"/usr/local/lib/nemoclaw/dcode-managed-exec",
"/bin/sh",
"-c",
expect.stringContaining("/usr/bin/curl"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe("sandbox connect inference route probe argv", () => {
"--name",
"deep-code",
"--",
"/usr/local/bin/nemoclaw-start",
"/usr/local/lib/nemoclaw/dcode-managed-exec",
"/bin/sh",
"-c",
]);
Expand Down
8 changes: 6 additions & 2 deletions src/lib/actions/sandbox/connect-inference-route-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export const INFERENCE_ROUTE_PROBE_SCRIPT = [
// inherited descriptor can emit probe evidence. Removal condition: use a raw
// probe only when OpenShell provides the same trusted proxy environment to every
// sandbox exec process without shell startup.
const DCODE_MANAGED_RUNTIME_LAUNCHER = "/usr/local/bin/nemoclaw-start";
// This separate regular-file install is intentionally absent from older images:
// a newer CLI probing one fails before the stateful entrypoint or dcode wrapper
// can run, so version skew cannot mutate observability state.
const DCODE_MANAGED_RUNTIME_LAUNCHER = "/usr/local/lib/nemoclaw/dcode-managed-exec";

/**
* Classify a route result that is already known not to be healthy.
Expand All @@ -64,7 +67,8 @@ export function buildSandboxInferenceRouteProbeArgs(
agent?.name === "langchain-deepagents-code"
? [
// The trusted launcher ignores ambient proxy overrides and does not
// source sandbox-user startup files before executing this probe.
// source sandbox-user startup files or rewrite persistent runtime
// state before executing this probe.
DCODE_MANAGED_RUNTIME_LAUNCHER,
"/bin/sh",
"-c",
Expand Down
15 changes: 14 additions & 1 deletion src/lib/actions/sandbox/inference-route-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ describe("sandbox inference route health", () => {

expect(result).toMatchObject({ ok: true, httpStatus: 200 });
expect(getSessionAgentImpl).toHaveBeenCalledWith("deep-code");
expect(captureOpenshellImpl).toHaveBeenCalledOnce();
expect(captureOpenshellImpl).toHaveBeenCalledWith(
[
"sandbox",
"exec",
"--name",
"deep-code",
"--",
"/usr/local/lib/nemoclaw/dcode-managed-exec",
"/bin/sh",
"-c",
expect.stringContaining("/usr/bin/curl -q"),
],
expect.objectContaining({ ignoreError: true }),
);
});
});
146 changes: 146 additions & 0 deletions test/dcode-managed-exec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";

const LAUNCHER_PATH = path.join(
process.cwd(),
"agents",
"langchain-deepagents-code",
"dcode-launcher.sh",
);
const TEST_OWNER_UID = process.getuid?.() ?? 0;

function makeLauncherFixture(tempDir: string): {
launcherPath: string;
markerPath: string;
wrapperMarkerPath: string;
} {
const launcherPath = path.join(tempDir, "dcode-launcher.sh");
const markerPath = path.join(tempDir, "observability-enabled");
const hostPath = path.join(tempDir, "trusted-proxy-host");
const portPath = path.join(tempDir, "trusted-proxy-port");
const wrapperPath = path.join(tempDir, "dcode-wrapper.sh");
const wrapperMarkerPath = path.join(tempDir, "wrapper-ran");
const source = fs
.readFileSync(LAUNCHER_PATH, "utf8")
.replace(
'readonly MANAGED_DCODE_WRAPPER="/usr/local/lib/nemoclaw/dcode-wrapper.sh"',
`readonly MANAGED_DCODE_WRAPPER="${wrapperPath}"`,
)
.replace(
'readonly MANAGED_EXEC_LAUNCHER="/usr/local/lib/nemoclaw/dcode-managed-exec"',
`readonly MANAGED_EXEC_LAUNCHER="${launcherPath}"`,
)
.replace(
'readonly MANAGED_OBSERVABILITY_MARKER="/tmp/nemoclaw-observability-enabled"',
`readonly MANAGED_OBSERVABILITY_MARKER="${markerPath}"`,
)
.replace(
'readonly MANAGED_PROXY_HOST_FILE="/usr/local/share/nemoclaw/dcode-proxy-host"',
`readonly MANAGED_PROXY_HOST_FILE="${hostPath}"`,
)
.replace(
'readonly MANAGED_PROXY_PORT_FILE="/usr/local/share/nemoclaw/dcode-proxy-port"',
`readonly MANAGED_PROXY_PORT_FILE="${portPath}"`,
)
.replace(
"readonly MANAGED_PROXY_OWNER_UID=0",
`readonly MANAGED_PROXY_OWNER_UID=${TEST_OWNER_UID}`,
);

fs.writeFileSync(hostPath, "managed-proxy.internal\n", { mode: 0o444 });
fs.writeFileSync(portPath, "3128\n", { mode: 0o444 });
fs.writeFileSync(
wrapperPath,
`#!/bin/sh\nprintf ran > ${JSON.stringify(wrapperMarkerPath)}\nexit 99\n`,
{ mode: 0o755 },
);
fs.writeFileSync(launcherPath, source, { mode: 0o755 });
return { launcherPath, markerPath, wrapperMarkerPath };
}

describe("Deep Agents Code side-effect-free managed exec", () => {
it("preserves enabled observability during route diagnostics (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir);
fs.writeFileSync(markerPath, "1\n", { mode: 0o444 });

const result = spawnSync(
launcherPath,
[
"/bin/sh",
"-c",
'printf "OBS=%s PROXY=%s" "${NEMOCLAW_OBSERVABILITY-__unset__}" "$HTTPS_PROXY"',
],
{
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
},
);

expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toBe("OBS=1 PROXY=http://managed-proxy.internal:3128");
expect(fs.existsSync(wrapperMarkerPath)).toBe(false);
expect(fs.readFileSync(markerPath, "utf8")).toBe("1\n");
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

it("preserves disabled observability during route diagnostics (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir);

const result = spawnSync(
launcherPath,
[
"/bin/sh",
"-c",
'printf "OBS=%s PROXY=%s" "${NEMOCLAW_OBSERVABILITY-__unset__}" "$HTTPS_PROXY"',
],
{
env: {
PATH: process.env.PATH ?? "/usr/bin:/bin",
NEMOCLAW_OBSERVABILITY: "1",
},
encoding: "utf8",
},
);

expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toBe("OBS=__unset__ PROXY=http://managed-proxy.internal:3128");
expect(fs.existsSync(wrapperMarkerPath)).toBe(false);
expect(fs.existsSync(markerPath)).toBe(false);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

it("fails closed without a managed command and preserves the marker (#6504)", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-managed-exec-"));
try {
const { launcherPath, markerPath, wrapperMarkerPath } = makeLauncherFixture(tempDir);
fs.writeFileSync(markerPath, "1\n", { mode: 0o444 });

const result = spawnSync(launcherPath, [], {
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
encoding: "utf8",
});

expect(result.status).toBe(64);
expect(result.stdout).toBe("");
expect(result.stderr).toBe("dcode-managed-exec requires a command.\n");
expect(fs.readFileSync(markerPath, "utf8")).toBe("1\n");
expect(fs.existsSync(wrapperMarkerPath)).toBe(false);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ REPO="${REPO:-$(pwd)}"
CLI="${NEMOCLAW_E2E_CLI:-${REPO}/bin/nemoclaw.js}"
PROJECT_VENV="/sandbox/.nemoclaw-e2e-project-venv"
PROJECT_PYTHON="${PROJECT_VENV}/bin/python3"
OBSERVABILITY_MARKER_BEFORE="absent"

ok() { printf '%s\n' "${PREFIX}: OK ($*)"; }
info() { printf '%s\n' "${PREFIX}: $*"; }
Expand All @@ -28,6 +29,14 @@ sandbox_exec() {
openshell sandbox exec --name "$SANDBOX_NAME" -- bash -c "$1" 2>&1
}

observability_marker_value() {
# Expansion is intentionally deferred to the sandbox shell.
# shellcheck disable=SC2016
openshell sandbox exec --name "$SANDBOX_NAME" -- \
sh -c 'marker=/tmp/nemoclaw-observability-enabled; if test -f "$marker" && ! test -L "$marker"; then cat "$marker"; else printf "absent"; fi' \
2>/dev/null
}

nemoclaw_cli() {
if [ -f "$CLI" ]; then
node "$CLI" "$@"
Expand Down Expand Up @@ -113,6 +122,51 @@ python_probe() {
sandbox_exec "$remote_cmd"
}

restore_observability_state() {
local marker_after restore_output
[ "$OBSERVABILITY_MARKER_BEFORE" = "1" ] || return 0

marker_after="$(observability_marker_value || true)"
if [ "$marker_after" != "1" ]; then
if ! restore_output="$(openshell sandbox exec --name "$SANDBOX_NAME" -- \
/usr/bin/env NEMOCLAW_OBSERVABILITY=1 \
/usr/local/bin/nemoclaw-start /usr/bin/true 2>&1)"; then
fail_test "could not restore managed observability after policy-remove: $restore_output"
return 1
fi
fi

marker_after="$(observability_marker_value || true)"
if [ "$marker_after" = "1" ]; then
pass "managed observability state restores after policy-remove"
else
fail_test "managed observability marker was not restored after policy-remove"
return 1
fi
}

restore_tavily_denial() {
local cleanup_status=0 remove_output post_remove_probe_output
OBSERVABILITY_MARKER_BEFORE="$(observability_marker_value || true)"
if ! remove_output="$(nemoclaw_cli "$SANDBOX_NAME" policy-remove tavily --yes 2>&1)"; then
fail_test "policy-remove tavily failed after the opt-in proof: $remove_output"
cleanup_status=1
else
sleep "${NEMOCLAW_E2E_POLICY_SETTLE_SECONDS:-5}"
post_remove_probe_output="$(python_probe "https://api.tavily.com/search" || true)"
if [[ "$post_remove_probe_output" == *"BLOCKED:"* &&
"$post_remove_probe_output" != *"REACHED:"* ]]; then
pass "managed Deep Agents Code python returns to the default Tavily denial"
else
fail_test "policy-remove did not restore the default Tavily denial: $post_remove_probe_output"
cleanup_status=1
fi
fi

restore_observability_state || cleanup_status=1
return "$cleanup_status"
}

PASSED=0
FAILED=0

Expand All @@ -133,6 +187,28 @@ if [ "${NEMOCLAW_E2E_TAVILY_SELF_TEST:-}" = "probe-command-shape" ]; then
exit 0
fi

if [ "${NEMOCLAW_E2E_TAVILY_SELF_TEST:-}" = "restore-denial" ]; then
OBSERVABILITY_MARKER_FIXTURE="$(mktemp)"
printf '%s\n' "1" >"$OBSERVABILITY_MARKER_FIXTURE"
trap 'rm -f "$OBSERVABILITY_MARKER_FIXTURE"' EXIT
observability_marker_value() {
cat "$OBSERVABILITY_MARKER_FIXTURE"
}
nemoclaw_cli() {
[[ "$*" == "$SANDBOX_NAME policy-remove tavily --yes" ]] || return 1
[ "${NEMOCLAW_E2E_TAVILY_REMOVE_FIXTURE:-ok}" = "ok" ] || return 1
printf '%s\n' "absent" >"$OBSERVABILITY_MARKER_FIXTURE"
}
openshell() {
[[ "$*" == "sandbox exec --name $SANDBOX_NAME -- /usr/bin/env NEMOCLAW_OBSERVABILITY=1 /usr/local/bin/nemoclaw-start /usr/bin/true" ]] || return 1
printf '%s\n' "1" >"$OBSERVABILITY_MARKER_FIXTURE"
}
NEMOCLAW_E2E_POLICY_SETTLE_SECONDS=0 restore_tavily_denial
[ "$(cat "$OBSERVABILITY_MARKER_FIXTURE")" = "1" ]
[ "$FAILED" -eq 0 ]
exit 0
fi

if ! sandbox_exec "test -d /sandbox/.deepagents && command -v dcode >/dev/null 2>&1" >/dev/null; then
info "SKIP: sandbox '${SANDBOX_NAME}' is not a Deep Agents Code sandbox"
exit 0
Expand Down Expand Up @@ -164,6 +240,7 @@ APPLY_OUTPUT="$(nemoclaw_cli "$SANDBOX_NAME" policy-add tavily --yes 2>&1)" || {
printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
exit 1
}
trap restore_tavily_denial EXIT
pass "tavily policy preset applies"

sleep "${NEMOCLAW_E2E_POLICY_SETTLE_SECONDS:-5}"
Expand Down Expand Up @@ -200,5 +277,9 @@ else
fail_test "project venv under /sandbox did not expose a usable python3 executable: $PROJECT_OUT"
fi

# Do not leak this check's durable opt-in into later sequential checks.
restore_tavily_denial || true
trap - EXIT

printf '%s\n' "${PREFIX}: $PASSED passed, $FAILED failed"
[ "$FAILED" -eq 0 ] || exit 1
Loading
Loading