Skip to content

Commit fa32e4a

Browse files
committed
merge(main): incorporate current fixes
2 parents 9f4117a + 7b1c064 commit fa32e4a

12 files changed

Lines changed: 339 additions & 67 deletions

agents/langchain-deepagents-code/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,11 @@ RUN install -d -m 0755 /usr/local/share/nemoclaw \
289289
&& printf '%s\n' "$NEMOCLAW_PROXY_HOST" > /usr/local/share/nemoclaw/dcode-proxy-host \
290290
&& printf '%s\n' "$NEMOCLAW_PROXY_PORT" > /usr/local/share/nemoclaw/dcode-proxy-port \
291291
&& printf '%s\n' "$NEMOCLAW_INFERENCE_BASE_URL" > /usr/local/share/nemoclaw/dcode-inference-base-url \
292+
&& printf '%s\n' "$NEMOCLAW_UPSTREAM_PROVIDER" > /usr/local/share/nemoclaw/dcode-upstream-provider \
292293
&& printf '%s\n' "$NEMOCLAW_DCODE_AUTO_APPROVAL" > /usr/local/share/nemoclaw/dcode-auto-approval \
293294
&& printf '%s\n' "$NEMOCLAW_REASONING_EFFORT" > /usr/local/share/nemoclaw/dcode-reasoning-effort \
294-
&& chown root:root /usr/local/share/nemoclaw/dcode-proxy-host /usr/local/share/nemoclaw/dcode-proxy-port /usr/local/share/nemoclaw/dcode-inference-base-url /usr/local/share/nemoclaw/dcode-auto-approval /usr/local/share/nemoclaw/dcode-reasoning-effort \
295-
&& chmod 0444 /usr/local/share/nemoclaw/dcode-proxy-host /usr/local/share/nemoclaw/dcode-proxy-port /usr/local/share/nemoclaw/dcode-inference-base-url /usr/local/share/nemoclaw/dcode-auto-approval /usr/local/share/nemoclaw/dcode-reasoning-effort \
295+
&& chown root:root /usr/local/share/nemoclaw/dcode-proxy-host /usr/local/share/nemoclaw/dcode-proxy-port /usr/local/share/nemoclaw/dcode-inference-base-url /usr/local/share/nemoclaw/dcode-upstream-provider /usr/local/share/nemoclaw/dcode-auto-approval /usr/local/share/nemoclaw/dcode-reasoning-effort \
296+
&& chmod 0444 /usr/local/share/nemoclaw/dcode-proxy-host /usr/local/share/nemoclaw/dcode-proxy-port /usr/local/share/nemoclaw/dcode-inference-base-url /usr/local/share/nemoclaw/dcode-upstream-provider /usr/local/share/nemoclaw/dcode-auto-approval /usr/local/share/nemoclaw/dcode-reasoning-effort \
296297
&& unset OTEL_EXPORTER_OTLP_TRACES_ENDPOINT \
297298
&& empty_prompt_log="$(mktemp)" \
298299
&& if timeout 10 env -i /usr/local/lib/nemoclaw/dcode-wrapper.sh -n "" >"$empty_prompt_log" 2>&1; then empty_prompt_status=0; else empty_prompt_status=$?; fi \

agents/langchain-deepagents-code/managed-dcode-runtime.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
_INFERENCE_BASE_URL_FILE = Path(
2727
"/usr/local/share/nemoclaw/dcode-inference-base-url"
2828
)
29+
_UPSTREAM_PROVIDER_FILE = Path(
30+
"/usr/local/share/nemoclaw/dcode-upstream-provider"
31+
)
2932
_MANAGED_PROXY_HOST_FILE = Path(
3033
"/usr/local/share/nemoclaw/dcode-proxy-host"
3134
)
@@ -1115,21 +1118,21 @@ def managed_fetch_proxy_url() -> str | None:
11151118
return value
11161119

11171120

