From d6a7c52064b2f0e4e1bcfef94b1f6b719dfa5cfe Mon Sep 17 00:00:00 2001 From: HwangJohn Date: Thu, 9 Jul 2026 02:37:26 +0900 Subject: [PATCH] fix(dcode): allow managed OTLP collector endpoint --- .../dcode-wrapper.sh | 23 +++++++++++++++++++ .../managed-dcode-runtime.py | 12 ++++++++++ ...eepagents-code-direct-module-patch.test.ts | 2 ++ ...eepagents-code-managed-entrypoints.test.ts | 19 +++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/agents/langchain-deepagents-code/dcode-wrapper.sh b/agents/langchain-deepagents-code/dcode-wrapper.sh index 70c99dbb2a..37f5f694d2 100755 --- a/agents/langchain-deepagents-code/dcode-wrapper.sh +++ b/agents/langchain-deepagents-code/dcode-wrapper.sh @@ -119,6 +119,9 @@ run_dcode() { # TELEGRAM_BOT_TOKEN, DISCORD_BOT_TOKEN) are allowed only when the value # matches the platform-specific token shape AND does not embed a # non-platform canonical secret prefix. +# * Managed observability endpoint values are allowed only for the +# credential-free local OpenShell OTLP collector and only from the +# runtime environment. Mutable .env files still fail closed. # * The env-file parser strips a leading `export ` keyword (mirroring # python-dotenv) and rejects values containing dotenv expansion ($VAR, # ${VAR}), command substitution ($(...) or backticks), because upstream @@ -263,6 +266,23 @@ is_managed_token_value_for_name() { return 1 } +is_managed_observability_endpoint_value() { + local name="$1" + local value="${2%/}" + case "$name" in + OTEL_EXPORTER_OTLP_ENDPOINT) + [ "$value" = "http://host.openshell.internal:4318" ] \ + || [ "$value" = "http://host.openshell.internal:4318/v1/traces" ] + ;; + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) + [ "$value" = "http://host.openshell.internal:4318/v1/traces" ] + ;; + *) + return 1 + ;; + esac +} + trim_whitespace() { local s="$1" s="${s#"${s%%[![:space:]]*}"}" @@ -442,6 +462,9 @@ assert_no_secret_runtime_env() { if is_managed_token_value_for_name "$name" "$value"; then continue fi + if is_managed_observability_endpoint_value "$name" "$value"; then + continue + fi if is_secret_shaped_value "$value"; then refuse_secret_env "runtime environment variable" "$name" fi diff --git a/agents/langchain-deepagents-code/managed-dcode-runtime.py b/agents/langchain-deepagents-code/managed-dcode-runtime.py index c6760db1fe..04423c2e66 100644 --- a/agents/langchain-deepagents-code/managed-dcode-runtime.py +++ b/agents/langchain-deepagents-code/managed-dcode-runtime.py @@ -57,6 +57,15 @@ r"[^\t\n\v\f\r \u00a0\u1680\u2000-\u200a\u2028\u2029" r"\u202f\u205f\u3000\ufeff'\"]" ) +_MANAGED_OBSERVABILITY_ENDPOINTS = { + "OTEL_EXPORTER_OTLP_ENDPOINT": { + "http://host.openshell.internal:4318", + "http://host.openshell.internal:4318/v1/traces", + }, + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": { + "http://host.openshell.internal:4318/v1/traces", + }, +} _OPENSHELL_ENV_PLACEHOLDER_PREFIX = "openshell:resolve:env:" _UPSTREAM_PROVIDER_ENV = "NEMOCLAW_UPSTREAM_PROVIDER" _MANAGED_ADAPTER_PROVIDER = "openai" @@ -200,6 +209,9 @@ def _is_managed_value(name: str, value: str) -> bool: return value == "nemoclaw-managed-inference" if name == "OPENSHELL_TLS_KEY": return value == "/etc/openshell/tls/client/tls.key" + if name in _MANAGED_OBSERVABILITY_ENDPOINTS: + normalized_value = value.removesuffix("/") + return normalized_value in _MANAGED_OBSERVABILITY_ENDPOINTS[name] if name == "SLACK_BOT_TOKEN": return bool(re.fullmatch(r"xoxb-[A-Za-z0-9_-]{10,}", value)) and not _contains_other_platform_secret(value, "slack") if name == "SLACK_APP_TOKEN": diff --git a/test/langchain-deepagents-code-direct-module-patch.test.ts b/test/langchain-deepagents-code-direct-module-patch.test.ts index 2a4af1afa8..8804f533e0 100644 --- a/test/langchain-deepagents-code-direct-module-patch.test.ts +++ b/test/langchain-deepagents-code-direct-module-patch.test.ts @@ -368,6 +368,8 @@ check("disabled", False) LANGCHAIN_TRACING: "true", LANGCHAIN_TRACING_V2: "true", OTEL_ENABLED: "true", + OTEL_EXPORTER_OTLP_ENDPOINT: "http://host.openshell.internal:4318", + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://host.openshell.internal:4318/v1/traces", }, encoding: "utf8", }); diff --git a/test/langchain-deepagents-code-managed-entrypoints.test.ts b/test/langchain-deepagents-code-managed-entrypoints.test.ts index 544e51c6f7..f0605ce274 100644 --- a/test/langchain-deepagents-code-managed-entrypoints.test.ts +++ b/test/langchain-deepagents-code-managed-entrypoints.test.ts @@ -152,6 +152,25 @@ describe("LangChain Deep Agents Code managed entrypoints", () => { expect(result.stdout).toContain("analytics=1"); }); + it.each([ + ["OTEL_EXPORTER_OTLP_ENDPOINT", "http://host.openshell.internal:4318"], + ["OTEL_EXPORTER_OTLP_ENDPOINT", "http://host.openshell.internal:4318/"], + ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "http://host.openshell.internal:4318/v1/traces"], + ])("allows the local managed OTLP collector endpoint in %s", (name, value) => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dcode-otel-endpoint-")); + const { wrapperPath, ranMarker } = makeWrapperFixture(tempDir); + const result = spawnSync("bash", [wrapperPath, "-n", "hi"], { + env: { + PATH: process.env.PATH ?? "/usr/bin:/bin", + [name]: value, + }, + encoding: "utf8", + }); + + expect(result.status, result.stderr).toBe(0); + expect(fs.existsSync(ranMarker)).toBe(true); + }); + it.each([ "LANGSMITH_RUNS_ENDPOINTS", "LANGCHAIN_RUNS_ENDPOINTS",