Skip to content
Draft
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
78 changes: 30 additions & 48 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,14 @@ COPY scripts/nemoclaw-start.sh /usr/local/bin/nemoclaw-start
# needs to read these files to install runtime preloads under /tmp.
COPY nemoclaw-blueprint/scripts/*.js /usr/local/lib/nemoclaw/preloads/
COPY scripts/codex-acp-wrapper.sh /usr/local/bin/nemoclaw-codex-acp
COPY scripts/generate-openclaw-config.mts /usr/local/lib/nemoclaw/generate-openclaw-config.mts
COPY scripts/openclaw-build-messaging-plugins.py /usr/local/lib/nemoclaw/openclaw-build-messaging-plugins.py
COPY scripts/seed-wechat-accounts.py /usr/local/lib/nemoclaw/seed-wechat-accounts.py
COPY scripts/generate-openclaw-config.mts /scripts/generate-openclaw-config.mts
COPY src/lib/messaging/ /src/lib/messaging/
COPY nemoclaw-blueprint/openclaw-plugins/ /usr/local/share/nemoclaw/openclaw-plugins/
RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-codex-acp \
/usr/local/lib/nemoclaw/sandbox-init.sh \
/usr/local/lib/nemoclaw/generate-openclaw-config.mts \
/usr/local/lib/nemoclaw/openclaw-build-messaging-plugins.py \
/usr/local/lib/nemoclaw/seed-wechat-accounts.py \
/scripts/generate-openclaw-config.mts \
/src/lib/messaging/applier/build/messaging-build-applier.mts \
&& chmod -R a+rX /src/lib/messaging \
&& chmod 644 /usr/local/lib/nemoclaw/openclaw_device_approval_policy.py \
/usr/local/lib/nemoclaw/clean_runtime_shell_env_shim.py \
&& if [ -d /usr/local/lib/nemoclaw/preloads ]; then find /usr/local/lib/nemoclaw/preloads -type f -name '*.js' -exec chmod 644 {} +; fi \
Expand Down Expand Up @@ -454,34 +453,10 @@ ARG NEMOCLAW_AGENT_TIMEOUT=600
# change at image build time. Ref: issue #2880
ARG NEMOCLAW_AGENT_HEARTBEAT_EVERY=
ARG NEMOCLAW_INFERENCE_COMPAT_B64=e30=
# Base64-encoded JSON list of messaging channel names to pre-configure
# (e.g. ["discord","telegram"]). Channels are added with placeholder tokens
# so the L7 proxy can rewrite them at egress. Default: empty list.
ARG NEMOCLAW_MESSAGING_CHANNELS_B64=W10=
# Base64-encoded JSON map of channel→allowed sender IDs for DM allowlisting
# (e.g. {"telegram":["123456789"]}). Channels with IDs get dmPolicy=allowlist.
# Slack also uses those IDs for channel @mention allowlisting. Channels without
# IDs keep the OpenClaw default (pairing). Default: empty map.
ARG NEMOCLAW_MESSAGING_ALLOWED_IDS_B64=e30=
# Base64-encoded JSON map of Discord guild configs keyed by server ID
# (e.g. {"1234567890":{"requireMention":true,"users":["555"]}}).
# Used to enable guild-channel responses for native Discord. Default: empty map.
ARG NEMOCLAW_DISCORD_GUILDS_B64=e30=
# Base64-encoded JSON Telegram config (e.g. {"requireMention":true}).
# When requireMention is true, Telegram groups get groups: {"*": {"requireMention": true}}
# with groupPolicy: open. See #1737, #3022. Default: empty map.
ARG NEMOCLAW_TELEGRAM_CONFIG_B64=e30=
# Base64-encoded JSON WeChat config (e.g.
# {"accountId":"…","baseUrl":"https://…","userId":"…"}).
# Captured by the host-side iLink QR login during onboard. Non-secret per-account
# metadata only — the bot token flows through the OpenShell provider, never
# baked into the image. Default: empty map.
ARG NEMOCLAW_WECHAT_CONFIG_B64=e30=
# Base64-encoded JSON Slack config (e.g.
# {"allowedChannels":["C012AB3CD","C987ZY6XW"]}).
# Channel IDs scope Slack channel @mention handling. User allowlists still come
# from NEMOCLAW_MESSAGING_ALLOWED_IDS_B64. Default: empty map.
ARG NEMOCLAW_SLACK_CONFIG_B64=e30=
# Base64-encoded messaging build plan for messaging build inputs and agent
# rendering. The plan contains placeholders only; secrets are resolved at
# runtime via OpenShell providers.
ARG NEMOCLAW_MESSAGING_PLAN_B64=
# Base64-encoded JSON array of secondary OpenClaw agent config entries
# (e.g. [{"id":"research","workspace":"/sandbox/.openclaw/workspace-research",
# "agentDir":"/sandbox/.openclaw/agents/research", ...}]).
Expand Down Expand Up @@ -535,12 +510,7 @@ ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_AGENT_TIMEOUT=${NEMOCLAW_AGENT_TIMEOUT} \
NEMOCLAW_AGENT_HEARTBEAT_EVERY=${NEMOCLAW_AGENT_HEARTBEAT_EVERY} \
NEMOCLAW_INFERENCE_COMPAT_B64=${NEMOCLAW_INFERENCE_COMPAT_B64} \
NEMOCLAW_MESSAGING_CHANNELS_B64=${NEMOCLAW_MESSAGING_CHANNELS_B64} \
NEMOCLAW_MESSAGING_ALLOWED_IDS_B64=${NEMOCLAW_MESSAGING_ALLOWED_IDS_B64} \
NEMOCLAW_DISCORD_GUILDS_B64=${NEMOCLAW_DISCORD_GUILDS_B64} \
NEMOCLAW_TELEGRAM_CONFIG_B64=${NEMOCLAW_TELEGRAM_CONFIG_B64} \
NEMOCLAW_WECHAT_CONFIG_B64=${NEMOCLAW_WECHAT_CONFIG_B64} \
NEMOCLAW_SLACK_CONFIG_B64=${NEMOCLAW_SLACK_CONFIG_B64} \
NEMOCLAW_MESSAGING_PLAN_B64=${NEMOCLAW_MESSAGING_PLAN_B64} \
NEMOCLAW_EXTRA_AGENTS_JSON_B64=${NEMOCLAW_EXTRA_AGENTS_JSON_B64} \
NEMOCLAW_OPENCLAW_WECHAT_PLUGIN_PREINSTALLED=1 \
NEMOCLAW_DISABLE_DEVICE_AUTH=${NEMOCLAW_DISABLE_DEVICE_AUTH} \
Expand All @@ -567,18 +537,26 @@ USER sandbox
# is opt-in via `shields up` (DAC 444 root:root + chattr +i).
# Build args (NEMOCLAW_MODEL, CHAT_UI_URL) customize per deployment.
#
# Generate openclaw.json from environment variables. Config generation logic
# lives in scripts/generate-openclaw-config.mts — see that file for the full
# list of env vars and derivation rules.
# Generate base openclaw.json from environment variables. Messaging build
# steps run through src/lib/messaging/applier/build/messaging-build-applier.mts.
#
# OpenClaw's managed proxy config activates process-wide HTTP_PROXY/HTTPS_PROXY
# for child npm processes. During image build the OpenShell gateway is not
# available at the runtime sandbox proxy address yet, so defer the final proxy
# block until after build-time OpenClaw doctor/plugin commands complete.
RUN NEMOCLAW_OPENCLAW_MANAGED_PROXY=0 node --experimental-strip-types /usr/local/lib/nemoclaw/generate-openclaw-config.mts
RUN NEMOCLAW_OPENCLAW_MANAGED_PROXY=0 node --experimental-strip-types /scripts/generate-openclaw-config.mts

# Install the non-messaging OpenClaw diagnostics plugin when OTEL is enabled.
# hadolint ignore=DL3059,DL4006
RUN python3 /usr/local/lib/nemoclaw/openclaw-build-messaging-plugins.py
RUN set -eu; \
if [ "$NEMOCLAW_OPENCLAW_OTEL" = "1" ]; then \
test -n "$OPENCLAW_VERSION"; \
openclaw plugins install "npm:@openclaw/diagnostics-otel@${OPENCLAW_VERSION}" --pin; \
openclaw doctor --fix --non-interactive; \
fi

# hadolint ignore=DL3059,DL4006
RUN node --experimental-strip-types /src/lib/messaging/applier/build/messaging-build-applier.mts --agent openclaw --phase agent-install

# Lock down npm for the next RUN: the local OpenClaw plugin install must
# resolve from /opt/nemoclaw and the staged plugin-runtime-deps tree without
Expand All @@ -592,16 +570,16 @@ ENV NPM_CONFIG_OFFLINE=true \
# This must fail the image build if registration fails; otherwise the sandbox
# can boot with a discoverable plugin manifest but without the /nemoclaw runtime
# command registered in the active Gateway.
# Re-apply WeChat account seeding after OpenClaw doctor/plugin-install touches
# openclaw.json; the seed script no-ops unless WeChat is actively configured.
# Messaging post-agent-install hooks run after the OpenClaw agent and
# NemoClaw plugin are installed; for example, WeChat seed files are written
# from messaging hook build-file outputs before the sandbox starts.
# Prune non-runtime metadata from staged bundled plugin dependencies before
# this layer is committed; deleting it in a later layer would not reduce the
# OCI image imported by k3s.
# hadolint ignore=DL3059,DL4006
RUN openclaw plugins install /opt/nemoclaw \
&& openclaw plugins enable nemoclaw \
&& openclaw plugins inspect nemoclaw --json > /dev/null \
&& python3 /usr/local/lib/nemoclaw/seed-wechat-accounts.py \
&& if [ -d /sandbox/.openclaw/plugin-runtime-deps ]; then \
find /sandbox/.openclaw/plugin-runtime-deps -type f \( \
-name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o \
Expand All @@ -613,6 +591,10 @@ RUN openclaw plugins install /opt/nemoclaw \
\) -prune -exec rm -rf {} +; \
fi

# Apply messaging render and post-agent-install build-file hooks after agent/plugin installation.
# hadolint ignore=DL3059,DL4006
RUN node --experimental-strip-types /src/lib/messaging/applier/build/messaging-build-applier.mts --agent openclaw --phase post-agent-install

# Release the offline lock so the runtime sandbox can install MCP servers,
# skills, and ad-hoc packages via the OpenShell L7 proxy.
ENV NPM_CONFIG_OFFLINE=false
Expand Down
33 changes: 14 additions & 19 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ RUN chmod -R a+rX /opt/nemoclaw-hermes-plugin/
COPY agents/hermes/generate-config.ts /opt/nemoclaw-hermes-config/generate-config.ts
COPY agents/hermes/config/ /opt/nemoclaw-hermes-config/config/
COPY agents/hermes/host/managed-tool-gateway-matrix.json /opt/nemoclaw-hermes-config/managed-tool-gateway-matrix.json
COPY src/lib/messaging/ /src/lib/messaging/
RUN find /opt/nemoclaw-hermes-config -type d -exec chmod 755 {} + \
&& find /opt/nemoclaw-hermes-config -type f -exec chmod 444 {} +
&& find /opt/nemoclaw-hermes-config -type f -exec chmod 444 {} + \
&& chmod -R a+rX /src/lib/messaging

# Copy blueprint (shared infrastructure)
COPY nemoclaw-blueprint/ /opt/nemoclaw-blueprint/
Expand All @@ -100,18 +102,7 @@ ARG NEMOCLAW_INFERENCE_API=openai-completions
# Hermes this URL points at the browser dashboard. The OpenAI-compatible
# API remains exposed separately on port 8642.
ARG CHAT_UI_URL=http://127.0.0.1:18789
ARG NEMOCLAW_MESSAGING_CHANNELS_B64=W10=
ARG NEMOCLAW_MESSAGING_ALLOWED_IDS_B64=e30=
ARG NEMOCLAW_DISCORD_GUILDS_B64=e30=
ARG NEMOCLAW_TELEGRAM_CONFIG_B64=e30=
# Captured by NemoClaw's host-side iLink QR login during onboard (see
# src/lib/onboard/wechat-config.ts). Carries {accountId, baseUrl, userId} so
# the Hermes WeChat adapter starts with WEIXIN_ACCOUNT_ID/WEIXIN_BASE_URL
# already populated from .env; no in-sandbox QR re-scan. The token itself
# is never baked here — it flows through the OpenShell L7 proxy via the
# WECHAT_BOT_TOKEN credential slot.
ARG NEMOCLAW_WECHAT_CONFIG_B64=e30=
ARG NEMOCLAW_SLACK_CONFIG_B64=e30=
ARG NEMOCLAW_MESSAGING_PLAN_B64=
ARG NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER=0
ARG NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64=W10=
ARG NEMOCLAW_BUILD_ID=default
Expand All @@ -123,12 +114,7 @@ ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_INFERENCE_BASE_URL=${NEMOCLAW_INFERENCE_BASE_URL} \
NEMOCLAW_INFERENCE_API=${NEMOCLAW_INFERENCE_API} \
CHAT_UI_URL=${CHAT_UI_URL} \
NEMOCLAW_MESSAGING_CHANNELS_B64=${NEMOCLAW_MESSAGING_CHANNELS_B64} \
NEMOCLAW_MESSAGING_ALLOWED_IDS_B64=${NEMOCLAW_MESSAGING_ALLOWED_IDS_B64} \
NEMOCLAW_DISCORD_GUILDS_B64=${NEMOCLAW_DISCORD_GUILDS_B64} \
NEMOCLAW_TELEGRAM_CONFIG_B64=${NEMOCLAW_TELEGRAM_CONFIG_B64} \
NEMOCLAW_WECHAT_CONFIG_B64=${NEMOCLAW_WECHAT_CONFIG_B64} \
NEMOCLAW_SLACK_CONFIG_B64=${NEMOCLAW_SLACK_CONFIG_B64} \
NEMOCLAW_MESSAGING_PLAN_B64=${NEMOCLAW_MESSAGING_PLAN_B64} \
NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER=${NEMOCLAW_HERMES_TOOL_GATEWAY_BROKER} \
NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64=${NEMOCLAW_HERMES_TOOL_GATEWAY_PRESETS_B64}

Expand All @@ -146,10 +132,19 @@ RUN mkdir -p /sandbox/.nemoclaw/blueprints/0.1.0 \
# code injection via build-arg interpolation (same concern as OpenClaw C-2).
RUN node --experimental-strip-types /opt/nemoclaw-hermes-config/generate-config.ts

# Apply messaging agent-install hooks before Hermes plugin installation.
# hadolint ignore=DL3059
RUN node --experimental-strip-types /src/lib/messaging/applier/build/messaging-build-applier.mts --agent hermes --phase agent-install

# Install NemoClaw plugin into Hermes
# hadolint ignore=DL3059
RUN mkdir -p /sandbox/.hermes/plugins/nemoclaw \
&& cp -r /opt/nemoclaw-hermes-plugin/* /sandbox/.hermes/plugins/nemoclaw/

# Apply messaging render and post-agent-install build-file hooks after agent/plugin installation.
# hadolint ignore=DL3059
RUN node --experimental-strip-types /src/lib/messaging/applier/build/messaging-build-applier.mts --agent hermes --phase post-agent-install

# Write the default SOUL.md (agent identity) for the sandboxed agent.
# This is the stock Hermes default soul (hermes_cli/default_soul.py,
# DEFAULT_SOUL_MD) shipped verbatim. The OpenShell/NemoClaw environment is
Expand Down
55 changes: 0 additions & 55 deletions agents/hermes/config/build-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,6 @@

import { Buffer } from "node:buffer";

export type MessagingAllowedIds = Record<string, (string | number)[]>;

export type DiscordGuilds = Record<
string,
{
requireMention?: boolean;
users?: (string | number)[];
}
>;

export type TelegramConfig = {
requireMention?: boolean;
};

// Non-secret per-account metadata captured by the host-side iLink QR login
// during onboard (src/lib/onboard/wechat-config.ts). The bot token itself
// stays in the OpenShell credential store; only these fields are serialized
// into the build arg, so the in-sandbox adapter can hydrate WEIXIN_ACCOUNT_ID
// and WEIXIN_BASE_URL without a fresh QR scan on rebuild.
export type WechatConfig = {
accountId?: string;
baseUrl?: string;
userId?: string;
};

export type SlackConfig = {
allowedChannels?: string[];
};

export type HermesBuildSettings = {
model: string;
baseUrl: string;
Expand All @@ -41,14 +12,6 @@ export type HermesBuildSettings = {
brokerEnabled: boolean;
presets: string[];
};
messaging: {
enabledChannels: Set<string>;
allowedIds: MessagingAllowedIds;
discordGuilds: DiscordGuilds;
telegramConfig: TelegramConfig;
wechatConfig: WechatConfig;
slackConfig: SlackConfig;
};
};

export function readHermesBuildSettings(env: NodeJS.ProcessEnv): HermesBuildSettings {
Expand All @@ -68,24 +31,6 @@ export function readHermesBuildSettings(env: NodeJS.ProcessEnv): HermesBuildSett
"W10=",
),
},
messaging: {
enabledChannels: new Set(
readBase64Json<string[]>(env, "NEMOCLAW_MESSAGING_CHANNELS_B64", "W10="),
),
allowedIds: readBase64Json<MessagingAllowedIds>(
env,
"NEMOCLAW_MESSAGING_ALLOWED_IDS_B64",
"e30=",
),
discordGuilds: readBase64Json<DiscordGuilds>(env, "NEMOCLAW_DISCORD_GUILDS_B64", "e30="),
telegramConfig: readBase64Json<TelegramConfig>(
env,
"NEMOCLAW_TELEGRAM_CONFIG_B64",
"e30=",
),
wechatConfig: readBase64Json<WechatConfig>(env, "NEMOCLAW_WECHAT_CONFIG_B64", "e30="),
slackConfig: readBase64Json<SlackConfig>(env, "NEMOCLAW_SLACK_CONFIG_B64", "e30="),
},
};
}

Expand Down
Loading
Loading