FROM python:3.12-slim # Fix timezone — HF Space defaults to UTC, we need Asia/Shanghai for log timestamps ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # System deps (add build tools for node-pty native compilation) RUN apt-get update && apt-get install -y --no-install-recommends \ git curl gnupg2 fontconfig make g++ python3 \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Clone hermes-agent (pinned to v2026.4.30 = v0.12.0) # Runtime auto-update will pull newer releases transparently RUN git clone --depth 1 --branch v2026.4.30 https://github.com/NousResearch/hermes-agent.git /app/hermes-agent && \ echo "v2026.4.30" > /app/hermes-agent.version # Build venv RUN python3 -m venv /app/venv ENV PATH="/app/venv/bin:$PATH" RUN pip install --quiet --upgrade pip && \ pip install --quiet psutil networkx duckduckgo-search && \ pip install --quiet -e "/app/hermes-agent[feishu,mcp,cron,pty]" && \ pip install --quiet aiohttp cryptography 2>&1 | tail -10 # Patch: add document file extensions to auto-detection for native delivery COPY scripts/patch_file_delivery.py /tmp/patch_file_delivery.py RUN python3 /tmp/patch_file_delivery.py; rm -f /tmp/patch_file_delivery.py # Patch: Feishu media support in send_message_tool + anti-hallucination prompts COPY patches/hermes-agent/agent/prompt_builder.py /app/hermes-agent/agent/prompt_builder.py COPY patches/hermes-agent/tools/send_message_tool.py /app/hermes-agent/tools/send_message_tool.py # Patch: Auto-inject MEDIA: tags from write_file tool calls COPY scripts/patch_auto_media.py /tmp/patch_auto_media.py RUN python3 /tmp/patch_auto_media.py; rm -f /tmp/patch_auto_media.py # Patch: Auto-resolve relative media paths to absolute paths COPY scripts/patch_resolve_media_paths.py /tmp/patch_resolve_media_paths.py RUN python3 /tmp/patch_resolve_media_paths.py; rm -f /tmp/patch_resolve_media_paths.py # Patch: Fix WeixinAdapter cross-event-loop session reuse # Prevents "Timeout context manager should be used inside a task" when # send_weixin_direct() reuses the gateway's aiohttp session from _run_async() COPY scripts/patch_weixin_cross_loop.py /tmp/patch_weixin_cross_loop.py RUN python3 /tmp/patch_weixin_cross_loop.py; rm -f /tmp/patch_weixin_cross_loop.py # Patch: Strip <|channel>thinking / model tags from user-visible output COPY scripts/patch_strip_thinking_tags.py /tmp/patch_strip_thinking_tags.py RUN python3 /tmp/patch_strip_thinking_tags.py; rm -f /tmp/patch_strip_thinking_tags.py # Patch: Sandbox isolation for dangerous terminal commands (inspired by OpenAI Agents SDK) # Installs bubblewrap if available, falls back to unshare + resource limits RUN apt-get update && apt-get install -y --no-install-recommends bubblewrap 2>/dev/null || true; rm -rf /var/lib/apt/lists/* COPY scripts/patch_sandbox_isolation.py /tmp/patch_sandbox_isolation.py RUN python3 /tmp/patch_sandbox_isolation.py; rm -f /tmp/patch_sandbox_isolation.py # Patch: DuckDuckGo free fallback for web_search (no API key needed) COPY scripts/patch_web_search_fallback.py /tmp/patch_web_search_fallback.py RUN python3 /tmp/patch_web_search_fallback.py; rm -f /tmp/patch_web_search_fallback.py # Install Node.js 23 RUN ARCH=$(dpkg --print-architecture) \ && if [ "$ARCH" = "amd64" ]; then NODE_ARCH="x64"; else NODE_ARCH="$ARCH"; fi \ && echo "Installing Node.js v23 for ${NODE_ARCH}" \ && curl -fsSL "https://nodejs.org/dist/v23.11.0/node-v23.11.0-linux-${NODE_ARCH}.tar.gz" \ -o /tmp/node.tar.gz \ && tar -xzf /tmp/node.tar.gz -C /usr/local --strip-components=1 \ && rm -f /tmp/node.tar.gz \ && node --version && npm --version # Chinese font (Noto Sans SC Regular + Bold) RUN mkdir -p /usr/share/fonts/truetype/noto && \ curl -sL "https://github.com/googlefonts/noto-cjk/raw/main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf" \ -o /usr/share/fonts/truetype/noto/NotoSansSC-Regular.otf && \ curl -sL "https://github.com/googlefonts/noto-cjk/raw/main/Sans/SubsetOTF/SC/NotoSansSC-Bold.otf" \ -o /usr/share/fonts/truetype/noto/NotoSansSC-Bold.otf && \ fc-cache -f # Clone agency-agents-zh (211 expert role prompts for instant role switching) RUN git clone --depth 1 https://github.com/jnMetaCode/agency-agents-zh.git /app/agency-agents && \ echo "agency-agents-zh cloned ($(find /app/agency-agents -name '*.md' ! -name 'README*' ! -name 'CATALOG*' ! -name 'AGENT-LIST*' ! -name 'CONTRIBUTING*' ! -name 'LICENSE*' ! -name 'UPSTREAM*' | wc -l) agent files)" # Build hermes-web-ui v0.5.9 # Aligned with upstream Dockerfile: NODE_OPTIONS + npm rebuild node-pty RUN rm -rf /tmp/hermes-web-ui && \ git clone --depth 1 --branch v0.5.9 https://github.com/EKKOLearnAI/hermes-web-ui.git /tmp/hermes-web-ui && \ cd /tmp/hermes-web-ui && \ echo "build-v0.5.9-$(date +%Y%m%d)" > .buildstamp && \ npm install --ignore-scripts 2>&1 | tail -5 && \ npm rebuild node-pty 2>&1 | tail -5 && \ NODE_OPTIONS=--max-old-space-size=4096 npm run build 2>&1 | tail -10 && \ mkdir -p /app/webui-server && \ cp -r dist/server/* /app/webui-server/ && \ mkdir -p /app/webui-client && \ cp -r dist/client/* /app/webui-client/ && \ cp package.json /app/webui-server/package.json && \ npm prune --omit=dev --prefix /tmp/hermes-web-ui 2>&1 | tail -3 && \ cp -r node_modules /app/webui-server/node_modules && \ rm -rf /tmp/hermes-web-ui && \ echo "v0.5.9" > /app/webui.version && \ echo "hermes-web-ui v0.5.9 build done" # Create hermes home RUN mkdir -p /root/.hermes/plugins/image_gen/pollinations # Copy config files (to both hermes home AND /app for persistence fallback) COPY config.yaml /root/.hermes/config.yaml COPY SOUL.md /root/.hermes/SOUL.md # Keep repo copies in /app as fallback sources for persistent storage recovery COPY config.yaml /app/config.yaml COPY .env.example /app/.env.example COPY entry.py /app/entry.py COPY dashboard.html /app/dashboard.html COPY plugins/pollinations/ /root/.hermes/plugins/image_gen/pollinations/ COPY scripts/ /app/scripts/ COPY custom-agents/ /app/custom-agents/ # Startup script COPY start.sh /app/start.sh RUN chmod +x /app/start.sh EXPOSE 7860 ENV HERMES_ACCEPT_HOOKS=1 ENV NODE_ENV=production ENV AUTH_TOKEN=hermes-bot-2026 CMD ["/app/start.sh"]