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: 3 additions & 3 deletions agents/langchain-deepagents-code/dcode-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ run_dcode() {
# - Regression: test/langchain-deepagents-code-secret-pattern-parity.test.ts
# pins the canonical TOKEN_PREFIX_PATTERNS, CONTEXT_PATTERNS, and
# SECRET_BLOCK_PATTERNS fingerprints (source + flags), while
# test/langchain-deepagents-code-image.test.ts feeds the shared positive
# corpus through this wrapper. Any canonical change trips the parity gate and
# forces this matcher (and its samples) to update.
# test/langchain-deepagents-code-image-credentials.test.ts feeds the shared
# positive corpus through this wrapper. Any canonical change trips the parity
# gate and forces this matcher (and its samples) to update.
# The live no-network acceptance clause is covered by
# test/e2e/e2e-cloud-experimental/checks/08-deepagents-code-secret-boundary.sh
# which exercises a real sandbox launch under `nemoclaw exec` and inspects
Expand Down
2 changes: 1 addition & 1 deletion agents/langchain-deepagents-code/managed-dcode-runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
# Regression gate: test/langchain-deepagents-code-secret-pattern-parity.test.ts
# fingerprints all canonical groups and runs one shared positive corpus through
# both those groups and _contains_secret_shape; the Bash wrapper consumes the
# same corpus in test/langchain-deepagents-code-image.test.ts.
# same corpus in test/langchain-deepagents-code-image-credentials.test.ts.
# Removal condition: delete this mirror only when the managed runtime can consume
# the canonical patterns directly or upstream rejects these shapes before boot.
_SECRET_PATTERNS = tuple(
Expand Down
121 changes: 121 additions & 0 deletions test/helpers/langchain-deepagents-code-image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { type SpawnSyncReturns, spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { expect } from "vitest";

const repoRoot = path.resolve(import.meta.dirname, "../..");
const agentDir = path.join(repoRoot, "agents", "langchain-deepagents-code");

export function readAgentFile(name: string): string {
return fs.readFileSync(path.join(agentDir, name), "utf8");
}

const MANAGED_MCP_VALIDATOR_INVOCATION = [
'managed_mcp_config="$(',
" /opt/venv/bin/python3 -I -c \\",
" 'from deepagents_code._nemoclaw_managed import managed_mcp_config_path; print(managed_mcp_config_path() or \"\")'",
')"',
].join("\n");

const DEEPAGENTS_CODE_EXEC = "exec /opt/venv/bin/python3 -I -m deepagents_code";

function stubManagedMcpValidator(source: string): string {
expect(source).not.toContain(MANAGED_MCP_VALIDATOR_INVOCATION);
return source;
}

function mustReplaceOnce(
source: string,
replacements: readonly (readonly [search: string, replacement: string])[],
): string {
return replacements.reduce((current, [search, replacement]) => {
if (current.split(search).length !== 2) {
throw new Error(`fixture drift: expected exactly one ${JSON.stringify(search)}`);
}
return current.replace(search, replacement);
}, source);
}

function materializeWrapperFixture(
tempDir: string,
envFile: string,
transform: (source: string) => string,
): string {
const wrapperPath = path.join(tempDir, "dcode-wrapper.sh");
const source = mustReplaceOnce(stubManagedMcpValidator(readAgentFile("dcode-wrapper.sh")), [
[
'readonly DEEPAGENTS_ENV_FILE="/sandbox/.deepagents/.env"',
`readonly DEEPAGENTS_ENV_FILE="${envFile}"`,
],
]);
fs.writeFileSync(envFile, "", "utf8");
fs.writeFileSync(wrapperPath, transform(source), "utf8");
fs.chmodSync(wrapperPath, 0o755);
return wrapperPath;
}

export function makeWrapperFixture(
tempDir: string,
envFileOverride?: string,
): {
wrapperPath: string;
ranMarker: string;
envFile: string;
authFile: string;
codexAuthFile: string;
} {
const ranMarker = path.join(tempDir, "dcode-ran");
const envFile = envFileOverride ?? path.join(tempDir, ".env");
const authFile = path.join(tempDir, "auth.json");
const codexAuthFile = path.join(tempDir, "chatgpt-auth.json");
const wrapperPath = materializeWrapperFixture(tempDir, envFile, (source) =>
mustReplaceOnce(source, [
[
'readonly DEEPAGENTS_AUTH_FILE="/sandbox/.deepagents/.state/auth.json"',
`readonly DEEPAGENTS_AUTH_FILE="${authFile}"`,
],
[
'readonly DEEPAGENTS_CODEX_AUTH_FILE="/sandbox/.deepagents/.state/chatgpt-auth.json"',
`readonly DEEPAGENTS_CODEX_AUTH_FILE="${codexAuthFile}"`,
],
['/opt/venv/bin/python3 -I - "$auth_file"', 'python3 -I - "$auth_file"'],
[
DEEPAGENTS_CODE_EXEC,
`touch "${ranMarker}"; echo dcode-stub-ran; exit 0; : ${DEEPAGENTS_CODE_EXEC}`,
],
]),
);
return { wrapperPath, ranMarker, envFile, authFile, codexAuthFile };
}

export function makeNetworkSimulatingFixture(tempDir: string): {
wrapperPath: string;
networkLog: string;
envFile: string;
} {
const networkLog = path.join(tempDir, "network.log");
const envFile = path.join(tempDir, ".env");
const wrapperPath = materializeWrapperFixture(tempDir, envFile, (source) =>
mustReplaceOnce(source, [
[
DEEPAGENTS_CODE_EXEC,
`printf 'NET:OPEN inference.local/v1/chat\\nNET:OPEN pypi.org/simple\\nNET:OPEN api.openai.com/v1\\n' > "${networkLog}"; exit 0; : ${DEEPAGENTS_CODE_EXEC}`,
],
]),
);
return { wrapperPath, networkLog, envFile };
}

export function runWrapper(
wrapperPath: string,
args: readonly string[],
env: NodeJS.ProcessEnv,
): SpawnSyncReturns<string> {
return spawnSync("bash", [wrapperPath, ...args], {
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", ...env },
encoding: "utf8",
});
}
Loading