Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ COPY agents/hermes/patch-cron-restore-drain.py /opt/nemoclaw-hermes-config/patch
COPY agents/hermes/patch-neutral-platform-env-activation.py /opt/nemoclaw-hermes-config/patch-neutral-platform-env-activation.py
COPY agents/hermes/host/managed-tool-gateway-matrix.json /opt/nemoclaw-hermes-config/managed-tool-gateway-matrix.json
COPY src/lib/hermes-managed-route.ts /src/lib/hermes-managed-route.ts
COPY src/lib/hermes-switchyard-routing.ts /src/lib/hermes-switchyard-routing.ts
COPY src/lib/tool-disclosure.ts /src/lib/tool-disclosure.ts
COPY src/lib/messaging/ /src/lib/messaging/
COPY scripts/lib/openclaw-npm-remediation.mts /scripts/lib/openclaw-npm-remediation.mts
Expand Down Expand Up @@ -369,7 +370,7 @@ RUN chmod -R a+rX /opt/nemoclaw-hermes-plugin/
# read-only.
RUN find /opt/nemoclaw-hermes-config -type d -exec chmod 755 {} + \
&& find /opt/nemoclaw-hermes-config -type f -exec chmod 444 {} + \
&& chmod 444 /src/lib/hermes-managed-route.ts /src/lib/tool-disclosure.ts \
&& chmod 444 /src/lib/hermes-managed-route.ts /src/lib/hermes-switchyard-routing.ts /src/lib/tool-disclosure.ts \
&& chmod 444 /scripts/lib/reviewed-npm-archive.mts /scripts/lib/openclaw-npm-remediation.mts \
/scripts/patch-bundled-npm-brace-expansion.mts /scripts/lib/patch-bundled-npm-ip-address.mts \
/scripts/patch-bundled-npm-tar.mts \
Expand Down Expand Up @@ -662,7 +663,7 @@ RUN node --experimental-strip-types \
ARG NEMOCLAW_HERMES_WRAPPER_SHA256=f4276e9833638b7a620176c88bd329d6b6d4948538a3227b727a1397146a0e0e
ARG NEMOCLAW_HERMES_CLI_ADAPTER_SHA256=989edf54a8c09c6efb348600a8aa2f264c0b71408eb9d7bcd579b92cbeccf9b1
ARG NEMOCLAW_HERMES_CLI_ADAPTER_VALIDATOR_SHA256=db4046e79e513eab67b069a8eda20167b8b65529cf26842531d2ad673c670330
ARG NEMOCLAW_HERMES_VALIDATOR_SHA256=a0c87387c0a00e7aad5892375303115577d72293ea6af7d15d1861e6a02c62c6
ARG NEMOCLAW_HERMES_VALIDATOR_SHA256=9e4ad135076b952e56d385ea91b2e22aeaed79204615367fbb68fab89c290baf
ARG NEMOCLAW_HERMES_TIRITH_FINALIZER_SHA256=a1e6b1c53ab297569abb87c29d15c294d729e46005bfd022136b4c447a791819
ARG NEMOCLAW_HERMES_CRON_RESTORE_CONTROLLER_SHA256=e8593cf1580bffa4663e91c079ba0ce31c3d26391f5b1718872701138ce250b0
# hadolint ignore=DL4006
Expand Down
30 changes: 30 additions & 0 deletions agents/hermes/config/build-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

import { Buffer } from "node:buffer";
import { TextDecoder } from "node:util";

import {
type HermesSwitchyardRouting,
validateHermesSwitchyardRouting,
} from "../../../src/lib/hermes-switchyard-routing.ts";
import { normalizeProviderPlaceholderForEnvKey } from "../../../src/lib/messaging/provider-placeholders.ts";
import { readToolDisclosureEnv } from "../../../src/lib/tool-disclosure.ts";
import { isObjectRecord } from "./object-record.ts";
Expand Down Expand Up @@ -31,6 +36,7 @@ export type HermesBuildSettings = {
brokerEnabled: boolean;
presets: string[];
};
switchyardRouting?: HermesSwitchyardRouting | null;
};

/** Read and validate the environment consumed by the Hermes config generator. */
Expand Down Expand Up @@ -60,9 +66,33 @@ export function readHermesBuildSettings(env: NodeJS.ProcessEnv): HermesBuildSett
brokerEnabled: env.NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER === "1",
presets: readBase64Json<string[]>(env, "NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64", "W10="),
},
switchyardRouting: readSwitchyardRouting(env),
};
}