1118-
def _read_managed_proxy_value(path: Path, label: str) -> str:
1119-
"""Read one immutable proxy component from the managed image."""
1121+
def _read_managed_file_value(path: Path, label: str) -> str:
1122+
"""Read one root-owned, read-only value from the managed image."""
11201123
if not path.is_file() or path.is_symlink():
1121-
raise RuntimeError(f"managed proxy {label} file is missing or unsafe")
1124+
raise RuntimeError(f"managed {label} file is missing or unsafe")
11221125
try:
11231126
metadata = path.stat()
11241127
raw = path.read_text(encoding="utf-8")
11251128
except OSError as exc:
1126-
raise RuntimeError(f"managed proxy {label} file is unreadable") from exc
1129+
raise RuntimeError(f"managed {label} file is unreadable") from exc
11271130
if (
11281131
metadata.st_uid != _MANAGED_FILE_OWNER_UID
11291132
or stat.S_IMODE(metadata.st_mode) != 0o444
11301133
):
11311134
raise RuntimeError(
1132-
f"managed proxy {label} file has unsafe ownership or mode"
1135+
f"managed {label} file has unsafe ownership or mode"
11331136
)
11341137
value = raw.rstrip("\n")
11351138
if (
@@ -1139,14 +1142,14 @@ def _read_managed_proxy_value(path: Path, label: str) -> str:
11391142
or value != value.strip()
11401143
or any(ord(character) < 32 for character in value)
11411144
):
1142-
raise RuntimeError(f"managed proxy {label} file has invalid contents")
1145+
raise RuntimeError(f"managed {label} file has invalid contents")
11431146
return value
11441147

11451148

