feat: Hermes Admin Panel + Swarm Phase 1 + OpenSandbox lifecycle - #1
Conversation
The check_interval parameter on terminal_tool sent periodic output updates to the gateway chat, but these were display-only — the agent couldn't see or act on them. This added schema bloat and introduced a bug where notify_on_complete=True was silently dropped when check_interval was also set (the not-check_interval guard skipped fast-watcher registration, and the check_interval watcher dict was missing the notify_on_complete key). Removing check_interval entirely: - Eliminates the notify_on_complete interaction bug - Reduces tool schema size (one fewer parameter for the model) - Simplifies the watcher registration path - notify_on_complete (agent wake-on-completion) still works - watch_patterns (output alerting) still works - process(action='poll') covers manual status checking Closes #7947 (root cause eliminated rather than patched).
Add display.platforms section to config.yaml for per-platform overrides of
display settings (tool_progress, show_reasoning, streaming, tool_preview_length).
Each platform gets sensible built-in defaults based on capability tier:
- High (telegram, discord): tool_progress=all, streaming follows global
- Medium (slack, mattermost, matrix, feishu): tool_progress=new
- Low (signal, whatsapp, bluebubbles, wecom, etc.): tool_progress=off, streaming=false
- Minimal (email, sms, webhook, homeassistant): tool_progress=off, streaming=false
Example config:
display:
platforms:
telegram:
tool_progress: all
show_reasoning: true
slack:
tool_progress: off
Resolution order: platform override > global setting > built-in platform default.
Changes:
- New gateway/display_config.py: resolver module with tier-based platform defaults
- gateway/run.py: tool_progress, tool_preview_length, streaming, show_reasoning
all resolve per-platform via the new resolver
- /verbose command: now cycles tool_progress per-platform (saves to
display.platforms.<platform>.tool_progress instead of global)
- /reasoning show|hide: now saves show_reasoning per-platform
- Config version 15 -> 16: migrates tool_progress_overrides into display.platforms
- Backward compat: legacy tool_progress_overrides still read as fallback
- 27 new tests for resolver, normalization, migration, backward compat
- Updated verbose command tests for per-platform behavior
Addresses community request for per-channel verbosity control (Guillaume Meyer,
Nathan Danielsen) — high verbosity on backchannel Telegram, low on customer-facing
Slack, none on email.
…(#7991) * feat: component-separated logging with session context and filtering Phase 1 — Gateway log isolation: - gateway.log now only receives records from gateway.* loggers (platform adapters, session management, slash commands, delivery) - agent.log remains the catch-all (all components) - errors.log remains WARNING+ catch-all - Moved gateway.log handler creation from gateway/run.py into hermes_logging.setup_logging(mode='gateway') with _ComponentFilter Phase 2 — Session ID injection: - Added set_session_context(session_id) / clear_session_context() API using threading.local() for per-thread session tracking - _SessionFilter enriches every log record with session_tag attribute - Log format: '2026-04-11 10:23:45 INFO [session_id] logger.name: msg' - Session context set at start of run_conversation() in run_agent.py - Thread-isolated: gateway conversations on different threads don't leak Phase 3 — Component filtering in hermes logs: - Added --component flag: hermes logs --component gateway|agent|tools|cli|cron - COMPONENT_PREFIXES maps component names to logger name prefixes - Works with all existing filters (--level, --session, --since, -f) - Logger name extraction handles both old and new log formats Files changed: - hermes_logging.py: _SessionFilter, _ComponentFilter, COMPONENT_PREFIXES, set/clear_session_context(), gateway.log creation in setup_logging() - gateway/run.py: removed redundant gateway.log handler (now in hermes_logging) - run_agent.py: set_session_context() at start of run_conversation() - hermes_cli/logs.py: --component filter, logger name extraction - hermes_cli/main.py: --component argument on logs subparser Addresses community request for component-separated, filterable logging. Zero changes to existing logger names — __name__ already provides hierarchy. * fix: use LogRecord factory instead of per-handler _SessionFilter The _SessionFilter approach required attaching a filter to every handler we create. Any handler created outside our _add_rotating_handler (like the gateway stderr handler, or third-party handlers) would crash with KeyError: 'session_tag' if it used our format string. Replace with logging.setLogRecordFactory() which injects session_tag into every LogRecord at creation time — process-global, zero per-handler wiring needed. The factory is installed at import time (before setup_logging) so session_tag is available from the moment hermes_logging is imported. - Idempotent: marker attribute prevents double-wrapping on module reload - Chains with existing factory: won't break third-party record factories - Removes _SessionFilter from _add_rotating_handler and setup_verbose_logging - Adds tests: record factory injection, idempotency, arbitrary handler compat
…#8014) * perf(ssh,modal): bulk file sync via tar pipe and tar/base64 archive SSH: symlink-staging + tar -ch piped over SSH in a single TCP stream. Eliminates per-file scp round-trips. Handles timeout (kills both processes), SSH Popen failure (kills tar), and tar create failure. Modal: in-memory gzipped tar archive, base64-encoded, decoded+extracted in one exec call. Checks exit code and raises on failure. Both backends use shared helpers extracted into file_sync.py: - quoted_mkdir_command() — mirrors existing quoted_rm_command() - unique_parent_dirs() — deduplicates parent dirs from file pairs Migrates _ensure_remote_dirs to use the new helpers. 28 new tests (21 SSH + 7 Modal), all passing. Closes #7465 Closes #7467 * fix(modal): pipe stdin to avoid ARG_MAX, clean up review findings - Modal bulk upload: stream base64 payload through proc.stdin in 1MB chunks instead of embedding in command string (Modal SDK enforces 64KB ARG_MAX_BYTES — typical payloads are ~4.3MB) - Modal single-file upload: same stdin fix, add exit code checking - Remove what-narrating comments in ssh.py and modal.py (keep WHY comments: symlink staging rationale, SIGPIPE, deadlock avoidance) - Remove unnecessary `sandbox = self._sandbox` alias in modal bulk - Daytona: use shared helpers (unique_parent_dirs, quoted_mkdir_command) instead of inlined duplicates --------- Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
…tion (#7981) Fixes #7952 — Matrix E2EE completely broken after mautrix migration. - Replace MemoryCryptoStore + pickle/HMAC persistence with mautrix's PgCryptoStore backed by SQLite via aiosqlite. Crypto state now persists reliably across restarts without fragile serialization. - Add handle_sync() call on initial sync response so to-device events (queued Megolm key shares) are dispatched to OlmMachine instead of being silently dropped. - Add _verify_device_keys_on_server() after loading crypto state. Detects missing keys (re-uploads), stale keys from migration (attempts re-upload), and corrupted state (refuses E2EE). - Add _CryptoStateStore adapter wrapping MemoryStateStore to satisfy mautrix crypto's StateStore interface (is_encrypted, get_encryption_info, find_shared_rooms). - Remove redundant share_keys() call from sync loop — OlmMachine already handles this via DEVICE_OTK_COUNT event handler. - Fix datetime vs float TypeError in session.py suspend_recently_active() that crashed gateway startup. - Add aiosqlite and asyncpg to [matrix] extra in pyproject.toml. - Update test mocks for PgCryptoStore/Database and add query_keys mock for key verification. 174 tests pass. - Add E2EE upgrade/migration docs to Matrix user guide.
* feat: add `hermes backup` and `hermes import` commands hermes backup — creates a zip of ~/.hermes/ (config, skills, sessions, profiles, memories, skins, cron jobs, etc.) excluding the hermes-agent codebase, __pycache__, and runtime PID files. Defaults to ~/hermes-backup-<timestamp>.zip, customizable with -o. hermes import <zipfile> — restores from a backup zip, validating it looks like a hermes backup before extracting. Handles .hermes/ prefix stripping, path traversal protection, and confirmation prompts (skip with --force). 29 tests covering exclusion rules, backup creation, import validation, prefix detection, path traversal blocking, confirmation flow, and a full round-trip test. * test: improve backup/import coverage to 97% Add 17 additional tests covering: - _format_size helper (bytes through terabytes) - Nonexistent hermes home error exit - Output path is a directory (auto-names inside it) - Output without .zip suffix (auto-appends) - Empty hermes home (all files excluded) - Permission errors during backup and import - Output zip inside hermes root (skips itself) - Not-a-zip file rejection - EOFError and KeyboardInterrupt during confirmation - 500+ file progress display - Directory-only zip prefix detection Remove dead code branch in _detect_prefix (unreachable guard). * feat: auto-restore profile wrapper scripts on import After extracting backup files, hermes import now scans profiles/ for subdirectories with config.yaml or .env and recreates the ~/.local/bin wrapper scripts so profile aliases (e.g. 'coder chat') work immediately. Also prints guidance for re-installing gateway services per profile. Handles edge cases: - Skips profile dirs without config (not real profiles) - Skips aliases that collide with existing commands - Gracefully degrades if hermes_cli.profiles isn't available (fresh install) - Shows PATH hint if ~/.local/bin isn't in PATH 3 new profile restoration tests (49 total).
Adds an optional focus topic to /compress: `/compress database schema` guides the summariser to preserve information related to the focus topic (60-70% of summary budget) while compressing everything else more aggressively. Inspired by Claude Code's /compact <focus>. Changes: - context_compressor.py: focus_topic parameter on _generate_summary() and compress(); appends FOCUS TOPIC guidance block to the LLM prompt - run_agent.py: focus_topic parameter on _compress_context(), passed through to the compressor - cli.py: _manual_compress() extracts focus topic from command string, preserves existing manual_compression_feedback integration (no regression) - gateway/run.py: _handle_compress_command() extracts focus from event args and passes through — full gateway parity - commands.py: args_hint="[focus topic]" on /compress CommandDef Salvaged from PR #7459 (CLI /compress focus only — /context command deferred). 15 new tests across CLI, compressor, and gateway.
The gateway startup path references RedactingFormatter without importing it, causing a NameError crash when launched with a verbosity flag (e.g. via launchd --replace). Fixes #8044 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tions (#8107)
After compression, models (especially Kimi 2.5) would sometimes respond
to questions from the summary instead of the latest user message. This
happened ~30% of the time on Telegram.
Root cause: the summary's 'Next Steps' section read as active instructions,
and the SUMMARY_PREFIX didn't explicitly tell the model to ignore questions
in the summary. When the summary merged into the first tail message, there
was no clear separator between historical context and the actual user message.
Changes inspired by competitor analysis (Claude Code, OpenCode, Codex):
1. SUMMARY_PREFIX rewritten with explicit 'Do NOT answer questions from
this summary — respond ONLY to the latest user message AFTER it'
2. Summarizer preamble (shared by both prompts) adds:
- 'Do NOT respond to any questions' (from OpenCode's approach)
- 'Different assistant' framing (from Codex) to create psychological
distance between summary content and active conversation
3. New summary sections:
- '## Resolved Questions' — tracks already-answered questions with
their answers, preventing re-answering (from Claude Code's
'Pending user asks' pattern)
- '## Pending User Asks' — explicitly marks unanswered questions
- '## Remaining Work' replaces '## Next Steps' — passive framing
avoids reading as active instructions
4. merge-summary-into-tail path now inserts a clear separator:
'--- END OF CONTEXT SUMMARY — respond to the message below ---'
5. Iterative update prompt now instructs: 'Move answered questions to
Resolved Questions' to maintain the resolved/pending distinction
across multiple compactions.
On servers with broken or unreachable IPv6, Python's socket.getaddrinfo returns AAAA records first. urllib/httpx/requests all try IPv6 connections first and hang for the full TCP timeout before falling back to IPv4. This affects web_extract, web_search, the OpenAI SDK, and all HTTP tools. Adds network.force_ipv4 config option (default: false) that monkey-patches socket.getaddrinfo to resolve as AF_INET when the caller didn't specify a family. Falls back to full resolution if no A record exists, so pure-IPv6 hosts still work. Applied early at all three entry points (CLI, gateway, cron scheduler) before any HTTP clients are created. Reported by user @29n — Chinese Ubuntu server with unreachable IPv6 causing timeouts on lobste.rs and other IPv6-enabled sites while Google/GitHub worked fine (IPv4-only resolution).
…opic context (#8187) Rewrite the cronjob tool's 'deliver' parameter description to strongly guide models toward omitting the parameter (which auto-detects origin including thread/topic). The previous description listed all platform names equally, inviting models to construct explicit targets like 'telegram:<chat_id>' which silently drops the thread_id. New description: - Leads with 'Omit this parameter' as the recommended path - Explicitly warns that platform:chat_id without :thread_id loses topics - Removes the long flat list of platform names that invited construction Also adds diagnostic logging at two key points: - _origin_from_env(): logs when thread_id is captured during job creation - _deliver_result(): warns when origin has thread_id but delivery target lost it; logs at debug when delivering to a specific thread Helps diagnose user-reported issue where cron responses from Telegram topics are delivered to the main chat instead of the originating topic.
The TUI transition (4970705, f83e86d) replaced stacked per-tool history lines with a single live-updating spinner widget. While the spinner provides a nice live timer, it removed the scrollback history that users relied on to see what the agent did during a session. This restores stacked tool progress lines in 'all' and 'new' modes by printing persistent scrollback lines via _cprint() when tools complete, in addition to the existing live spinner display. Behavior per mode: - off: no scrollback lines, no spinner (unchanged) - new: scrollback line on completion, skipping consecutive same-tool repeats - all: scrollback line on every tool completion - verbose: no scrollback (run_agent.py handles verbose output directly) Implementation: - Store function_args from tool.started events in _pending_tool_info - On tool.completed, pop stored args and format via get_cute_tool_message() - FIFO queue per function_name handles concurrent tool execution - 'new' mode tracks _last_scrollback_tool for dedup - State cleared at end of agent run Reported by community user Mr.D — the stacked history provides transparency into what the agent is doing, which builds trust. Addresses user report from Discord about lost tool call visibility.
The interrupt mechanism for regular text messages (non-commands) during active agent runs relied on a single async polling task (monitor_for_interrupt) with no error handling. If this task died silently due to an unhandled exception, stale adapter reference after reconnect, or any other failure, user messages sent during agent execution would be queued but never trigger an actual interrupt — the agent would continue running until it finished naturally, then process the queued message. Three improvements: 1. Error handling in monitor_for_interrupt(): wrap the polling body in try/except so transient errors are logged and retried instead of silently killing the task. 2. Fresh adapter reference on each poll iteration: re-resolve self.adapters.get(source.platform) every 200ms instead of capturing the adapter once at task creation time. This prevents stale references after adapter reconnects. 3. Backup interrupt check in the inactivity poll loop: both the unlimited and timeout-enabled paths now check for pending interrupts every 5 seconds (the existing poll interval). Uses a shared _interrupt_detected asyncio.Event to avoid double-firing when the primary monitor already handled the interrupt. Logs at INFO level with monitor task state for debugging.
…ternal credentials (#8224) Users whose credentials exist only in external files — OpenAI Codex OAuth tokens in ~/.codex/auth.json or Anthropic Claude Code credentials in ~/.claude/.credentials.json — would not see those providers in the /model picker, even though hermes auth and hermes model detected them. Root cause: list_authenticated_providers() only checked the raw Hermes auth store and env vars. External credential file fallbacks (Codex CLI import, Claude Code file discovery) were never triggered. Fix (three parts): 1. _seed_from_singletons() in credential_pool.py: openai-codex now imports from ~/.codex/auth.json when the Hermes auth store is empty, mirroring resolve_codex_runtime_credentials(). 2. list_authenticated_providers() in model_switch.py: auth store + pool checks now run for ALL providers (not just OAuth auth_type), catching providers like anthropic that support both API key and OAuth. 3. list_authenticated_providers(): direct check for anthropic external credential files (Claude Code, Hermes PKCE). The credential pool intentionally gates anthropic behind is_provider_explicitly_configured() to prevent auxiliary tasks from silently consuming tokens. The /model picker bypasses this gate since it is discovery-oriented.
- Add rebrand_text() that replaces OpenClaw, Open Claw, Open-Claw, ClawdBot, and MoltBot with Hermes (case-insensitive, word-boundary) - Apply rebranding to memory entries (MEMORY.md, USER.md, daily memory) - Apply rebranding to SOUL.md and workspace instructions via new transform parameter on copy_file() - Fix moldbot -> moltbot typo across codebase (claw.py, migration script, docs, tests) - Add unit tests for rebrand_text and integration tests for memory and soul migration rebranding
Remove auto-archival from hermes claw migrate — not its responsibility (hermes claw cleanup is still there for that). Skip MESSAGING_CWD when it points inside the OpenClaw source directory, which was the actual root cause of agent confusion after migration. Use Path.is_relative_to() for robust path containment check. Salvaged from PR #8192 by opriz. Co-authored-by: opriz <opriz@users.noreply.github.com>
Add a 'tip of the day' feature that displays a random one-liner about Hermes Agent features on every new session — CLI startup, /clear, /new, and gateway /new across all messaging platforms. - New hermes_cli/tips.py module with 210 curated tips covering slash commands, keybindings, CLI flags, config options, tools, gateway platforms, profiles, sessions, memory, skills, cron, voice, security, and more - CLI: tips display in skin-aware dim gold color after the welcome line - Gateway: tips append to the /new and /reset response on all platforms - Fully wrapped in try/except — tips are non-critical and never break startup or reset Display format (CLI): ✦ Tip: /btw <question> asks a quick side question without tools or history. Display format (gateway): ✨ Session reset! Starting fresh. ✦ Tip: hermes -c resumes your most recent CLI session.
…onsumed output via wait/poll/log (#8228) When the agent calls process(action='wait') or process(action='poll') and gets the exited status, the completion_queue notification is redundant — the agent already has the output from the tool return. Previously, the drain loops in CLI and gateway would still inject the [SYSTEM: Background process completed] message, causing the agent to receive the same information twice. Fix: track session IDs in _completion_consumed set when wait/poll/log returns an exited process. Drain loops in cli.py and gateway watcher skip completion events for consumed sessions. Watch pattern events are never suppressed (they have independent semantics). Adds 4 tests covering wait/poll/log marking and running-process negative case.
…tructured content together Add content-aware splitting to compact mode: short chat-like exchanges (2-6 short lines without headings/lists/quotes) get separate message bubbles for a natural chat feel, while structured content (tables, headings with body, numbered lists) stays in a single message. Cherry-picked from PR #7587 by bravohenry, adapted to the compact/legacy split_per_line architecture from #7903.
- Add gosu for runtime privilege dropping from root to hermes user - Support HERMES_UID/HERMES_GID env vars for host mount permission matching - Switch to debian:13.4-slim base image - Use uv venv instead of pip install --break-system-packages - Pin uv and gosu multi-stage images with SHA256 digests - Set PLAYWRIGHT_BROWSERS_PATH to /opt/hermes/.playwright so build-time chromium install survives the /opt/data volume mount - Keep procps for container debugging Based on work by m0n5t3r in PR #5811. Stripped to hardening-only changes (non-root, virtualenv, slim base); matrix deps, fonts, xvfb, and entrypoint playwright download deferred to follow-up.
The slim image drops packages that may be needed at runtime. Keep the full Debian base for compatibility.
Add lesser-known power-user tips covering: - BOOT.md gateway startup automation - Cron script attachment for data collection pipelines - Prefill messages for few-shot priming - Focus topic compression (/compress <topic>) - Terminal exit code annotations and auto-retry - Automatic sudo password piping - execute_code built-in helpers (json_parse, shell_quote, retry) - File loop detection and staleness warnings - MCP sampling and dynamic tool discovery - Delegation heartbeat and ACP child agents (Claude Code) - 402 auto-fallback in auxiliary client - Container mode, HERMES_HOME_MODE, subprocess HOME isolation - Ctrl+C 5-tier priority system - Browser CDP URL override and stealth mode - Skills quarantine, audit log, and well-known protocol - Per-platform display overrides, human delay mode - And many more deep-cut features
…(#8231) * fix: list all available toolsets in delegate_task schema description The delegate_task tool's toolsets parameter description only mentioned 'terminal', 'file', and 'web' as examples. Models (especially smaller ones like Gemma) would substitute 'web' for 'browser' because they didn't know 'browser' was a valid option. Now dynamically builds the toolset list from the TOOLSETS dict at import time, excluding blocked, composite, and platform-specific toolsets. Auto-updates when new toolsets are added. Reported by jeffutter on Discord. * chore: exclude moa and rl from delegate_task toolset list
- Add openai/openai-codex -> openai mapping to PROVIDER_TO_MODELS_DEV so context-length lookups use models.dev data instead of 128k fallback. Fixes #8161. - Set api_mode from custom_providers entry when switching via hermes model, and clear stale api_mode when the entry has none. Also extract api_mode in _named_custom_provider_map(). Fixes #8181. - Convert OpenAI image_url content blocks to Anthropic image blocks when the endpoint is Anthropic-compatible (MiniMax, MiniMax-CN, or any URL containing /anthropic). Fixes #8147.
… (#8209)
The previous wording ('If one clearly matches') set too high a threshold,
and 'If none match, proceed normally' was an easy escape hatch for lazy
models. Now:
- Lowered threshold: 'matches or is even partially relevant'
- Added MUST directive and 'err on the side of loading' guidance
- Replaced permissive closer with 'only proceed without if genuinely none
are relevant'
This should reduce cases where the agent skips loading relevant skills
unless explicitly forced.
Reject non-URL values (e.g. shell commands typed by mistake) in the base URL prompt during provider setup. Previously any string was saved as-is to .env, breaking connectivity when the garbage value was used as the API endpoint. Adds http:// / https:// prefix check with a clear error message. The custom-endpoint flow already had this validation (line 1620); this brings the generic API-key provider flow to parity. Triggered by a user support case where 'nano ~/.hermes/.env' was accidentally entered as GLM_BASE_URL during Z.AI setup.
…suming it
The monitor_for_interrupt() and backup interrupt checks were calling
get_pending_message() which pops the message from the adapter's queue.
This created a race condition: if the agent finished naturally before
checking _interrupt_requested, the pending message was permanently lost.
Timeline of the race:
1. Agent near completion, user sends message
2. Level 1 guard stores message in adapter._pending_messages, sets event
3. monitor_for_interrupt() detects event, POPS message, calls agent.interrupt()
4. Agent's run_conversation() was already returning (interrupted=False)
5. Post-run dequeue finds nothing (monitor already consumed it)
6. result.get('interrupted') is False so interrupt_message fallback doesn't fire
7. User message permanently lost — agent finishes without processing it
Fix: change all three interrupt detection sites (primary monitor + two
backup checks) from get_pending_message() (pop) to
_pending_messages.get() (peek). The message stays in the adapter's queue
until _dequeue_pending_event() consumes it in the post-run handler,
which runs regardless of whether the agent was interrupted or finished
naturally.
Reported by @_SushantSays — intermittent message loss during long
terminal command execution, persisting after the previous fix (73f970f)
which addressed monitor task death but not this consumption race.
…gging (#8276) After /model switches the model (both picker and text paths), the cached agent's config signature becomes stale — the agent was updated in-place via switch_model() but the cache tuple's signature was never refreshed. The next turn *should* detect the signature mismatch and create a fresh agent, but this relies on the new model's signature differing from the old one in _agent_config_signature(). Evicting the cached agent explicitly after storing the session override is more defensive — the next turn is guaranteed to create a fresh agent from the override without depending on signature mismatch detection. Also adds debug logging at three key decision points so we can trace exactly what happens when /model + /retry interact: - _resolve_session_agent_runtime: which override path is taken (fast with api_key vs fallback), or why no override was found - _run_agent.run_sync: final resolved model/provider before agent creation Reported: /model switch to xiaomi/mimo-v2-pro followed by /retry still used the old model (glm-5.1).
- index.css: add Tailwind v4 @theme directive (all colors were transparent) - AdminLayout.tsx: fix layout to h-screen flex column for proper scrolling - admin-api.ts: handle Pydantic 422 array error format (was crashing React) - CreateAgentPage.tsx: add MiniMax and Kimi LLM provider configs - CreateAgentPage.tsx: fix layout with min-h-0 for scrollable content - dist/: rebuild frontend with all fixes
- ingress.yaml: add proxy timeout annotations for long K8s operations - docker-compose.yml: configuration updates - entrypoint-merged.sh: merged admin + gateway entrypoint script
- Fix CustomObjectsApi missing api_client in get_pod_metrics() - Add get_node_metrics() to K8sClient for node-level metrics - Add _parse_cpu() and _parse_memory() helpers for K8s quantity parsing - Populate ResourceUsage in list_agents() via get_resource_usage() - Calculate cpu_usage_percent/memory_usage_percent in get_cluster_status()
…, logging - C1: Add metrics.k8s.io pods/nodes permissions to ClusterRole in rbac.yaml - C2: Parallelize resource usage fetch in list_agents() with asyncio.gather - H2: get_agent_detail now calls get_resource_usage() instead of empty default - M6: get_agent_detail passes conditions to _resolve_status() for failed state - H4: Replace silent except:pass in _read_stream_sync with logger.warning
- Remove hostNetwork: true from admin deployment — use ClusterIP + Ingress - Remove dnsPolicy: Default — let K8s use default ClusterFirst - Move _ingress_lock from class attribute to __init__ instance attribute to prevent RuntimeError across event loops
Dark purple backgrounds (#0d0221), neon pink (#ff2a6d) + cyan (#05d9e8) accents, Orbitron + Exo 2 typography, glass-effect sidebar layout with mobile drawer, staggered page animations, SVG arc gauges, and prefers-reduced-motion support. All functionality and i18n preserved.
… components - Extract getApiError() to utils.ts, replacing 9 repeated error extraction patterns - Merge YamlEditor/SoulEditor into shared TextConfigEditor component (-60 lines) - Add --color-bar-track CSS token, replace 7 hardcoded rgba() values - Replace inline spinners in SettingsPage with LoadingSpinner component - Fix statusOrder import: DashboardPage imports directly from utils.ts - Remove unused Tabs.tsx component - Remove stale AdminApiError imports from cleaned files
…d data persistence
- Add WeChat QR login/unbind/status endpoints and frontend components
- Display agent display_name from K8s annotations on cards and detail page
- Add POST /agents/{id}/api-key endpoint for full key reveal with audit logging
- Implement data persistence: templates (dual-layer read), resource limits (JSON file)
- Add Anthropic-compatible api_mode support in config rendering
- Add Anthropic compat provider with base_url handling
- Fix clipboard copy on HTTP deployments with execCommand fallback
- Use hmac.compare_digest for timing-safe admin key comparison
- Add Playwright E2E test infrastructure with mock API fixtures
|
Addresses 11 CRITICAL and 23 HIGH issues from five-expert review of the swarm collaboration design. Covers sync/async bridge, message transport redesign, exactly-once semantics, Redis deployment strategy, connection management, circuit breaker, frontend interaction specs, Zustand state management, and SSE real-time transport.
Consolidate original design v1.0 and technical supplementary document (addressing 11 CRITICAL + 23 HIGH review issues) into a single v2.0 design document. Removes the separate supplementary file. Key additions: sync/async bridge (3-thread architecture), message transport redesign (Streams + Pub/Sub dual layer), exactly-once semantics (5-layer defense), Redis deployment (3-phase), connection management, circuit breaker + graceful degradation, frontend interaction specs, Zustand state management, SSE real-time transport, visual design spec.
… client, and tool integration Implements the Swarm Phase 1 backend: Redis connection management, health checks, exactly-once task delivery via Streams+Pub/Sub, circuit breaker for fault tolerance, graceful degradation to standalone mode, and the swarm_tool bridge for the agent loop's three-thread architecture. 47 tests covering all modules.
Redis 7-alpine with redis-exporter sidecar, ConfigMap (AOF, maxmemory 384mb allkeys-lru), Secret template, local PV/PVC, and NetworkPolicy restricting ingress to hermes-agent namespace.
Pydantic models, authenticated routes for agent registry/health/metrics, SSE stream with one-time token auth, and swarm router mounted on admin app.
…stores SwarmGuard feature flag, RedisHealthCard, SwarmOverviewPage with agent grid/stats, swarm-sse.ts with exponential backoff reconnect, Zustand stores for registry and events. Full zh/en i18n coverage.
Playwright config and swarm.spec.ts with 3 tests (agent cards, Redis health, capability guard redirect). Updated dist build output.
2810-line plan with 27 tasks across 3 groups (Redis infrastructure, core runtime, admin panel), plus hermes admin panel PRD spec.
- CLAUDE.md: architecture, conventions (auth, i18n, SSE, API layer), testing guide, common pitfalls - scripts/check-admin.sh: CI checks (i18n sync, console.log audit, TypeScript, build, auth patterns, bare except, secrets, E2E count) - scripts/admin-hooks.sh: Claude Code PostToolUse hooks (tsc, console.log, i18n sync, auth pattern) - scripts/pre-commit.sh: git pre-commit hook for security patterns
- Add redis[hiredis] to backend requirements.txt - Copy swarm/ package into Docker image (needed by swarm_routes.py) - Fix redis PV node affinity (roc-epyc, not hermes-node) - Add imagePullPolicy: Never to redis-exporter sidecar - Update frontend build artifacts
|
…line Connect swarm collaboration to the agent lifecycle: - run_agent.py: AIAgent._init_swarm() reads swarm config, creates ResilientSwarmClient with graceful degradation, wires swarm_tool - admin backend: render_config_yaml() generates swarm: section, CreateAgentRequest accepts swarm_enabled/capabilities/max_tasks - gateway/run.py: bridges swarm config to SWARM_* env vars - K8s deployments: inject SWARM_REDIS_URL and K8S_DEPLOYMENT env vars so agents can identify themselves and connect to Redis
capabilities is already a list after json.loads(profile_json), calling json.loads() again on a list raises TypeError, silently skipping agents.
- SwarmGuard redirects to "/" which maps to "/admin" URL, not root "/" - Use regex matcher for Connected text to support i18n variations
|
- Auth key sync: both main.py and swarm_routes.py now read from app.state - SPA path traversal: add resolve() + containment check on static file serving - WeChat credentials: chmod 0o750/0o640 instead of 0o777/0o666 - Redis URL logging: sanitize password before logging in _init_swarm - ResilientSwarmClient: recover from STANDALONE mode on successful heartbeat - Admin key fallback: add warning log for plaintext file persistence - I18n context: memoize provider value to prevent unnecessary re-renders
|
Summary
Test plan