function readSwitchyardRouting(env: NodeJS.ProcessEnv): HermesSwitchyardRouting | null {
const encoded = env.NEMOCLAW_HERMES_SWITCHYARD_ROUTING_B64;
if (encoded === undefined || encoded === "") return null;
try {
if (
encoded.length > 128 * 1024 ||
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(encoded)
) {
throw new Error("transport is not canonical base64");
}
const bytes = Buffer.from(encoded, "base64");
if (bytes.toString("base64") !== encoded) {
throw new Error("transport is not canonical base64");
}
return validateHermesSwitchyardRouting(
JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)) as unknown,
);
} catch (error) {
const message = error instanceof Error ? error.message : "unknown validation failure";
throw new Error(`NEMOCLAW_HERMES_SWITCHYARD_ROUTING_B64 is invalid: ${message}`);
}
}

function readBooleanBuildFlag(env: NodeJS.ProcessEnv, name: string): boolean {
const value = env[name] ?? "0";
if (value !== "0" && value !== "1") {
Expand Down
11 changes: 10 additions & 1 deletion agents/hermes/config/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,20 @@ export function generateHermesConfig({
const config = policy.config;
const envLines = policy.env_lines;
finalizeHermesPlatformToolsets(config, settings);
const written = writeHermesConfigFiles(config, envLines, policy, homeDir);
const written = writeHermesConfigFiles(
config,
envLines,
policy,
settings.switchyardRouting ?? null,
homeDir,
);

log(`[config] Wrote ${written.configPath} (model=${settings.model}, provider=custom)`);
log(`[config] Wrote ${written.envPath} (${written.envEntryCount} entries)`);
log(`[config] Wrote ${written.policyPath} (schema=${policy.schema_version})`);
if (written.relayPluginsPath !== null) {
log(`[config] Wrote ${written.relayPluginsPath} (native Relay/Switchyard configuration)`);
}

return { settings, config, envLines, policy, written };
}
5 changes: 5 additions & 0 deletions agents/hermes/config/hermes-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { HermesBuildSettings } from "./build-env.ts";
import { HERMES_SWITCHYARD_RELAY_TOML } from "../../../src/lib/hermes-switchyard-routing.ts";
import {
effectiveManagedToolGatewayPresets,
loadManagedToolGatewayMatrix,
Expand All @@ -15,6 +16,10 @@ export function buildHermesEnvLines(
): string[] {
const envLines = ["API_SERVER_PORT=18642", "API_SERVER_HOST=127.0.0.1"];

if (settings.switchyardRouting != null) {
envLines.push(`HERMES_NEMO_RELAY_PLUGINS_TOML=${HERMES_SWITCHYARD_RELAY_TOML}`);
}

for (const { envKey, placeholder } of settings.messagingCredentialPlaceholders) {
envLines.push(`${envKey}=${placeholder}`);
}
Expand Down
19 changes: 18 additions & 1 deletion agents/hermes/config/write-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { chmodSync, writeFileSync } from "node:fs";
import { chmodSync, rmSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { HermesManagedPolicyV1 } from "./managed-policy.ts";
import {
serializeHermesSwitchyardRelayToml,
type HermesSwitchyardRouting,
} from "../../../src/lib/hermes-switchyard-routing.ts";
import { buildHermesUpstreamHeader } from "./upstream-header.ts";
import { toYaml } from "./yaml.ts";

Expand All @@ -13,12 +17,14 @@ export type WrittenHermesConfig = {
envPath: string;
envEntryCount: number;
policyPath: string;
relayPluginsPath: string | null;
};

export function writeHermesConfigFiles(
config: Record<string, unknown>,
envLines: string[],
policy: HermesManagedPolicyV1,
switchyardRouting: HermesSwitchyardRouting | null,
homeDir: string = homedir(),
): WrittenHermesConfig {
const configPath = join(homeDir, ".hermes", "config.yaml");
Expand All @@ -33,10 +39,21 @@ export function writeHermesConfigFiles(
writeFileSync(policyPath, `${JSON.stringify(policy, null, 2)}\n`);
chmodSync(policyPath, 0o600);

const generatedRelayPluginsPath = join(homeDir, ".hermes", "relay-plugins.toml");
let relayPluginsPath: string | null = null;
if (switchyardRouting === null) {
rmSync(generatedRelayPluginsPath, { force: true });
} else {
writeFileSync(generatedRelayPluginsPath, serializeHermesSwitchyardRelayToml(switchyardRouting));
chmodSync(generatedRelayPluginsPath, 0o600);
relayPluginsPath = generatedRelayPluginsPath;
}

return {
configPath,
envPath,
envEntryCount: envLines.length,
policyPath,
relayPluginsPath,
};
}
Loading
Loading