11461149
def _managed_fetch_proxy_url_from_files() -> str:
11471150
"""Derive the trusted proxy URL independently from root-owned files."""
1148-
host = _read_managed_proxy_value(_MANAGED_PROXY_HOST_FILE, "host")
1149-
port = _read_managed_proxy_value(_MANAGED_PROXY_PORT_FILE, "port")
1151+
host = _read_managed_file_value(_MANAGED_PROXY_HOST_FILE, "proxy host")
1152+
port = _read_managed_file_value(_MANAGED_PROXY_PORT_FILE, "proxy port")
11501153
if _MANAGED_PROXY_HOST.fullmatch(host) is None:
11511154
raise RuntimeError("managed proxy host file has invalid contents")
11521155
if (
@@ -1157,6 +1160,16 @@ def _managed_fetch_proxy_url_from_files() -> str:
11571160
return f"http://{host}:{port}"
11581161

11591162

1163+
def _managed_upstream_provider() -> str:
1164+
"""Return the root-owned upstream provider."""
1165+
value = _read_managed_file_value(
1166+
_UPSTREAM_PROVIDER_FILE, "upstream provider"
1167+
)
1168+
if _DISPLAY_PROVIDER_NAME.fullmatch(value) is None:
1169+
raise RuntimeError("managed upstream provider file has invalid contents")
1170+
return value
1171+
1172+
11601173
def _managed_fetch_ca_bundle() -> tuple[int, str]:
11611174
"""Open and validate fixed OpenShell TLS trust without a pathname race."""
11621175
path = _MANAGED_FETCH_CA_BUNDLE_FILE
@@ -1472,6 +1485,7 @@ def assert_safe_runtime() -> None:
14721485
"""Reject unmanaged runtime credentials before dcode bootstraps settings."""
14731486
_assert_safe_environment()
14741487
_assert_safe_auth_state()
1488+
os.environ[_UPSTREAM_PROVIDER_ENV] = _managed_upstream_provider()
14751489
managed_fetch_proxy_url()
14761490
base_url = managed_inference_base_url()
14771491
os.environ["OPENAI_BASE_URL"] = base_url

src/lib/onboard/dockerfile-patch.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
type DcodeAutoApprovalMode,
3838
isDcodeAutoApprovalMode,
3939
} from "./dcode-auto-approval";
40+
import { isValidDcodeUpstreamProvider } from "./managed-startup/dcode-upstream-provider";
4041
import * as remoteDashboardBindContract from "./dockerfile-remote-dashboard-bind-contract";
4142
import {
4243
type DockerfileInstruction,
@@ -322,6 +323,14 @@ export function patchStagedDockerfile(
322323
// etc.) rather than the proxy-routing key. The replace is a silent no-op
323324
// when the staged Dockerfile predates this ARG (e.g. OpenClaw).
324325
const upstreamProvider = provider && provider.trim() ? provider : providerKey;
326+
if (
327+
options.agentName === "langchain-deepagents-code" &&
328+
!isValidDcodeUpstreamProvider(upstreamProvider)
329+
) {
330+
throw new Error(
331+
"NEMOCLAW_UPSTREAM_PROVIDER must start with an ASCII letter or digit and contain 1-64 ASCII letters, digits, dots, underscores, or hyphens for DCode.",
332+
);
333+
}
325334
dockerfile = dockerfile.replace(
326335
/^ARG NEMOCLAW_UPSTREAM_PROVIDER=.*$/m,
327336
`ARG NEMOCLAW_UPSTREAM_PROVIDER=${sanitizeDockerArg(upstreamProvider)}`,

src/lib/onboard/managed-startup-agent-environment.test.ts

Lines changed: 82 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -400,25 +400,26 @@ describe("managed startup agent environment", () => {
400400
).toThrow(message);
401401
});
402402

403-
it.each(
404-
MANAGED_STARTUP_AGENTS,
405-
)("derives every unsupported $0 runtime unset from the closed contract", (agent) => {
406-
const result = mapManagedStartupProfileToAgentEnvironment(PROFILES[agent](), {
407-
NEMOCLAW_AUTO_PAIR_DEADLINE_SECS: "30",
408-
NEMOCLAW_AUTO_PAIR_FAST_DEADLINE_SECS: "3",
409-
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS: "0.25",
410-
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS: "3",
411-
NEMOCLAW_AUTO_PAIR_RUN_TIMEOUT_SECS: "10",
412-
NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS: "600",
413-
});
414-
const unsets = new Set(result.applicationRuntime.unsetEnvironment);
415-
for (const obligation of MANAGED_STARTUP_RUNTIME_CLEANUP_OBLIGATIONS) {
416-
expect(unsets.has(obligation.input)).toBe(!obligation.supportedFor.includes(agent));
417-
}
418-
for (const name of OPENCLAW_APPLICATION_RUNTIME_NAMES) {
419-
expect(unsets.has(name)).toBe(agent !== "openclaw");
420-
}
421-
});
403+
it.each(MANAGED_STARTUP_AGENTS)(
404+
"derives every unsupported $0 runtime unset from the closed contract",
405+
(agent) => {
406+
const result = mapManagedStartupProfileToAgentEnvironment(PROFILES[agent](), {
407+
NEMOCLAW_AUTO_PAIR_DEADLINE_SECS: "30",
408+
NEMOCLAW_AUTO_PAIR_FAST_DEADLINE_SECS: "3",
409+
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS: "0.25",
410+
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS: "3",
411+
NEMOCLAW_AUTO_PAIR_RUN_TIMEOUT_SECS: "10",
412+
NEMOCLAW_AUTO_PAIR_SLOW_INTERVAL_SECS: "600",
413+
});
414+
const unsets = new Set(result.applicationRuntime.unsetEnvironment);
415+
for (const obligation of MANAGED_STARTUP_RUNTIME_CLEANUP_OBLIGATIONS) {
416+
expect(unsets.has(obligation.input)).toBe(!obligation.supportedFor.includes(agent));
417+
}
418+
for (const name of OPENCLAW_APPLICATION_RUNTIME_NAMES) {
419+
expect(unsets.has(name)).toBe(agent !== "openclaw");
420+
}
421+
},
422+
);
422423

423424
it("keeps the profile mapper independent from mutable process-global runtime input", () => {
424425
const name = "NEMOCLAW_AUTO_PAIR_FAST_REENTRY_POLLS";
@@ -514,7 +515,7 @@ describe("managed startup agent environment", () => {
514515
});
515516
});
516517

517-
it("keeps DCode routing and auto-approval in root-owned files instead of ambient runtime env", () => {
518+
it("keeps DCode routing, provider identity, and auto-approval in root-owned files", () => {
518519
const result = mapManagedStartupProfileToAgentEnvironment(dcodeProfile(), {
519520
NEMOCLAW_AUTO_PAIR_FAST_REENTRY_INTERVAL_SECS: "not-a-number",
520521
});
@@ -538,6 +539,7 @@ describe("managed startup agent environment", () => {
538539
const expectedDcodeRuntime = { ...result.configurationEnvironment };
539540
delete expectedDcodeRuntime.NEMOCLAW_INFERENCE_BASE_URL;
540541
delete expectedDcodeRuntime.NEMOCLAW_REASONING_EFFORT;
542+
delete expectedDcodeRuntime.NEMOCLAW_UPSTREAM_PROVIDER;
541543
for (const name of [
542544
"HTTP_PROXY",
543545
"HTTPS_PROXY",
@@ -566,6 +568,7 @@ describe("managed startup agent environment", () => {
566568
expect(result.runtimeEnvironment).not.toHaveProperty("HTTPS_PROXY");
567569
expect(result.runtimeEnvironment).not.toHaveProperty("NEMOCLAW_INFERENCE_BASE_URL");
568570
expect(result.runtimeEnvironment).not.toHaveProperty("NEMOCLAW_REASONING_EFFORT");
571+
expect(result.runtimeEnvironment).not.toHaveProperty("NEMOCLAW_UPSTREAM_PROVIDER");
569572

570573
expect(result.materials).toEqual([
571574
{
@@ -591,6 +594,15 @@ describe("managed startup agent environment", () => {
591594
group: "root",
592595
mode: 0o444,
593596
},
597+
{
598+
kind: "root-owned-file",
599+
legacyInput: "NEMOCLAW_UPSTREAM_PROVIDER",
600+
path: "/usr/local/share/nemoclaw/dcode-upstream-provider",
601+
contents: "openrouter\n",
602+
owner: "root",
603+
group: "root",
604+
mode: 0o444,
605+
},
594606
{
595607
kind: "root-owned-file",
596608
legacyInput: "NEMOCLAW_PROXY_HOST",
@@ -678,29 +690,43 @@ describe("managed startup agent environment", () => {
678690
});
679691
});
680692

681-
it.each(
682-
MANAGED_STARTUP_AGENTS,
683-
)("represents the complete $0 Docker/start affordance inventory", (agent) => {
684-
const result = mapManagedStartupProfileToAgentEnvironment(PROFILES[agent]());
685-
expect(representedLegacyInputs(result)).toEqual(
686-
MANAGED_STARTUP_PROFILE_AFFORDANCE_INVENTORY[agent]
687-
.map((affordance) => affordance.input)
688-
.sort(),
689-
);
690-
const messagingActions = result.actions.filter(
691-
(action) => action.kind === "apply-messaging-plan",
692-
);
693-
expect(messagingActions.map(({ phase, runAs }) => [phase, runAs])).toEqual(
694-
agent === "langchain-deepagents-code"
695-
? []
696-
: [
697-
["runtime-setup", "root"],
698-
["post-agent-install", "sandbox"],
699-
],
700-
);
701-
expect(messagingActions.map((action) => String(action.phase))).not.toContain("agent-install");
693+
it("materializes the longest DCode upstream provider accepted by its runtime (#7112)", () => {
694+
const profile = dcodeProfile();
695+
const upstreamProvider = "a".repeat(64);
696+
const result = mapManagedStartupProfileToAgentEnvironment({
697+
...profile,
698+
inference: { ...profile.inference, upstreamProvider },
699+
});
700+
701+
expect(
702+
result.materials.find((material) => material.legacyInput === "NEMOCLAW_UPSTREAM_PROVIDER"),
703+
).toMatchObject({ contents: `${upstreamProvider}\n` });
702704
});
703705

706+
it.each(MANAGED_STARTUP_AGENTS)(
707+
"represents the complete $0 Docker/start affordance inventory",
708+
(agent) => {
709+
const result = mapManagedStartupProfileToAgentEnvironment(PROFILES[agent]());
710+
expect(representedLegacyInputs(result)).toEqual(
711+
MANAGED_STARTUP_PROFILE_AFFORDANCE_INVENTORY[agent]
712+
.map((affordance) => affordance.input)
713+
.sort(),
714+
);
715+
const messagingActions = result.actions.filter(
716+
(action) => action.kind === "apply-messaging-plan",
717+
);
718+
expect(messagingActions.map(({ phase, runAs }) => [phase, runAs])).toEqual(
719+
agent === "langchain-deepagents-code"
720+
? []
721+
: [
722+
["runtime-setup", "root"],
723+
["post-agent-install", "sandbox"],
724+
],
725+
);
726+
expect(messagingActions.map((action) => String(action.phase))).not.toContain("agent-install");
727+
},
728+
);
729+
704730
it("uses explicit clear states without erasing launch-only ambient proxy credentials", () => {
705731
const openclawBase = openClawProfile();
706732
assert(openclawBase.agentConfig.agent === "openclaw", "fixture mismatch");
@@ -871,6 +897,21 @@ describe("managed startup agent environment", () => {
871897
);
872898
});
873899

900+
it.each(["provider-π", `p${"x".repeat(64)}`, "-ollama-local"])(
901+
"rejects unsupported DCode provider identifier %s before materialization (#7112)",
902+
(upstreamProvider) => {
903+
const base = dcodeProfile();
904+
const profile: ManagedStartupProfile = {
905+
...base,
906+
inference: { ...base.inference, upstreamProvider },
907+
};
908+
909+
expect(() => mapManagedStartupProfileToAgentEnvironment(profile)).toThrow(
910+
/must start with an ASCII letter or digit and contain 1-64 ASCII letters, digits, dots, underscores, or hyphens for DCode/u,
911+
);
912+
},
913+
);
914+
874915
it("revalidates typed input while keeping DCode host proxy intent outside its pinned runtime", () => {
875916
const dcodeBase = dcodeProfile();
876917
const profile: ManagedStartupProfile = {

src/lib/onboard/managed-startup/agent-environment.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ export interface ManagedStartupRootOwnedFileMaterial {
3434
| "NEMOCLAW_INFERENCE_BASE_URL"
3535
| "NEMOCLAW_PROXY_HOST"
3636
| "NEMOCLAW_PROXY_PORT"
37-
| "NEMOCLAW_REASONING_EFFORT";
37+
| "NEMOCLAW_REASONING_EFFORT"
38+
| "NEMOCLAW_UPSTREAM_PROVIDER";
3839
readonly path:
3940
| "/usr/local/share/nemoclaw/dcode-auto-approval"
4041
| "/usr/local/share/nemoclaw/dcode-inference-base-url"
4142
| "/usr/local/share/nemoclaw/dcode-proxy-host"
4243
| "/usr/local/share/nemoclaw/dcode-proxy-port"
43-
| "/usr/local/share/nemoclaw/dcode-reasoning-effort";
44+
| "/usr/local/share/nemoclaw/dcode-reasoning-effort"
45+
| "/usr/local/share/nemoclaw/dcode-upstream-provider";
4446
readonly contents: string;
4547
readonly owner: "root";
4648
readonly group: "root";
@@ -76,15 +78,13 @@ interface ManagedStartupApplyMessagingActionBase {
7678
readonly phase: "runtime-setup" | "post-agent-install";
7779
}
7880

79-
export interface ManagedStartupApplyMessagingRuntimeAction
80-
extends ManagedStartupApplyMessagingActionBase {
81+
export interface ManagedStartupApplyMessagingRuntimeAction extends ManagedStartupApplyMessagingActionBase {
8182
readonly phase: "runtime-setup";
8283
/** Writes the reduced, root-owned messaging runtime-plan artifact. */
8384
readonly runAs: "root";
8485
}
8586

86-
export interface ManagedStartupApplyMessagingConfigAction
87-
extends ManagedStartupApplyMessagingActionBase {
87+
export interface ManagedStartupApplyMessagingConfigAction extends ManagedStartupApplyMessagingActionBase {
8888
readonly phase: "post-agent-install";
8989
/** Renders only sandbox-owned agent configuration from preinstalled assets. */
9090
readonly runAs: "sandbox";
@@ -495,6 +495,7 @@ function mapDcodeProfile(
495495
// consumed by managed-dcode-runtime.py.
496496
delete runtimeEnvironment.NEMOCLAW_INFERENCE_BASE_URL;
497497
delete runtimeEnvironment.NEMOCLAW_REASONING_EFFORT;
498+
delete runtimeEnvironment.NEMOCLAW_UPSTREAM_PROVIDER;
498499
for (const name of [
499500
"HTTP_PROXY",
500501
"HTTPS_PROXY",
@@ -517,6 +518,11 @@ function mapDcodeProfile(
517518
"/usr/local/share/nemoclaw/dcode-inference-base-url",
518519
profile.inference.routedBaseUrl,
519520
),
521+
rootOwnedFile(
522+
"NEMOCLAW_UPSTREAM_PROVIDER",
523+
"/usr/local/share/nemoclaw/dcode-upstream-provider",
524+
profile.inference.upstreamProvider,
525+
),
520526
rootOwnedFile(
521527
"NEMOCLAW_PROXY_HOST",
522528
"/usr/local/share/nemoclaw/dcode-proxy-host",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
const DCODE_UPSTREAM_PROVIDER_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
5+
6+
export function isValidDcodeUpstreamProvider(value: string): boolean {
7+
return DCODE_UPSTREAM_PROVIDER_RE.test(value);
8+
}

0 commit comments

Comments
 (0)