diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index a4b3c383df7b9..7384a2b847866 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -5470,7 +5470,7 @@ def block_task( kind: Optional[str] = None, expected_run_id: Optional[int] = None, ) -> bool: - """Transition ``running``/``ready`` → ``blocked`` (or route elsewhere). + """Transition ``running``/``ready`` → ``blocked``/``waiting``/``todo``/``triage``. ``kind`` (one of :data:`VALID_BLOCK_KINDS`, or ``None`` for a legacy un-typed block) drives routing instead of every block landing in one @@ -5480,22 +5480,22 @@ def block_task( sit in ``blocked`` (where a cron would keep "unblocking" it); it goes to ``todo`` so the existing parent-gating / ``recompute_ready`` machinery promotes it automatically once its parents finish. No human, no cron, no - retry storm. This is Dale's "Type 2 — dependency blocked". - - * ``needs_input`` / ``capability`` / ``None`` — "truly blocked" (Dale's - "Type 1"). Lands in ``blocked`` for a human. BUT: each time such a task - is re-blocked for the SAME kind after having been unblocked, the - unblock-loop counter (``block_recurrences``) increments. When it reaches - :data:`BLOCK_RECURRENCE_LIMIT`, the task is routed to ``triage`` instead - of ``blocked`` — breaking the cron-unblock ↔ worker-re-block loop and - forcing a human-in-the-loop triage decision. - - * ``transient`` — treated like a generic block for routing, but a worker - can use it to signal "this might clear on its own"; it still participates - in the loop breaker so a forever-flaky task eventually escalates. - - Returns True on any successful transition (to ``blocked``, ``todo``, or - ``triage``), False when the task wasn't in a blockable state. + retry storm. + + * ``needs_input`` / ``capability`` — human-time waits. Land in ``waiting`` + (TTL-exempt, never auto-promoted) so the dispatcher never reclaims them. + An explicit ``unwait_task`` / ``unblock_task`` / ``promote_task`` is + required to return them to the work pool. + + * ``transient`` / ``None`` — agent-time blocks. Land in ``blocked`` + (TTL-reclaimable, auto-promotable for circuit-breaker conditions). + A transient failure may clear on retry; the loop breaker routes + the task to ``triage`` after :data:`BLOCK_RECURRENCE_LIMIT` unblock↔ + re-block cycles. + + Returns True on any successful transition (to ``blocked``, ``waiting``, + ``todo``, or ``triage``), False when the task wasn't in a blockable + state. """ if kind is not None and kind not in VALID_BLOCK_KINDS: raise ValueError( diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 95bca3408d074..69e9964ce3ec5 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -14722,6 +14722,7 @@ def cmd_dashboard(args): allow_public=getattr(args, "insecure", False), initial_profile=getattr(args, "open_profile", "") or "", headless=_headless_backend, + isolated=getattr(args, "isolated", False), ssh_session_token=_ssh_session_token, ssh_owner_nonce=_ssh_owner_nonce, ) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 5ae0010ca4727..9e16444d1ea4f 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -11707,6 +11707,7 @@ def _open_session_db_for_profile(profile: Optional[str]): from hermes_state import SessionDB if not profile: return SessionDB() + _check_isolated_profile_access(profile) _name, home = _cron_profile_home(profile) return SessionDB(db_path=Path(home) / "state.db") @@ -15546,6 +15547,7 @@ def _profile_scope(profile: Optional[str]): imported the modules before a HERMES_HOME override, or under test isolation). """ + _check_isolated_profile_access(profile) requested = (profile or "").strip() from hermes_constants import ( @@ -15599,6 +15601,7 @@ def _config_profile_scope(profile: Optional[str]): None/""/"current" means the dashboard's own profile — no override. """ + _check_isolated_profile_access(profile) requested = (profile or "").strip() if not requested or requested.lower() == "current": yield None @@ -15617,6 +15620,29 @@ def _config_profile_scope(profile: Optional[str]): reset_hermes_home_override(token) +def _check_isolated_profile_access(profile: Optional[str]) -> None: + if not getattr(app.state, "isolated", False): + return + requested = (profile or "").strip() + if not requested or requested.lower() == "current": + return + from hermes_constants import get_hermes_home + + isolated_home = Path(str(get_hermes_home())) + try: + requested_dir = _resolve_profile_dir(requested) + except HTTPException: + return + if requested_dir.resolve() != isolated_home.resolve(): + raise HTTPException( + status_code=403, + detail=( + "Isolated mode: profile '" + requested + "' is not accessible. " + "This dashboard is scoped to its own profile only." + ), + ) + + class SkillToggle(BaseModel): name: str enabled: bool @@ -20021,6 +20047,7 @@ def start_server( allow_public: bool = False, initial_profile: str = "", headless: bool = False, + isolated: bool = False, ssh_session_token: Optional[str] = None, ssh_owner_nonce: Optional[str] = None, ): @@ -20055,6 +20082,7 @@ def start_server( # uses this to decide whether to refuse the bind, log the gate-on # banner, and enable uvicorn proxy_headers. app.state.auth_required = should_require_auth(host) + app.state.isolated = isolated # ``--insecure`` no longer disables the auth gate (June 2026 hardening: # the hermes-0day MCP-persistence campaign abused unauthenticated public diff --git a/plugins/model-providers/kimi-oauth/__init__.py b/plugins/model-providers/kimi-oauth/__init__.py new file mode 100644 index 0000000000000..97a99f4736c22 --- /dev/null +++ b/plugins/model-providers/kimi-oauth/__init__.py @@ -0,0 +1,29 @@ +"""Kimi OAuth provider profile — reuse Kimi Code CLI OAuth tokens. + +The Kimi Code CLI stores its OAuth2 tokens at +``~/.kimi-code/credentials/kimi-code.json``. This provider reads those +tokens and exposes them as a Hermes provider, so Kimi subscription users +(K3, K2.7 Coding, etc.) don't need a separate platform API key. +""" + +from providers import register_provider +from providers.base import ProviderProfile + +kimi_oauth = ProviderProfile( + name="kimi-oauth", + aliases=( + "kimi", + "kimi-oauth-code", + "kimi-oauth-cli", + "kimi-code-oauth", + ), + display_name="Kimi Code (OAuth)", + description="Kimi Code via OAuth tokens from Kimi Code CLI — no API key required", + signup_url="https://kimi.moonshot.cn/", + env_vars=(), # OAuth — tokens in ~/.kimi-code/credentials/kimi-code.json, not env + base_url="https://api.kimi.com/coding/v1", + auth_type="oauth_external", + default_max_tokens=65536, +) + +register_provider(kimi_oauth) diff --git a/ui-tui/src/app/interfaces.ts b/ui-tui/src/app/interfaces.ts index 485c0ec28e24b..0bfd02645969f 100644 --- a/ui-tui/src/app/interfaces.ts +++ b/ui-tui/src/app/interfaces.ts @@ -29,7 +29,8 @@ import type { SessionInfo, SlashCatalog, SudoReq, - Usage + Usage, + WelcomeBannerConfig } from '../types.js' export interface StateSetter { diff --git a/ui-tui/src/gatewayTypes.ts b/ui-tui/src/gatewayTypes.ts index 719ffe9938d2b..8413b0ab4af22 100644 --- a/ui-tui/src/gatewayTypes.ts +++ b/ui-tui/src/gatewayTypes.ts @@ -108,6 +108,25 @@ export interface ConfigDisplayConfig { /** Theme mode pin: 'light' / 'dark' beat background auto-detection; 'auto' * (default) trusts the OSC-11 probe + env signals. */ tui_theme?: string + + /** + * Welcome banner section configuration. + * + * Controls which accordion sections appear in the TUI welcome panel, their + * default open/closed state, and custom plugin-provided sections. + * + * Sections omitted from config use their built-in defaults (tools=open, + * skills=closed, system_prompt=closed, mcp_servers=closed). Set + * `enabled: false` to hide a section entirely. + * + * `plugin_sections` renders custom Accordion sections after the built-in + * ones. Data for plugin sections is expected on SessionInfo under the + * matching key (future — currently renders a placeholder label). + */ + welcome_banner?: { + sections?: Record + plugin_sections?: Array<{ id: string; title: string; default_open?: boolean }> + } } export interface ConfigVoiceConfig { diff --git a/ui-tui/src/types.ts b/ui-tui/src/types.ts index 49c4cef4189a5..91ead78d7c322 100644 --- a/ui-tui/src/types.ts +++ b/ui-tui/src/types.ts @@ -228,6 +228,40 @@ export interface SlashCatalog { sub: Record } +/** + * Welcome banner section config as resolved from display.welcome_banner. + * Each built-in section (tools, skills, system_prompt, mcp_servers) can be + * independently enabled/disabled and default-open/closed. + * + * Plugin sections render custom Accordion entries after built-in sections. + */ +export interface WelcomeBannerSectionConfig { + default_open: boolean + enabled: boolean +} + +export interface WelcomeBannerPluginSection { + id: string + title: string + default_open: boolean +} + +export interface WelcomeBannerConfig { + sections: Record + plugin_sections: WelcomeBannerPluginSection[] +} + +/** + * Welcome banner section default defaults for each built-in section. + * These match the hardcoded values in branding.tsx before this feature. + */ +export const WELCOME_BANNER_DEFAULTS: Record = { + tools: { default_open: true, enabled: true }, + skills: { default_open: false, enabled: true }, + system_prompt: { default_open: false, enabled: true }, + mcp_servers: { default_open: false, enabled: true } +} + export interface SlashCategory { name: string pairs: [string, string][] diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 3626529740353..8de6d257847db 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -273,7 +273,7 @@ These are set automatically by the Docker terminal backend when `proxy.enabled: | Variable | Description | |----------|-------------| | `TELEGRAM_BOT_TOKEN` | Telegram bot token (from @BotFather) | -| `TELEGRAM_ALLOWED_USERS` | Comma-separated user IDs allowed to use the bot (applies to DMs, groups, and forums) | +| `TELEGRAM_ALLOWED_USERS` | Comma-separated user IDs allowed to use the bot (applies to DMs, groups, and forums). Prefer `platforms.telegram.extra.allow_from` in `config.yaml`. | | `TELEGRAM_ALLOW_ALL_USERS` | Allow any Telegram user to trigger the bot (dev only). | | `TELEGRAM_GROUP_ALLOWED_USERS` | Comma-separated sender user IDs authorized in groups/forums only (does NOT grant DM access). Chat-ID-shaped values (starting with `-`) are still honored as chat IDs for backward compat with pre-#17686 configs, with a deprecation warning. | | `TELEGRAM_GROUP_ALLOWED_CHATS` | Comma-separated group/forum chat IDs; any member is authorized | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/gateway-internals.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/gateway-internals.md index 63c89d7e80295..7837f8ed940eb 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/gateway-internals.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/gateway-internals.md @@ -92,7 +92,7 @@ agent:main:{platform}:{chat_type}:{chat_id} Gateway 使用多层授权检查,按顺序评估: 1. **平台级全量放行标志**(如 `TELEGRAM_ALLOW_ALL_USERS`)— 若设置,该平台所有用户均被授权 -2. **平台白名单**(如 `TELEGRAM_ALLOWED_USERS`)— 逗号分隔的用户 ID +2. **平台白名单**——来自 `config.yaml`(`platforms..extra.allow_from`)或对应环境变量(如 `TELEGRAM_ALLOWED_USERS`) 3. **DM 配对** — 已认证用户可通过配对码为新用户授权 4. **全局放行标志**(`GATEWAY_ALLOW_ALL_USERS`)— 若设置,所有平台的所有用户均被授权 5. **默认:拒绝** — 未授权用户被拒绝 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/team-telegram-assistant.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/team-telegram-assistant.md index e8b5c4c0ea67d..c5fcf6606620a 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/team-telegram-assistant.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/team-telegram-assistant.md @@ -93,14 +93,22 @@ hermes gateway setup ### 方式 B:手动配置 -在 `~/.hermes/.env` 中添加以下内容: +将你的机器人 token(此为密钥)添加到 `~/.hermes/.env`: ```bash -# Telegram bot token from BotFather -TELEGRAM_BOT_TOKEN=7123456789:AAH1bGciOiJSUzI1NiIsInR5cCI6Ikp... +# 来自 BotFather 的 Telegram 机器人 token(此为密钥,始终放在 .env 中) +TELEGRAM_BOT_TOKEN=7123456789:***... +``` + +将你的用户 ID(非密钥设置)添加到 `~/.hermes/config.yaml`: -# Your Telegram user ID (numeric) -TELEGRAM_ALLOWED_USERS=123456789 +```yaml +gateway: + platforms: + telegram: + extra: + # 你的 Telegram 用户 ID(数字)——多个用户用逗号分隔 + allow_from: "123456789" ``` ### 查找你的用户 ID @@ -109,7 +117,7 @@ TELEGRAM_ALLOWED_USERS=123456789 1. 在 Telegram 上给 [@userinfobot](https://t.me/userinfobot) 发消息 2. 它会立即回复你的数字用户 ID -3. 将该数字填入 `TELEGRAM_ALLOWED_USERS` +3. 将该数字填入 `config.yaml` 中的 `allow_from` 字段 :::info Telegram 用户 ID 是永久性数字,例如 `123456789`。它与可以更改的 `@username` 不同。白名单中请始终使用数字 ID。 @@ -191,19 +199,22 @@ hermes gateway status 现在让你的队友获得访问权限。有两种方式。 -### 方式 A:静态白名单 +### 方式 A:静态白名单(config.yaml) -收集每位团队成员的 Telegram 用户 ID(让他们给 [@userinfobot](https://t.me/userinfobot) 发消息),然后以逗号分隔的列表形式添加: +收集每位团队成员的 Telegram 用户 ID(让他们给 [@userinfobot](https://t.me/userinfobot) 发消息),然后添加到 `~/.hermes/config.yaml`: -```bash -# 在 ~/.hermes/.env 中 -TELEGRAM_ALLOWED_USERS=123456789,987654321,555555555 +```yaml +gateway: + platforms: + telegram: + extra: + allow_from: "123456789,987654321,555555555" ``` 修改后重启 gateway: ```bash -hermes gateway stop && hermes gateway start +hermes gateway restart ``` ### 方式 B:私信配对(推荐用于团队) @@ -260,11 +271,15 @@ hermes pairing clear-pending **方式 1:** 在机器人所在的任意 Telegram 群组或聊天中使用 `/sethome` 命令。 -**方式 2:** 在 `~/.hermes/.env` 中手动设置: +**方式 2:** 在 `~/.hermes/config.yaml` 中手动设置: -```bash -TELEGRAM_HOME_CHANNEL=-1001234567890 -TELEGRAM_HOME_CHANNEL_NAME="Team Updates" +```yaml +gateway: + platforms: + telegram: + home_channel: + chat_id: "-1001234567890" + name: "Team Updates" ``` 要查找频道 ID,可将 [@userinfobot](https://t.me/userinfobot) 添加到群组——它会报告该群组的聊天 ID。 @@ -372,20 +387,11 @@ Cron 任务的 prompt 在完全全新的会话中运行,不保留任何先前 在共享团队机器人上,使用 Docker 作为终端后端,让 agent 命令在容器中运行,而非直接在宿主机上运行: -```bash -# 在 ~/.hermes/.env 中 -TERMINAL_BACKEND=docker -TERMINAL_DOCKER_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20 -``` - -或在 `~/.hermes/config.yaml` 中: - ```yaml +# 在 ~/.hermes/config.yaml 中 terminal: backend: docker - container_cpu: 1 - container_memory: 5120 - container_persistent: true + docker_image: nikolaik/python-nodejs:python3.11-nodejs20 ``` 这样即使有人要求机器人执行破坏性操作,你的宿主系统也受到保护。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/tips.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/tips.md index 140a294ecf860..9c7eb95b9a726 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/tips.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/tips.md @@ -218,15 +218,18 @@ Hermes 在执行每条命令前都会与一份精心维护的危险模式列表 ### 为消息 Bot 使用白名单 -永远不要在拥有终端访问权限的 bot 上设置 `GATEWAY_ALLOW_ALL_USERS=true`。始终使用平台专属白名单(`TELEGRAM_ALLOWED_USERS`、`DISCORD_ALLOWED_USERS`)或 DM 配对来控制谁可以与你的 agent 交互。 - -```bash -# Recommended: explicit allowlists per platform -TELEGRAM_ALLOWED_USERS=123456789,987654321 -DISCORD_ALLOWED_USERS=123456789012345678 - -# Or use cross-platform allowlist -GATEWAY_ALLOWED_USERS=123456789,987654321 +永远不要在拥有终端访问权限的 bot 上设置 `GATEWAY_ALLOW_ALL_USERS=true`。始终使用平台专属白名单或 DM 配对来控制谁可以与你的 agent 交互。 + +```yaml +# 推荐:在 config.yaml 中为每个平台配置显式白名单 +gateway: + platforms: + telegram: + extra: + allow_from: "123456789,987654321" + discord: + extra: + allow_from: "123456789012345678" ``` --- diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md index 696e17e8c41ac..d8b7d208b0702 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md @@ -210,23 +210,33 @@ command_allowlist: ### 平台允许列表 -在 `~/.hermes/.env` 中以逗号分隔的值设置允许的用户 ID: +在 `~/.hermes/config.yaml` 中设置允许的用户 ID,或回退到环境变量: + +```yaml +# config.yaml —— 推荐用于非密钥设置 +gateway: + platforms: + telegram: + extra: + allow_from: "123456789,987654321" # DM 用户允许列表 + group_allow_from: "111222333,444555666" # 仅限群组的用户允许列表 + telegram: + allowed_chats: + - "-1001234567890" # 群组聊天 ID 允许列表(任何成员均可触发) +``` + +环境变量(config.yaml 中未设置时的回退): ```bash -# 平台专属允许列表 -TELEGRAM_ALLOWED_USERS=123456789,987654321 +# 在 ~/.hermes/.env 中 —— 环境变量仍然有效,但 config.yaml 是 +# 非密钥设置的首选。机器人 token 和 API 密钥始终放在 .env 中。 +TELEGRAM_BOT_TOKEN=123456789:ABCdef... # 始终在 .env 中(此为密钥) + +# 跨平台允许列表 +GATEWAY_ALLOWED_USERS=123456789 DISCORD_ALLOWED_USERS=111222333444555666 WHATSAPP_ALLOWED_USERS=15551234567 SLACK_ALLOWED_USERS=U01ABC123 - -# 跨平台允许列表(对所有平台均检查) -GATEWAY_ALLOWED_USERS=123456789 - -# 每平台允许所有用户(谨慎使用) -DISCORD_ALLOW_ALL_USERS=true - -# 全局允许所有用户(极度谨慎使用) -GATEWAY_ALLOW_ALL_USERS=true ``` :::warning @@ -234,8 +244,8 @@ GATEWAY_ALLOW_ALL_USERS=true ``` No user allowlists configured. All unauthorized users will be denied. -Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access, -or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id). +Set GATEWAY_ALLOW_ALL_USERS=true in your config or platform section +to allow open access, or configure platform allowlists. ``` :::