@clawpump/claw-agent 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/.dockerignore +67 -0
- package/agent/.envrc +1 -1
- package/agent/.gitattributes +8 -0
- package/agent/AGENTS.md +216 -4
- package/agent/CONTRIBUTING.md +46 -8
- package/agent/Dockerfile +78 -35
- package/agent/MANIFEST.in +2 -0
- package/agent/README.md +12 -5
- package/agent/README.ur-pk.md +261 -0
- package/agent/README.zh-CN.md +11 -8
- package/agent/SECURITY.md +5 -4
- package/agent/acp_adapter/provenance.py +127 -0
- package/agent/acp_adapter/server.py +112 -5
- package/agent/acp_adapter/session.py +1 -6
- package/agent/acp_registry/agent.json +2 -2
- package/agent/agent/account_usage.py +313 -1
- package/agent/agent/agent_init.py +140 -37
- package/agent/agent/agent_runtime_helpers.py +342 -83
- package/agent/agent/anthropic_adapter.py +320 -33
- package/agent/agent/auxiliary_client.py +525 -105
- package/agent/agent/background_review.py +157 -19
- package/agent/agent/bedrock_adapter.py +71 -6
- package/agent/agent/billing_view.py +295 -0
- package/agent/agent/chat_completion_helpers.py +229 -4
- package/agent/agent/codex_responses_adapter.py +86 -10
- package/agent/agent/codex_runtime.py +153 -1
- package/agent/agent/coding_context.py +738 -0
- package/agent/agent/context_compressor.py +392 -44
- package/agent/agent/context_references.py +34 -1
- package/agent/agent/conversation_compression.py +159 -22
- package/agent/agent/conversation_loop.py +643 -908
- package/agent/agent/copilot_acp_client.py +4 -11
- package/agent/agent/credential_pool.py +5 -3
- package/agent/agent/credits_tracker.py +794 -0
- package/agent/agent/curator.py +91 -18
- package/agent/agent/curator_backup.py +26 -10
- package/agent/agent/display.py +42 -1
- package/agent/agent/error_classifier.py +52 -3
- package/agent/agent/errors.py +3 -0
- package/agent/agent/file_safety.py +0 -17
- package/agent/agent/gemini_native_adapter.py +31 -1
- package/agent/agent/i18n.py +48 -4
- package/agent/agent/image_gen_provider.py +74 -5
- package/agent/agent/image_routing.py +29 -0
- package/agent/agent/insights.py +8 -17
- package/agent/agent/lsp/install.py +3 -0
- package/agent/agent/memory_manager.py +326 -31
- package/agent/agent/message_content.py +50 -0
- package/agent/agent/model_metadata.py +214 -3
- package/agent/agent/moonshot_schema.py +8 -1
- package/agent/agent/onboarding.py +60 -0
- package/agent/agent/prompt_builder.py +327 -37
- package/agent/agent/redact.py +1 -0
- package/agent/agent/runtime_cwd.py +34 -5
- package/agent/agent/secret_scope.py +205 -0
- package/agent/agent/secret_sources/bitwarden.py +34 -2
- package/agent/agent/skill_commands.py +90 -1
- package/agent/agent/skill_preprocessing.py +1 -0
- package/agent/agent/skill_utils.py +209 -36
- package/agent/agent/ssl_guard.py +94 -0
- package/agent/agent/system_prompt.py +133 -5
- package/agent/agent/tool_executor.py +496 -70
- package/agent/agent/transports/anthropic.py +83 -21
- package/agent/agent/transports/chat_completions.py +94 -5
- package/agent/agent/transports/codex.py +67 -2
- package/agent/agent/transports/codex_app_server.py +1 -0
- package/agent/agent/transports/codex_app_server_session.py +30 -0
- package/agent/agent/transports/types.py +12 -0
- package/agent/agent/turn_context.py +408 -0
- package/agent/agent/turn_finalizer.py +428 -0
- package/agent/agent/turn_retry_state.py +68 -0
- package/agent/agent/usage_pricing.py +3 -0
- package/agent/apps/bootstrap-installer/package.json +6 -5
- package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
- package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
- package/agent/apps/bootstrap-installer/src/store.ts +3 -2
- package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
- package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
- package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
- package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
- package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
- package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
- package/agent/apps/desktop/DESIGN.md +167 -0
- package/agent/apps/desktop/README.md +20 -16
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-env.cjs +112 -0
- package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
- package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
- package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
- package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
- package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
- package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
- package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
- package/agent/apps/desktop/electron/connection-config.cjs +288 -0
- package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
- package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
- package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
- package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
- package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
- package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
- package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
- package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
- package/agent/apps/desktop/electron/git-root.cjs +54 -0
- package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
- package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
- package/agent/apps/desktop/electron/hardening.cjs +123 -28
- package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
- package/agent/apps/desktop/electron/main.cjs +3121 -331
- package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
- package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
- package/agent/apps/desktop/electron/preload.cjs +52 -2
- package/agent/apps/desktop/electron/session-windows.cjs +124 -0
- package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
- package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
- package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
- package/agent/apps/desktop/electron/update-remote.cjs +56 -0
- package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
- package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
- package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
- package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
- package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
- package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
- package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
- package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
- package/agent/apps/desktop/eslint.config.mjs +0 -3
- package/agent/apps/desktop/index.html +27 -2
- package/agent/apps/desktop/package.json +31 -11
- package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/nous-girl.jpg +0 -0
- package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
- package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
- package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
- package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
- package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
- package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
- package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
- package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
- package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
- package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
- package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
- package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
- package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
- package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
- package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
- package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
- package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
- package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
- package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
- package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
- package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
- package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
- package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
- package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
- package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
- package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
- package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
- package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
- package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
- package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
- package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
- package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
- package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
- package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
- package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
- package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
- package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
- package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
- package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
- package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
- package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
- package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
- package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
- package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
- package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
- package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
- package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
- package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
- package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
- package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
- package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
- package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
- package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
- package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
- package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
- package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
- package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
- package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
- package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
- package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
- package/agent/apps/desktop/src/app/routes.ts +9 -0
- package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
- package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
- package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
- package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
- package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
- package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
- package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
- package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
- package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
- package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
- package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
- package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
- package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
- package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
- package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
- package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
- package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
- package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
- package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
- package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
- package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
- package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
- package/agent/apps/desktop/src/app/settings/types.ts +9 -6
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
- package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
- package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
- package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
- package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
- package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
- package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
- package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
- package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
- package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
- package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
- package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
- package/agent/apps/desktop/src/app/types.ts +85 -0
- package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
- package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
- package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
- package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
- package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
- package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
- package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
- package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
- package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
- package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
- package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
- package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
- package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
- package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
- package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
- package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
- package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
- package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
- package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
- package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
- package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
- package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
- package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
- package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
- package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
- package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
- package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
- package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
- package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
- package/agent/apps/desktop/src/components/notifications.tsx +48 -27
- package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
- package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
- package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
- package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
- package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
- package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
- package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
- package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
- package/agent/apps/desktop/src/components/ui/control.ts +25 -0
- package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
- package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
- package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
- package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
- package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
- package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
- package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
- package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
- package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
- package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
- package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
- package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
- package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
- package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
- package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
- package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
- package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
- package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
- package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
- package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
- package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/agent/apps/desktop/src/global.d.ts +181 -4
- package/agent/apps/desktop/src/hermes.test.ts +60 -0
- package/agent/apps/desktop/src/hermes.ts +190 -13
- package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
- package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
- package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
- package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
- package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
- package/agent/apps/desktop/src/i18n/context.tsx +183 -0
- package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
- package/agent/apps/desktop/src/i18n/en.ts +1921 -0
- package/agent/apps/desktop/src/i18n/index.ts +20 -0
- package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
- package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
- package/agent/apps/desktop/src/i18n/languages.ts +86 -0
- package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
- package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
- package/agent/apps/desktop/src/i18n/types.ts +1559 -0
- package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
- package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
- package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
- package/agent/apps/desktop/src/lib/ansi.ts +186 -0
- package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
- package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
- package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
- package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
- package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
- package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
- package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
- package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
- package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
- package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
- package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
- package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
- package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
- package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
- package/agent/apps/desktop/src/lib/haptics.ts +17 -0
- package/agent/apps/desktop/src/lib/icons.ts +10 -2
- package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
- package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
- package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
- package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
- package/agent/apps/desktop/src/lib/media.ts +40 -1
- package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
- package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
- package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
- package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
- package/agent/apps/desktop/src/lib/query-client.ts +13 -0
- package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
- package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
- package/agent/apps/desktop/src/lib/session-export.ts +6 -3
- package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
- package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
- package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
- package/agent/apps/desktop/src/lib/session-search.ts +21 -0
- package/agent/apps/desktop/src/lib/session-source.ts +126 -0
- package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
- package/agent/apps/desktop/src/lib/storage.ts +35 -1
- package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
- package/agent/apps/desktop/src/lib/todos.ts +37 -0
- package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
- package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
- package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
- package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
- package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
- package/agent/apps/desktop/src/main.tsx +19 -19
- package/agent/apps/desktop/src/store/boot.ts +4 -3
- package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
- package/agent/apps/desktop/src/store/clarify.ts +50 -13
- package/agent/apps/desktop/src/store/command-palette.ts +20 -0
- package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
- package/agent/apps/desktop/src/store/compaction.ts +38 -0
- package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
- package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
- package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
- package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
- package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
- package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
- package/agent/apps/desktop/src/store/composer-status.ts +277 -0
- package/agent/apps/desktop/src/store/composer.test.ts +106 -0
- package/agent/apps/desktop/src/store/composer.ts +116 -0
- package/agent/apps/desktop/src/store/cron.ts +19 -0
- package/agent/apps/desktop/src/store/gateway.ts +280 -6
- package/agent/apps/desktop/src/store/keybinds.ts +143 -0
- package/agent/apps/desktop/src/store/layout.ts +107 -9
- package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
- package/agent/apps/desktop/src/store/model-presets.ts +86 -0
- package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
- package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
- package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
- package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
- package/agent/apps/desktop/src/store/notifications.ts +10 -7
- package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
- package/agent/apps/desktop/src/store/onboarding.ts +268 -38
- package/agent/apps/desktop/src/store/preview.ts +10 -1
- package/agent/apps/desktop/src/store/profile.test.ts +89 -0
- package/agent/apps/desktop/src/store/profile.ts +395 -0
- package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
- package/agent/apps/desktop/src/store/prompts.ts +117 -0
- package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
- package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
- package/agent/apps/desktop/src/store/session-sync.ts +25 -0
- package/agent/apps/desktop/src/store/session.test.ts +268 -2
- package/agent/apps/desktop/src/store/session.ts +392 -18
- package/agent/apps/desktop/src/store/subagents.ts +3 -0
- package/agent/apps/desktop/src/store/system-actions.ts +48 -0
- package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
- package/agent/apps/desktop/src/store/todos.test.ts +47 -0
- package/agent/apps/desktop/src/store/todos.ts +64 -0
- package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
- package/agent/apps/desktop/src/store/translucency.ts +38 -0
- package/agent/apps/desktop/src/store/updates.test.ts +187 -2
- package/agent/apps/desktop/src/store/updates.ts +268 -18
- package/agent/apps/desktop/src/store/windows.test.ts +143 -0
- package/agent/apps/desktop/src/store/windows.ts +115 -0
- package/agent/apps/desktop/src/styles.css +510 -119
- package/agent/apps/desktop/src/themes/color.ts +142 -0
- package/agent/apps/desktop/src/themes/context.tsx +128 -75
- package/agent/apps/desktop/src/themes/install.test.ts +119 -0
- package/agent/apps/desktop/src/themes/install.ts +95 -0
- package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
- package/agent/apps/desktop/src/themes/presets.ts +13 -4
- package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
- package/agent/apps/desktop/src/themes/types.ts +35 -0
- package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
- package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
- package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
- package/agent/apps/desktop/src/themes/vscode.ts +343 -0
- package/agent/apps/desktop/src/types/hermes.ts +138 -1
- package/agent/apps/desktop/tsconfig.json +2 -2
- package/agent/apps/desktop/vite.config.ts +18 -0
- package/agent/apps/shared/package.json +1 -1
- package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
- package/agent/apps/shared/tsconfig.json +2 -2
- package/agent/cli-config.yaml.example +78 -1
- package/agent/cli.py +2294 -3146
- package/agent/cron/blueprint_catalog.py +713 -0
- package/agent/cron/jobs.py +226 -110
- package/agent/cron/scheduler.py +468 -193
- package/agent/cron/scheduler_provider.py +177 -0
- package/agent/cron/scripts/__init__.py +1 -0
- package/agent/cron/scripts/classify_items.py +226 -0
- package/agent/cron/suggestion_catalog.py +154 -0
- package/agent/cron/suggestions.py +257 -0
- package/agent/docs/chronos-managed-cron-contract.md +196 -0
- package/agent/docs/design/profile-builder.md +146 -0
- package/agent/docs/middleware/README.md +260 -0
- package/agent/docs/observability/README.md +316 -0
- package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
- package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
- package/agent/docs/relay-connector-contract.md +285 -0
- package/agent/gateway/authz_mixin.py +536 -0
- package/agent/gateway/channel_directory.py +65 -3
- package/agent/gateway/config.py +222 -12
- package/agent/gateway/display_config.py +10 -0
- package/agent/gateway/hooks.py +17 -0
- package/agent/gateway/kanban_watchers.py +1146 -0
- package/agent/gateway/message_timestamps.py +166 -0
- package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
- package/agent/gateway/platforms/api_server.py +216 -38
- package/agent/gateway/platforms/base.py +210 -58
- package/agent/gateway/platforms/email.py +122 -12
- package/agent/gateway/platforms/feishu.py +80 -11
- package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
- package/agent/gateway/platforms/matrix.py +1498 -297
- package/agent/gateway/platforms/qqbot/adapter.py +6 -0
- package/agent/gateway/platforms/signal.py +8 -0
- package/agent/gateway/platforms/slack.py +308 -12
- package/agent/gateway/platforms/telegram.py +831 -24
- package/agent/gateway/platforms/webhook.py +109 -21
- package/agent/gateway/platforms/weixin.py +113 -2
- package/agent/gateway/platforms/whatsapp.py +94 -288
- package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
- package/agent/gateway/platforms/whatsapp_common.py +367 -0
- package/agent/gateway/platforms/yuanbao.py +608 -191
- package/agent/gateway/platforms/yuanbao_proto.py +232 -23
- package/agent/gateway/relay/__init__.py +375 -0
- package/agent/gateway/relay/adapter.py +222 -0
- package/agent/gateway/relay/auth.py +168 -0
- package/agent/gateway/relay/descriptor.py +118 -0
- package/agent/gateway/relay/transport.py +101 -0
- package/agent/gateway/relay/ws_transport.py +327 -0
- package/agent/gateway/response_filters.py +53 -0
- package/agent/gateway/rich_sent_store.py +80 -0
- package/agent/gateway/run.py +2940 -5001
- package/agent/gateway/session.py +109 -8
- package/agent/gateway/session_context.py +22 -4
- package/agent/gateway/slash_commands.py +3854 -0
- package/agent/gateway/status.py +141 -21
- package/agent/gateway/stream_consumer.py +288 -31
- package/agent/hermes-already-has-routines.md +1 -1
- package/agent/hermes_cli/__init__.py +62 -17
- package/agent/hermes_cli/_parser.py +30 -0
- package/agent/hermes_cli/_subprocess_compat.py +61 -0
- package/agent/hermes_cli/active_sessions.py +320 -0
- package/agent/hermes_cli/auth.py +707 -59
- package/agent/hermes_cli/auth_commands.py +39 -22
- package/agent/hermes_cli/backup.py +109 -7
- package/agent/hermes_cli/banner.py +88 -0
- package/agent/hermes_cli/blueprint_cmd.py +318 -0
- package/agent/hermes_cli/clawpump_cli.py +3 -3
- package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +216 -91
- package/agent/hermes_cli/config.py +967 -130
- package/agent/hermes_cli/container_boot.py +76 -11
- package/agent/hermes_cli/cron.py +5 -11
- package/agent/hermes_cli/curator.py +21 -0
- package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
- package/agent/hermes_cli/dashboard_auth/base.py +62 -0
- package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
- package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
- package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
- package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
- package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
- package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
- package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
- package/agent/hermes_cli/dashboard_register.py +427 -0
- package/agent/hermes_cli/debug.py +155 -50
- package/agent/hermes_cli/distribution.py +227 -0
- package/agent/hermes_cli/doctor.py +255 -14
- package/agent/hermes_cli/dump.py +60 -6
- package/agent/hermes_cli/env_loader.py +33 -0
- package/agent/hermes_cli/gateway.py +755 -103
- package/agent/hermes_cli/gateway_enroll.py +250 -0
- package/agent/hermes_cli/gateway_windows.py +254 -11
- package/agent/hermes_cli/gui_uninstall.py +285 -0
- package/agent/hermes_cli/inventory.py +105 -4
- package/agent/hermes_cli/kanban.py +58 -71
- package/agent/hermes_cli/kanban_db.py +391 -14
- package/agent/hermes_cli/kanban_decompose.py +2 -2
- package/agent/hermes_cli/kanban_specify.py +3 -1
- package/agent/hermes_cli/logs.py +2 -0
- package/agent/hermes_cli/main.py +2889 -5287
- package/agent/hermes_cli/managed_scope.py +214 -0
- package/agent/hermes_cli/managed_uv.py +254 -0
- package/agent/hermes_cli/mcp_catalog.py +6 -3
- package/agent/hermes_cli/mcp_config.py +145 -21
- package/agent/hermes_cli/mcp_security.py +96 -0
- package/agent/hermes_cli/mcp_startup.py +32 -3
- package/agent/hermes_cli/memory_providers.py +149 -0
- package/agent/hermes_cli/memory_setup.py +97 -42
- package/agent/hermes_cli/middleware.py +313 -0
- package/agent/hermes_cli/model_catalog.py +31 -0
- package/agent/hermes_cli/model_cost_guard.py +134 -0
- package/agent/hermes_cli/model_normalize.py +2 -1
- package/agent/hermes_cli/model_setup_flows.py +2759 -0
- package/agent/hermes_cli/model_switch.py +242 -27
- package/agent/hermes_cli/models.py +284 -44
- package/agent/hermes_cli/nous_account.py +33 -6
- package/agent/hermes_cli/nous_billing.py +406 -0
- package/agent/hermes_cli/nous_subscription.py +202 -5
- package/agent/hermes_cli/platforms.py +1 -0
- package/agent/hermes_cli/plugins.py +218 -18
- package/agent/hermes_cli/plugins_cmd.py +249 -105
- package/agent/hermes_cli/portal_cli.py +56 -16
- package/agent/hermes_cli/profile_distribution.py +6 -1
- package/agent/hermes_cli/profiles.py +283 -32
- package/agent/hermes_cli/provider_catalog.py +170 -0
- package/agent/hermes_cli/providers.py +4 -1
- package/agent/hermes_cli/pty_bridge.py +53 -4
- package/agent/hermes_cli/runtime_provider.py +216 -34
- package/agent/hermes_cli/secret_prompt.py +4 -4
- package/agent/hermes_cli/secrets_cli.py +24 -0
- package/agent/hermes_cli/send_cmd.py +28 -2
- package/agent/hermes_cli/service_manager.py +166 -19
- package/agent/hermes_cli/session_listing.py +97 -0
- package/agent/hermes_cli/setup.py +158 -94
- package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
- package/agent/hermes_cli/skills_config.py +8 -2
- package/agent/hermes_cli/skills_hub.py +149 -7
- package/agent/hermes_cli/status.py +2 -2
- package/agent/hermes_cli/subcommands/__init__.py +18 -0
- package/agent/hermes_cli/subcommands/_shared.py +29 -0
- package/agent/hermes_cli/subcommands/acp.py +52 -0
- package/agent/hermes_cli/subcommands/auth.py +109 -0
- package/agent/hermes_cli/subcommands/backup.py +38 -0
- package/agent/hermes_cli/subcommands/claw.py +92 -0
- package/agent/hermes_cli/subcommands/config.py +49 -0
- package/agent/hermes_cli/subcommands/cron.py +163 -0
- package/agent/hermes_cli/subcommands/dashboard.py +143 -0
- package/agent/hermes_cli/subcommands/debug.py +77 -0
- package/agent/hermes_cli/subcommands/doctor.py +35 -0
- package/agent/hermes_cli/subcommands/dump.py +28 -0
- package/agent/hermes_cli/subcommands/gateway.py +332 -0
- package/agent/hermes_cli/subcommands/gui.py +63 -0
- package/agent/hermes_cli/subcommands/hooks.py +77 -0
- package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
- package/agent/hermes_cli/subcommands/insights.py +25 -0
- package/agent/hermes_cli/subcommands/login.py +78 -0
- package/agent/hermes_cli/subcommands/logout.py +28 -0
- package/agent/hermes_cli/subcommands/logs.py +78 -0
- package/agent/hermes_cli/subcommands/mcp.py +108 -0
- package/agent/hermes_cli/subcommands/memory.py +53 -0
- package/agent/hermes_cli/subcommands/model.py +72 -0
- package/agent/hermes_cli/subcommands/pairing.py +36 -0
- package/agent/hermes_cli/subcommands/plugins.py +94 -0
- package/agent/hermes_cli/subcommands/postinstall.py +23 -0
- package/agent/hermes_cli/subcommands/profile.py +203 -0
- package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
- package/agent/hermes_cli/subcommands/security.py +62 -0
- package/agent/hermes_cli/subcommands/setup.py +58 -0
- package/agent/hermes_cli/subcommands/skills.py +298 -0
- package/agent/hermes_cli/subcommands/slack.py +60 -0
- package/agent/hermes_cli/subcommands/status.py +28 -0
- package/agent/hermes_cli/subcommands/tools.py +95 -0
- package/agent/hermes_cli/subcommands/uninstall.py +41 -0
- package/agent/hermes_cli/subcommands/update.py +70 -0
- package/agent/hermes_cli/subcommands/version.py +18 -0
- package/agent/hermes_cli/subcommands/webhook.py +76 -0
- package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
- package/agent/hermes_cli/suggestions_cmd.py +153 -0
- package/agent/hermes_cli/telegram_managed_bot.py +358 -0
- package/agent/hermes_cli/tips.py +3 -4
- package/agent/hermes_cli/tools_config.py +155 -28
- package/agent/hermes_cli/uninstall.py +231 -35
- package/agent/hermes_cli/web_server.py +6188 -975
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +15 -5
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +14 -4
- package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
- package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
- package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
- package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
- package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
- package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
- package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
- package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
- package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
- package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
- package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
- package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
- package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
- package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
- package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
- package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
- package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
- package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
- package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
- package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
- package/agent/optional-skills/security/1password/SKILL.md +1 -1
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
- package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
- package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
- package/agent/package-lock.json +4082 -7907
- package/agent/package.json +18 -3
- package/agent/plugins/browser/firecrawl/provider.py +4 -1
- package/agent/plugins/cron/__init__.py +344 -0
- package/agent/plugins/cron/chronos/__init__.py +241 -0
- package/agent/plugins/cron/chronos/_nas_client.py +123 -0
- package/agent/plugins/cron/chronos/plugin.yaml +9 -0
- package/agent/plugins/cron/chronos/verify.py +103 -0
- package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
- package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
- package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
- package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
- package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
- package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
- package/agent/plugins/google_meet/audio_bridge.py +4 -0
- package/agent/plugins/google_meet/meet_bot.py +7 -1
- package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
- package/agent/plugins/image_gen/fal/__init__.py +35 -6
- package/agent/plugins/image_gen/krea/__init__.py +56 -13
- package/agent/plugins/image_gen/openai/__init__.py +122 -24
- package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
- package/agent/plugins/image_gen/xai/__init__.py +92 -12
- package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
- package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
- package/agent/plugins/memory/__init__.py +48 -5
- package/agent/plugins/memory/byterover/__init__.py +1 -0
- package/agent/plugins/memory/hindsight/README.md +1 -1
- package/agent/plugins/memory/hindsight/__init__.py +138 -24
- package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
- package/agent/plugins/memory/honcho/README.md +13 -10
- package/agent/plugins/memory/honcho/cli.py +247 -122
- package/agent/plugins/memory/honcho/client.py +112 -102
- package/agent/plugins/memory/openviking/README.md +12 -1
- package/agent/plugins/memory/openviking/__init__.py +2281 -107
- package/agent/plugins/memory/openviking/plugin.yaml +1 -2
- package/agent/plugins/memory/supermemory/README.md +22 -10
- package/agent/plugins/memory/supermemory/__init__.py +142 -37
- package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
- package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
- package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
- package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
- package/agent/plugins/model-providers/custom/__init__.py +8 -2
- package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
- package/agent/plugins/model-providers/minimax/__init__.py +60 -8
- package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
- package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
- package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
- package/agent/plugins/model-providers/zai/__init__.py +1 -0
- package/agent/plugins/observability/langfuse/__init__.py +147 -14
- package/agent/plugins/observability/nemo_relay/README.md +559 -0
- package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
- package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
- package/agent/plugins/platforms/discord/adapter.py +932 -61
- package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
- package/agent/plugins/platforms/google_chat/adapter.py +9 -3
- package/agent/plugins/platforms/google_chat/oauth.py +1 -1
- package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
- package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
- package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
- package/agent/plugins/platforms/irc/adapter.py +4 -1
- package/agent/plugins/platforms/line/adapter.py +16 -1
- package/agent/plugins/platforms/mattermost/adapter.py +100 -24
- package/agent/plugins/platforms/photon/README.md +179 -0
- package/agent/plugins/platforms/photon/__init__.py +4 -0
- package/agent/plugins/platforms/photon/adapter.py +1586 -0
- package/agent/plugins/platforms/photon/auth.py +1046 -0
- package/agent/plugins/platforms/photon/cli.py +439 -0
- package/agent/plugins/platforms/photon/plugin.yaml +88 -0
- package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
- package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
- package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
- package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
- package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
- package/agent/plugins/platforms/raft/__init__.py +3 -0
- package/agent/plugins/platforms/raft/adapter.py +774 -0
- package/agent/plugins/platforms/raft/plugin.yaml +19 -0
- package/agent/plugins/platforms/simplex/adapter.py +777 -220
- package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
- package/agent/plugins/platforms/teams/adapter.py +175 -5
- package/agent/plugins/plugin_utils.py +135 -0
- package/agent/plugins/video_gen/fal/__init__.py +10 -3
- package/agent/plugins/web/searxng/provider.py +15 -2
- package/agent/plugins/web/xai/provider.py +2 -2
- package/agent/providers/base.py +22 -3
- package/agent/pyproject.toml +115 -21
- package/agent/run_agent.py +733 -39
- package/agent/scripts/build_skills_index.py +51 -19
- package/agent/scripts/check_subprocess_stdin.py +177 -0
- package/agent/scripts/contributor_audit.py +2 -0
- package/agent/scripts/docker_config_migrate.py +67 -0
- package/agent/scripts/install.cmd +3 -3
- package/agent/scripts/install.ps1 +580 -154
- package/agent/scripts/install.sh +402 -185
- package/agent/scripts/lib/node-bootstrap.sh +39 -4
- package/agent/scripts/release.py +183 -0
- package/agent/scripts/run_tests.sh +1 -0
- package/agent/scripts/run_tests_parallel.py +18 -23
- package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
- package/agent/setup.py +59 -0
- package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
- package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
- package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
- package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
- package/agent/skills/clawpump/SKILL.md +53 -5
- package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
- package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
- package/agent/skills/github/github-auth/SKILL.md +2 -2
- package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
- package/agent/skills/github/github-code-review/SKILL.md +2 -2
- package/agent/skills/github/github-issues/SKILL.md +2 -2
- package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
- package/agent/skills/github/github-repo-management/SKILL.md +2 -2
- package/agent/skills/media/gif-search/SKILL.md +1 -1
- package/agent/skills/media/youtube-content/SKILL.md +10 -7
- package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
- package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
- package/agent/skills/productivity/airtable/SKILL.md +2 -2
- package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
- package/agent/skills/productivity/notion/SKILL.md +2 -2
- package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
- package/agent/skills/research/llm-wiki/SKILL.md +1 -1
- package/agent/skills/social-media/xurl/SKILL.md +9 -0
- package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
- package/agent/skills/software-development/plan/SKILL.md +285 -5
- package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
- package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
- package/agent/skills/software-development/spike/SKILL.md +2 -2
- package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
- package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
- package/agent/tools/approval.py +302 -4
- package/agent/tools/async_delegation.py +386 -0
- package/agent/tools/blueprints.py +325 -0
- package/agent/tools/browser_cdp_tool.py +3 -3
- package/agent/tools/browser_tool.py +34 -6
- package/agent/tools/checkpoint_manager.py +31 -1
- package/agent/tools/clarify_tool.py +55 -5
- package/agent/tools/code_execution_tool.py +31 -14
- package/agent/tools/computer_use/cua_backend.py +81 -3
- package/agent/tools/computer_use/tool.py +79 -5
- package/agent/tools/computer_use/vision_routing.py +55 -3
- package/agent/tools/credential_files.py +31 -12
- package/agent/tools/cronjob_tools.py +30 -20
- package/agent/tools/delegate_tool.py +356 -31
- package/agent/tools/env_probe.py +1 -0
- package/agent/tools/environments/docker.py +163 -8
- package/agent/tools/environments/file_sync.py +2 -1
- package/agent/tools/environments/local.py +74 -23
- package/agent/tools/environments/singularity.py +4 -1
- package/agent/tools/environments/ssh.py +78 -11
- package/agent/tools/file_operations.py +277 -41
- package/agent/tools/file_tools.py +166 -28
- package/agent/tools/image_generation_tool.py +515 -29
- package/agent/tools/kanban_tools.py +99 -0
- package/agent/tools/lazy_deps.py +33 -2
- package/agent/tools/mcp_oauth.py +5 -5
- package/agent/tools/mcp_oauth_manager.py +7 -5
- package/agent/tools/mcp_tool.py +840 -33
- package/agent/tools/memory_tool.py +335 -38
- package/agent/tools/osv_check.py +15 -1
- package/agent/tools/process_registry.py +155 -11
- package/agent/tools/read_extract.py +248 -0
- package/agent/tools/read_terminal_tool.py +93 -0
- package/agent/tools/schema_sanitizer.py +38 -0
- package/agent/tools/send_message_tool.py +163 -49
- package/agent/tools/session_search_tool.py +189 -7
- package/agent/tools/skill_manager_tool.py +202 -3
- package/agent/tools/skill_usage.py +52 -4
- package/agent/tools/skills_hub.py +184 -44
- package/agent/tools/skills_sync.py +232 -5
- package/agent/tools/skills_tool.py +125 -11
- package/agent/tools/terminal_tool.py +148 -26
- package/agent/tools/tirith_security.py +2 -0
- package/agent/tools/todo_tool.py +32 -1
- package/agent/tools/transcription_tools.py +13 -5
- package/agent/tools/tts_tool.py +332 -38
- package/agent/tools/url_safety.py +52 -1
- package/agent/tools/vision_tools.py +124 -39
- package/agent/tools/voice_mode.py +4 -3
- package/agent/tools/web_tools.py +45 -15
- package/agent/tools/write_approval.py +493 -0
- package/agent/toolsets.py +34 -10
- package/agent/trajectory_compressor.py +81 -10
- package/agent/tui_gateway/entry.py +43 -6
- package/agent/tui_gateway/server.py +3335 -330
- package/agent/tui_gateway/slash_worker.py +61 -0
- package/agent/tui_gateway/ws.py +67 -9
- package/agent/ui-tui/eslint.config.mjs +0 -4
- package/agent/ui-tui/package.json +6 -6
- package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
- package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
- package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
- package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
- package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
- package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
- package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
- package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
- package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
- package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
- package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
- package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
- package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
- package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
- package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
- package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
- package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
- package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
- package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
- package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
- package/agent/ui-tui/src/app/interfaces.ts +64 -1
- package/agent/ui-tui/src/app/overlayStore.ts +18 -2
- package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
- package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
- package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
- package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
- package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
- package/agent/ui-tui/src/app/slash/registry.ts +4 -0
- package/agent/ui-tui/src/app/turnController.ts +145 -2
- package/agent/ui-tui/src/app/uiStore.ts +2 -0
- package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
- package/agent/ui-tui/src/app/useMainApp.ts +54 -8
- package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
- package/agent/ui-tui/src/app/useSubmission.ts +23 -31
- package/agent/ui-tui/src/components/appChrome.tsx +112 -5
- package/agent/ui-tui/src/components/appLayout.tsx +9 -0
- package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
- package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
- package/agent/ui-tui/src/components/branding.tsx +15 -3
- package/agent/ui-tui/src/components/messageLine.tsx +25 -3
- package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
- package/agent/ui-tui/src/components/prompts.tsx +31 -17
- package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
- package/agent/ui-tui/src/components/textInput.tsx +16 -0
- package/agent/ui-tui/src/config/env.ts +12 -0
- package/agent/ui-tui/src/config/limits.ts +13 -0
- package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
- package/agent/ui-tui/src/domain/paths.ts +24 -0
- package/agent/ui-tui/src/domain/slash.ts +40 -0
- package/agent/ui-tui/src/entry.tsx +35 -4
- package/agent/ui-tui/src/gatewayClient.ts +22 -10
- package/agent/ui-tui/src/gatewayTypes.ts +130 -1
- package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
- package/agent/ui-tui/src/lib/memory.test.ts +162 -0
- package/agent/ui-tui/src/lib/memory.ts +60 -1
- package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
- package/agent/ui-tui/src/lib/osc52.ts +1 -1
- package/agent/ui-tui/src/lib/text.test.ts +32 -1
- package/agent/ui-tui/src/lib/text.ts +29 -2
- package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
- package/agent/ui-tui/src/types.ts +5 -0
- package/agent/ui-tui/tsconfig.build.json +0 -1
- package/agent/ui-tui/tsconfig.json +2 -1
- package/agent/utils.py +66 -2
- package/agent/uv.lock +308 -696
- package/agent/web/index.html +2 -2
- package/agent/web/package.json +11 -6
- package/agent/web/public/claw-bg.webp +0 -0
- package/agent/web/public/claw-logo.webp +0 -0
- package/agent/web/src/App.tsx +138 -48
- package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
- package/agent/web/src/components/Backdrop.tsx +15 -0
- package/agent/web/src/components/ChatSessionList.tsx +260 -0
- package/agent/web/src/components/ChatSidebar.tsx +262 -78
- package/agent/web/src/components/ConfirmDialog.tsx +122 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
- package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
- package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
- package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
- package/agent/web/src/components/ReasoningPicker.tsx +167 -0
- package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
- package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
- package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
- package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
- package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
- package/agent/web/src/contexts/SystemActions.tsx +6 -8
- package/agent/web/src/contexts/profile-context.ts +19 -0
- package/agent/web/src/contexts/useProfileScope.ts +6 -0
- package/agent/web/src/i18n/af.ts +5 -4
- package/agent/web/src/i18n/de.ts +5 -4
- package/agent/web/src/i18n/en.ts +58 -4
- package/agent/web/src/i18n/es.ts +5 -3
- package/agent/web/src/i18n/fr.ts +5 -3
- package/agent/web/src/i18n/ga.ts +5 -4
- package/agent/web/src/i18n/hu.ts +5 -4
- package/agent/web/src/i18n/it.ts +5 -4
- package/agent/web/src/i18n/ja.ts +5 -4
- package/agent/web/src/i18n/ko.ts +5 -4
- package/agent/web/src/i18n/pt.ts +5 -3
- package/agent/web/src/i18n/ru.ts +5 -4
- package/agent/web/src/i18n/tr.ts +5 -4
- package/agent/web/src/i18n/types.ts +59 -1
- package/agent/web/src/i18n/uk.ts +5 -3
- package/agent/web/src/i18n/zh-hant.ts +5 -4
- package/agent/web/src/i18n/zh.ts +5 -4
- package/agent/web/src/index.css +2 -2
- package/agent/web/src/lib/api.ts +819 -52
- package/agent/web/src/lib/dashboard-flags.ts +16 -7
- package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
- package/agent/web/src/lib/reasoning-effort.ts +36 -0
- package/agent/web/src/lib/session-refresh.test.ts +21 -0
- package/agent/web/src/lib/session-refresh.ts +26 -0
- package/agent/web/src/pages/ChannelsPage.tsx +529 -68
- package/agent/web/src/pages/ChatPage.tsx +249 -56
- package/agent/web/src/pages/ConfigPage.tsx +11 -1
- package/agent/web/src/pages/CronPage.tsx +219 -31
- package/agent/web/src/pages/EnvPage.tsx +25 -6
- package/agent/web/src/pages/FilesPage.tsx +525 -0
- package/agent/web/src/pages/McpPage.tsx +80 -3
- package/agent/web/src/pages/ModelsPage.tsx +97 -12
- package/agent/web/src/pages/PluginsPage.tsx +1 -1
- package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
- package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
- package/agent/web/src/pages/SessionsPage.tsx +144 -13
- package/agent/web/src/pages/SkillsPage.tsx +851 -70
- package/agent/web/src/pages/SystemPage.tsx +340 -4
- package/agent/web/src/pages/WalletPage.tsx +401 -0
- package/agent/web/src/pages/WebhooksPage.tsx +145 -15
- package/agent/web/src/pages/X402Page.tsx +207 -0
- package/agent/web/src/plugins/registry.ts +28 -11
- package/agent/web/src/plugins/sdk.d.ts +160 -0
- package/agent/web/src/themes/context.tsx +112 -5
- package/agent/web/src/themes/fonts.ts +167 -0
- package/agent/web/src/themes/index.ts +7 -0
- package/agent/web/tsconfig.app.json +0 -1
- package/agent/web/vite.config.ts +1 -8
- package/agent/web/vitest.config.ts +16 -0
- package/package.json +1 -1
- package/agent/apps/desktop/package-lock.json +0 -18363
- package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
- package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
- package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
- package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
- package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
- package/agent/skills/diagramming/DESCRIPTION.md +0 -3
- package/agent/skills/domain/DESCRIPTION.md +0 -24
- package/agent/skills/gifs/DESCRIPTION.md +0 -3
- package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
- package/agent/skills/mcp/DESCRIPTION.md +0 -3
- package/agent/skills/media/spotify/SKILL.md +0 -135
- package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
- package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
- package/agent/skills/productivity/linear/SKILL.md +0 -380
- package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
- package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
- package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
- package/agent/ui-tui/package-lock.json +0 -7449
- package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
- package/agent/web/package-lock.json +0 -8887
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
|
@@ -13,15 +13,18 @@ This module provides:
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import copy
|
|
16
|
+
import json
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
18
19
|
import platform
|
|
19
20
|
import re
|
|
21
|
+
import shutil
|
|
20
22
|
import stat
|
|
21
23
|
import subprocess
|
|
22
24
|
import sys
|
|
23
25
|
import tempfile
|
|
24
26
|
import threading
|
|
27
|
+
import time
|
|
25
28
|
from dataclasses import dataclass
|
|
26
29
|
from pathlib import Path
|
|
27
30
|
from typing import Dict, Any, Optional, List, Tuple
|
|
@@ -36,6 +39,60 @@ logger = logging.getLogger(__name__)
|
|
|
36
39
|
_CONFIG_PARSE_WARNED: set = set()
|
|
37
40
|
|
|
38
41
|
|
|
42
|
+
def _backup_corrupt_config(config_path: Path) -> Optional[Path]:
|
|
43
|
+
"""Preserve a corrupted ``config.yaml`` by copying it to a timestamped ``.bak``.
|
|
44
|
+
|
|
45
|
+
When the YAML can't be parsed, ``load_config()`` silently falls back to
|
|
46
|
+
``DEFAULT_CONFIG`` and the user's broken file stays on disk untouched.
|
|
47
|
+
That file is still the user's only copy of their intended overrides — if
|
|
48
|
+
they re-run the setup wizard or ``hermes config set`` (which rewrites
|
|
49
|
+
``config.yaml``), the broken-but-recoverable content is gone for good.
|
|
50
|
+
|
|
51
|
+
This snapshots the corrupted file to ``config.yaml.corrupt.<ts>.bak`` so
|
|
52
|
+
the user can diff/repair it. Unlike Gemini CLI's policy-file recovery
|
|
53
|
+
(which resets the live file to a clean state), we deliberately leave
|
|
54
|
+
``config.yaml`` in place: hermes never silently mutates the user's config,
|
|
55
|
+
and leaving it means a hand-fixed file is re-read on the next load. The
|
|
56
|
+
backup is best-effort — any failure (permissions, symlink, disk full) is
|
|
57
|
+
swallowed so config loading is never blocked by backup problems.
|
|
58
|
+
|
|
59
|
+
Returns the backup path on success, else ``None``. Symlinks are not
|
|
60
|
+
followed/copied (mirrors the Gemini #21541 lstat guard) to avoid
|
|
61
|
+
clobbering whatever a malicious/misconfigured symlink points at.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
if config_path.is_symlink():
|
|
65
|
+
return None
|
|
66
|
+
st = config_path.stat()
|
|
67
|
+
if st.st_size == 0:
|
|
68
|
+
# Empty file isn't worth preserving and yaml.safe_load returns {}
|
|
69
|
+
# for it anyway (so it wouldn't reach here), but guard regardless.
|
|
70
|
+
return None
|
|
71
|
+
ts = time.strftime("%Y%m%d-%H%M%S")
|
|
72
|
+
backup_path = config_path.with_name(f"{config_path.name}.corrupt.{ts}.bak")
|
|
73
|
+
# Don't clobber an existing backup from the same second; if there's
|
|
74
|
+
# already a corrupt backup for this exact mtime, assume we've snapshotted
|
|
75
|
+
# this corruption already and skip (the dedup cache normally prevents a
|
|
76
|
+
# second call, but a process restart can clear it).
|
|
77
|
+
sibling_baks = list(
|
|
78
|
+
config_path.parent.glob(f"{config_path.name}.corrupt.*.bak")
|
|
79
|
+
)
|
|
80
|
+
for existing in sibling_baks:
|
|
81
|
+
try:
|
|
82
|
+
if existing.stat().st_size == st.st_size:
|
|
83
|
+
# Same size as the current broken file — likely the same
|
|
84
|
+
# corruption already preserved. Avoid backup churn.
|
|
85
|
+
return None
|
|
86
|
+
except OSError:
|
|
87
|
+
continue
|
|
88
|
+
if backup_path.exists():
|
|
89
|
+
return None
|
|
90
|
+
shutil.copy2(config_path, backup_path)
|
|
91
|
+
return backup_path
|
|
92
|
+
except Exception:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
39
96
|
def _warn_config_parse_failure(config_path: Path, exc: Exception) -> None:
|
|
40
97
|
"""Surface a config.yaml parse failure to user, log, and stderr.
|
|
41
98
|
|
|
@@ -48,7 +105,11 @@ def _warn_config_parse_failure(config_path: Path, exc: Exception) -> None:
|
|
|
48
105
|
Now: warn once per (path, mtime_ns, size) on stderr **and** in
|
|
49
106
|
``agent.log`` / ``errors.log`` at WARNING level so ``hermes logs``
|
|
50
107
|
surfaces it. Re-warns automatically if the file changes (different
|
|
51
|
-
mtime/size), so users editing the config see the next failure.
|
|
108
|
+
mtime/size), so users editing the config see the next failure. On the
|
|
109
|
+
first warning for a given broken file we also snapshot it to a
|
|
110
|
+
timestamped ``.bak`` (best-effort) so the user's recoverable content
|
|
111
|
+
survives any later rewrite of ``config.yaml`` by the setup wizard or
|
|
112
|
+
``hermes config set``.
|
|
52
113
|
"""
|
|
53
114
|
try:
|
|
54
115
|
st = config_path.stat()
|
|
@@ -59,12 +120,16 @@ def _warn_config_parse_failure(config_path: Path, exc: Exception) -> None:
|
|
|
59
120
|
return
|
|
60
121
|
_CONFIG_PARSE_WARNED.add(key)
|
|
61
122
|
|
|
123
|
+
backup_path = _backup_corrupt_config(config_path)
|
|
124
|
+
|
|
62
125
|
msg = (
|
|
63
126
|
f"Failed to parse {config_path}: {exc}. "
|
|
64
127
|
f"Falling back to default config — every user override "
|
|
65
128
|
f"(auxiliary providers, fallback chain, model settings) is being IGNORED. "
|
|
66
129
|
f"Fix the YAML and restart."
|
|
67
130
|
)
|
|
131
|
+
if backup_path is not None:
|
|
132
|
+
msg += f" A copy of the corrupted file was saved to {backup_path}."
|
|
68
133
|
logger.warning(msg)
|
|
69
134
|
try:
|
|
70
135
|
sys.stderr.write(f"⚠️ hermes config: {msg}\n")
|
|
@@ -158,7 +223,10 @@ _LAST_EXPANDED_CONFIG_BY_PATH: Dict[str, Any] = {}
|
|
|
158
223
|
# save_config() + migrate_config() write via atomic_yaml_write which
|
|
159
224
|
# produces a fresh inode, so stat() sees a new mtime_ns and the next
|
|
160
225
|
# load repopulates automatically — no explicit invalidation hook.
|
|
161
|
-
|
|
226
|
+
# Cached tuple is (user_mtime_ns, user_size, managed_mtime_ns, managed_size,
|
|
227
|
+
# merged_value) — the managed-file signature is folded in so editing the
|
|
228
|
+
# managed-scope config.yaml invalidates the cache (see managed_scope).
|
|
229
|
+
_LOAD_CONFIG_CACHE: Dict[str, Tuple[int, int, int, int, Dict[str, Any]]] = {}
|
|
162
230
|
# (path, mtime_ns, size) -> cached raw yaml dict. Same pattern as
|
|
163
231
|
# _LOAD_CONFIG_CACHE but for read_raw_config() — used when callers want
|
|
164
232
|
# the user's on-disk values without defaults merged in.
|
|
@@ -205,6 +273,11 @@ _EXTRA_ENV_KEYS = frozenset({
|
|
|
205
273
|
"IRC_SERVER", "IRC_PORT", "IRC_NICKNAME", "IRC_CHANNEL",
|
|
206
274
|
"IRC_USE_TLS", "IRC_SERVER_PASSWORD", "IRC_NICKSERV_PASSWORD",
|
|
207
275
|
"TERMINAL_ENV", "TERMINAL_SSH_KEY", "TERMINAL_SSH_PORT",
|
|
276
|
+
# Deprecated tool-progress env vars — replaced by display.tool_progress in
|
|
277
|
+
# config.yaml. Kept known here so .env sanitization/reload still handle
|
|
278
|
+
# them for existing users (gateway reads them as a back-compat fallback),
|
|
279
|
+
# without surfacing them in user-facing OPTIONAL_ENV_VARS listings.
|
|
280
|
+
"HERMES_TOOL_PROGRESS", "HERMES_TOOL_PROGRESS_MODE",
|
|
208
281
|
"WHATSAPP_MODE", "WHATSAPP_ENABLED",
|
|
209
282
|
"MATTERMOST_HOME_CHANNEL", "MATTERMOST_HOME_CHANNEL_NAME", "MATTERMOST_REPLY_MODE",
|
|
210
283
|
"MATRIX_PASSWORD", "MATRIX_ENCRYPTION", "MATRIX_DEVICE_ID", "MATRIX_HOME_ROOM",
|
|
@@ -280,52 +353,124 @@ def get_managed_update_command() -> Optional[str]:
|
|
|
280
353
|
return None
|
|
281
354
|
|
|
282
355
|
|
|
356
|
+
def _install_method_project_root(project_root: Optional[Path] = None) -> Path:
|
|
357
|
+
"""Resolve the directory that holds the *running code* (the install tree).
|
|
358
|
+
|
|
359
|
+
This is the parent of ``hermes_cli/`` — i.e. the git checkout for source
|
|
360
|
+
installs, ``/opt/hermes`` inside the published image, the venv's
|
|
361
|
+
site-packages root for pip installs. It is a property of the running
|
|
362
|
+
interpreter, NOT of ``$HERMES_HOME``, which is why a code-scoped stamp
|
|
363
|
+
here is immune to two installs sharing one data directory.
|
|
364
|
+
"""
|
|
365
|
+
if project_root is not None:
|
|
366
|
+
return project_root
|
|
367
|
+
return Path(__file__).parent.parent.resolve()
|
|
368
|
+
|
|
369
|
+
|
|
283
370
|
def detect_install_method(project_root: Optional[Path] = None) -> str:
|
|
284
371
|
"""Detect how Hermes was installed: 'docker', 'nixos', 'homebrew', 'git', or 'pip'.
|
|
285
372
|
|
|
286
373
|
Resolution order:
|
|
287
|
-
1.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
374
|
+
1. Code-scoped stamp ``<install tree>/.install_method`` (next to the
|
|
375
|
+
running code) — the authoritative marker.
|
|
376
|
+
2. Legacy home-scoped stamp ``$HERMES_HOME/.install_method`` — read for
|
|
377
|
+
backward compatibility, but a ``docker`` value is IGNORED when we are
|
|
378
|
+
not actually running inside a container (see below).
|
|
379
|
+
3. HERMES_MANAGED env / .managed marker (NixOS, Homebrew)
|
|
380
|
+
4. .git directory presence -> 'git'
|
|
381
|
+
5. Fallback -> 'pip'
|
|
382
|
+
|
|
383
|
+
Why the stamp is code-scoped, not home-scoped (issue: shared ``~/.hermes``)
|
|
384
|
+
--------------------------------------------------------------------------
|
|
385
|
+
The install method describes *the binary that is running*, but
|
|
386
|
+
``$HERMES_HOME`` is a shared DATA directory — the Docker docs deliberately
|
|
387
|
+
bind-mount it (``~/.hermes:/opt/data``) so config/sessions/memory persist
|
|
388
|
+
and can be shared with a host-side Desktop/CLI install. When a
|
|
389
|
+
containerised gateway and a host install share one ``$HERMES_HOME``, a
|
|
390
|
+
home-scoped stamp is a single slot describing two different installs:
|
|
391
|
+
the container stamps ``docker`` on every boot, the host install then reads
|
|
392
|
+
``docker`` and ``hermes update`` refuses to run ("doesn't apply inside the
|
|
393
|
+
Docker container") even though the host binary is a perfectly updatable
|
|
394
|
+
git/pip install. Scoping the stamp to the install tree gives each install
|
|
395
|
+
its own truthful marker.
|
|
396
|
+
|
|
397
|
+
Self-healing for already-poisoned homes: a legacy ``docker`` value in the
|
|
398
|
+
home-scoped stamp is only honoured when we are genuinely in a container.
|
|
399
|
+
On a host install that read a contaminating ``docker`` stamp, we fall
|
|
400
|
+
through to managed/.git/pip detection instead — so existing shared-home
|
|
401
|
+
setups recover without the user touching anything.
|
|
291
402
|
|
|
292
403
|
Note: running inside a container is NOT treated as "docker" on its own.
|
|
293
|
-
The
|
|
294
|
-
``.install_method`` stamp (caught by step 1), so neither relies on
|
|
295
|
-
container detection here:
|
|
404
|
+
The supported installs self-identify via the code-scoped stamp:
|
|
296
405
|
- the curl installer (scripts/install.sh, the README/website install
|
|
297
|
-
command) git-clones the repo and stamps ``git
|
|
298
|
-
- the published ``nousresearch/hermes-agent`` image
|
|
299
|
-
at
|
|
300
|
-
An unsupported manual install dropped into a container (no stamp)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
container". Without that fallback such installs fall through to the
|
|
304
|
-
``.git``/pip checks and behave like any off-path install. See issue #34397.
|
|
406
|
+
command) git-clones the repo and stamps ``git`` next to the code;
|
|
407
|
+
- the published ``nousresearch/hermes-agent`` image bakes a ``docker``
|
|
408
|
+
stamp into ``/opt/hermes`` at build time.
|
|
409
|
+
An unsupported manual install dropped into a container (no stamp) falls
|
|
410
|
+
through to the ``.git``/pip checks and behaves like any off-path install.
|
|
411
|
+
See issue #34397.
|
|
305
412
|
"""
|
|
306
|
-
|
|
413
|
+
root = _install_method_project_root(project_root)
|
|
414
|
+
|
|
415
|
+
# 1. Code-scoped stamp — authoritative, immune to shared $HERMES_HOME.
|
|
307
416
|
try:
|
|
308
|
-
method =
|
|
417
|
+
method = (root / ".install_method").read_text(encoding="utf-8").strip().lower()
|
|
309
418
|
if method:
|
|
310
419
|
return method
|
|
311
420
|
except OSError:
|
|
312
421
|
pass
|
|
422
|
+
|
|
423
|
+
# 2. Legacy home-scoped stamp — back-compat. Ignore a ``docker`` value
|
|
424
|
+
# when we are not actually containerised: that is the signature of a
|
|
425
|
+
# host install whose shared $HERMES_HOME was stamped by a co-located
|
|
426
|
+
# container, and honouring it wrongly blocks ``hermes update``.
|
|
427
|
+
try:
|
|
428
|
+
method = (
|
|
429
|
+
(get_hermes_home() / ".install_method")
|
|
430
|
+
.read_text(encoding="utf-8")
|
|
431
|
+
.strip()
|
|
432
|
+
.lower()
|
|
433
|
+
)
|
|
434
|
+
if method and not (method == "docker" and not _running_in_container()):
|
|
435
|
+
return method
|
|
436
|
+
except OSError:
|
|
437
|
+
pass
|
|
438
|
+
|
|
313
439
|
managed = get_managed_system()
|
|
314
440
|
if managed:
|
|
315
441
|
return managed.lower().replace(" ", "-")
|
|
316
|
-
if
|
|
317
|
-
project_root = Path(__file__).parent.parent.resolve()
|
|
318
|
-
if (project_root / ".git").is_dir():
|
|
442
|
+
if (root / ".git").is_dir():
|
|
319
443
|
return "git"
|
|
320
444
|
return "pip"
|
|
321
445
|
|
|
322
446
|
|
|
323
|
-
def
|
|
324
|
-
"""
|
|
325
|
-
|
|
447
|
+
def _running_in_container() -> bool:
|
|
448
|
+
"""Thin wrapper around ``hermes_constants.is_container`` (import-safe)."""
|
|
449
|
+
try:
|
|
450
|
+
from hermes_constants import is_container
|
|
451
|
+
|
|
452
|
+
return is_container()
|
|
453
|
+
except Exception:
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def stamp_install_method(method: str, project_root: Optional[Path] = None) -> None:
|
|
458
|
+
"""Write the install method next to the running code (code-scoped stamp).
|
|
459
|
+
|
|
460
|
+
The stamp lives in the install tree (``<install tree>/.install_method``),
|
|
461
|
+
not in ``$HERMES_HOME``, so that two installs sharing one data directory
|
|
462
|
+
do not overwrite each other's marker. See ``detect_install_method`` for
|
|
463
|
+
the full rationale.
|
|
464
|
+
|
|
465
|
+
Best-effort: if the install tree is read-only (e.g. the immutable
|
|
466
|
+
``/opt/hermes`` in the published image, which instead bakes the stamp at
|
|
467
|
+
build time) the write silently no-ops and detection falls back to its
|
|
468
|
+
other signals.
|
|
469
|
+
"""
|
|
470
|
+
root = _install_method_project_root(project_root)
|
|
326
471
|
try:
|
|
327
|
-
|
|
328
|
-
|
|
472
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
473
|
+
(root / ".install_method").write_text(method + "\n", encoding="utf-8")
|
|
329
474
|
except OSError:
|
|
330
475
|
pass
|
|
331
476
|
|
|
@@ -742,6 +887,9 @@ DEFAULT_CONFIG = {
|
|
|
742
887
|
"credential_pool_strategies": {},
|
|
743
888
|
"toolsets": ["hermes-cli"],
|
|
744
889
|
"mcp_servers": {},
|
|
890
|
+
# Global active chat session cap across CLI, TUI/dashboard, and messaging.
|
|
891
|
+
# None/0 = unbounded.
|
|
892
|
+
"max_concurrent_sessions": None,
|
|
745
893
|
"agent": {
|
|
746
894
|
"max_turns": 90,
|
|
747
895
|
# Inactivity timeout for gateway agent execution (seconds).
|
|
@@ -781,6 +929,15 @@ DEFAULT_CONFIG = {
|
|
|
781
929
|
# plausible-looking output when a real path is blocked. Costs ~80
|
|
782
930
|
# tokens in the cached system prompt. Set False to disable globally.
|
|
783
931
|
"task_completion_guidance": True,
|
|
932
|
+
# Universal parallel-tool-call guidance — short prompt block applied to
|
|
933
|
+
# all models that tells the model to batch independent tool calls
|
|
934
|
+
# (reads, searches, web fetches, read-only commands) into one turn
|
|
935
|
+
# instead of one call per turn. The runtime already runs independent
|
|
936
|
+
# calls concurrently, so this just steers the model to produce the
|
|
937
|
+
# batch — cutting round-trips and the resent-context cost that
|
|
938
|
+
# compounds over a long conversation. Costs ~70 tokens in the cached
|
|
939
|
+
# system prompt. Set False to disable globally.
|
|
940
|
+
"parallel_tool_call_guidance": True,
|
|
784
941
|
# Local-environment toolchain probe — surfaces Python/pip/uv/PEP-668
|
|
785
942
|
# state in the system prompt when something non-default is detected
|
|
786
943
|
# (e.g. python3 has no pip module, pip→python version mismatch, PEP
|
|
@@ -796,6 +953,21 @@ DEFAULT_CONFIG = {
|
|
|
796
953
|
# identity slot (SOUL.md). Empty by default. The HERMES_ENVIRONMENT_HINT
|
|
797
954
|
# env var overrides this (build-time/container mechanism).
|
|
798
955
|
"environment_hint": "",
|
|
956
|
+
# Coding posture — on interactive coding surfaces (CLI, TUI, desktop
|
|
957
|
+
# app, ACP) in a code workspace, Hermes adds a coding operating brief
|
|
958
|
+
# + a live git/workspace snapshot to the system prompt. See
|
|
959
|
+
# agent/coding_context.py.
|
|
960
|
+
# "auto" (default) — prompt-only posture when the surface is
|
|
961
|
+
# interactive AND cwd is a code workspace.
|
|
962
|
+
# Toolsets are never touched; messaging platforms
|
|
963
|
+
# unaffected.
|
|
964
|
+
# "focus" — auto + collapse the toolset to the lean coding
|
|
965
|
+
# set (+ enabled MCP servers) + demote non-coding
|
|
966
|
+
# skill categories to names-only in the prompt's
|
|
967
|
+
# skill index. Explicit opt-in.
|
|
968
|
+
# "on" — force the prompt posture everywhere.
|
|
969
|
+
# "off" — disable entirely.
|
|
970
|
+
"coding_context": "auto",
|
|
799
971
|
# Staged inactivity warning: send a warning to the user at this
|
|
800
972
|
# threshold before escalating to a full timeout. The warning fires
|
|
801
973
|
# once per run and does not interrupt the agent. 0 = disable warning.
|
|
@@ -854,6 +1026,13 @@ DEFAULT_CONFIG = {
|
|
|
854
1026
|
# (terminal and execute_code). Skill-declared required_environment_variables
|
|
855
1027
|
# are passed through automatically; this list is for non-skill use cases.
|
|
856
1028
|
"env_passthrough": [],
|
|
1029
|
+
# HOME handling for host tool subprocesses:
|
|
1030
|
+
# auto — host keeps the real OS-user HOME; containers use
|
|
1031
|
+
# HERMES_HOME/home for persistent state (default)
|
|
1032
|
+
# real — force the real OS-user HOME
|
|
1033
|
+
# profile — force HERMES_HOME/home when it exists (old strict
|
|
1034
|
+
# per-profile CLI config isolation)
|
|
1035
|
+
"home_mode": "auto",
|
|
857
1036
|
# Extra files to source in the login shell when building the
|
|
858
1037
|
# per-session environment snapshot. Use this when tools like nvm,
|
|
859
1038
|
# pyenv, asdf, or custom PATH entries are registered by files that
|
|
@@ -1010,11 +1189,34 @@ DEFAULT_CONFIG = {
|
|
|
1010
1189
|
"min_interval_hours": 24,
|
|
1011
1190
|
},
|
|
1012
1191
|
|
|
1192
|
+
# Hard cap (chars) for a single automatic context file such as SOUL.md,
|
|
1193
|
+
# AGENTS.md, CLAUDE.md, .hermes.md, or .cursorrules before Hermes applies
|
|
1194
|
+
# head/tail truncation. ``null`` (the default) lets the cap scale with the
|
|
1195
|
+
# model's context window (floor 20K, ceiling 500K) so large-context models
|
|
1196
|
+
# rarely truncate a project doc. Set a positive integer to pin a fixed cap
|
|
1197
|
+
# and override the dynamic behavior. Separate from read_file tool limits.
|
|
1198
|
+
"context_file_max_chars": None,
|
|
1199
|
+
|
|
1013
1200
|
# Maximum characters returned by a single read_file call. Reads that
|
|
1014
1201
|
# exceed this are rejected with guidance to use offset+limit.
|
|
1015
1202
|
# 100K chars ≈ 25–35K tokens across typical tokenisers.
|
|
1016
1203
|
"file_read_max_chars": 100_000,
|
|
1017
1204
|
|
|
1205
|
+
# Seconds to wait at agent-build time for in-flight MCP server discovery
|
|
1206
|
+
# to finish before the agent snapshots its tool list. MCP discovery runs
|
|
1207
|
+
# in a background thread so a slow/dead server can't freeze startup; this
|
|
1208
|
+
# bounds how long the first agent build blocks on it. The wait returns
|
|
1209
|
+
# the INSTANT discovery completes, so users with no MCP servers (the common
|
|
1210
|
+
# case) or fast servers pay ~0s regardless of this value — the bound is
|
|
1211
|
+
# only reached when a server is genuinely still connecting. The old 0.75s
|
|
1212
|
+
# default was a touch short for HTTP/OAuth servers on a cold connect; a
|
|
1213
|
+
# modest bump lets more of them land in the FIRST turn's snapshot. This is
|
|
1214
|
+
# only a turn-1 latency/UX knob: a server that misses this window is still
|
|
1215
|
+
# picked up automatically on the next turn by the between-turns refresh
|
|
1216
|
+
# (see agent/turn_context.py), so correctness never depends on it. Keep it
|
|
1217
|
+
# small so a slow/dead server adds little to first-response latency.
|
|
1218
|
+
"mcp_discovery_timeout": 1.5,
|
|
1219
|
+
|
|
1018
1220
|
# Tool-output truncation thresholds. When terminal output or a
|
|
1019
1221
|
# single read_file page exceeds these limits, Hermes truncates the
|
|
1020
1222
|
# payload sent to the model (keeping head + tail for terminal,
|
|
@@ -1076,6 +1278,31 @@ DEFAULT_CONFIG = {
|
|
|
1076
1278
|
# Default False matches historical behavior; set to
|
|
1077
1279
|
# True if you'd rather pause than silently lose
|
|
1078
1280
|
# context turns when your aux model is flaky.
|
|
1281
|
+
"codex_gpt55_autoraise": True, # When True, gpt-5.5 on the ChatGPT Codex OAuth
|
|
1282
|
+
# route raises its compaction trigger to 85% (vs the
|
|
1283
|
+
# global `threshold` above). Codex hard-caps gpt-5.5
|
|
1284
|
+
# at a 272K window, so the default 50% would compact
|
|
1285
|
+
# at ~136K and waste half the usable context. Set to
|
|
1286
|
+
# False to opt back down to the global threshold
|
|
1287
|
+
# (e.g. 0.50) for Codex gpt-5.5 sessions. Only this
|
|
1288
|
+
# exact route is affected — gpt-5.5 on OpenAI's
|
|
1289
|
+
# direct API, OpenRouter, and Copilot keep the
|
|
1290
|
+
# global threshold regardless.
|
|
1291
|
+
},
|
|
1292
|
+
|
|
1293
|
+
# Kanban subsystem (orchestrator workers + dispatcher-driven child tasks).
|
|
1294
|
+
# See tools/kanban_tools.py and hermes_cli/kanban_db.py for the actual
|
|
1295
|
+
# implementations. Per-platform notification opt-out is handled by the
|
|
1296
|
+
# kanban dashboard (see ``hermes dashboard`` -> Notifications).
|
|
1297
|
+
"kanban": {
|
|
1298
|
+
# Auto-subscribe the originating gateway/TUI session to task
|
|
1299
|
+
# completion + block events when ``kanban_create`` is called from
|
|
1300
|
+
# inside a session that has a persistent delivery channel. The
|
|
1301
|
+
# agent that dispatched the task will get notified automatically
|
|
1302
|
+
# instead of having to poll. Disable to mirror pre-feature
|
|
1303
|
+
# behaviour — e.g. for a profile that prefers explicit
|
|
1304
|
+
# ``kanban_notify-subscribe`` calls per task.
|
|
1305
|
+
"auto_subscribe_on_create": True,
|
|
1079
1306
|
},
|
|
1080
1307
|
|
|
1081
1308
|
# Anthropic prompt caching (Claude via OpenRouter or native Anthropic API).
|
|
@@ -1213,6 +1440,14 @@ DEFAULT_CONFIG = {
|
|
|
1213
1440
|
"timeout": 30,
|
|
1214
1441
|
"extra_body": {},
|
|
1215
1442
|
},
|
|
1443
|
+
"tts_audio_tags": {
|
|
1444
|
+
"provider": "auto",
|
|
1445
|
+
"model": "",
|
|
1446
|
+
"base_url": "",
|
|
1447
|
+
"api_key": "",
|
|
1448
|
+
"timeout": 30,
|
|
1449
|
+
"extra_body": {},
|
|
1450
|
+
},
|
|
1216
1451
|
# Triage specifier — flesh out a rough one-liner in the Kanban
|
|
1217
1452
|
# Triage column into a concrete spec, then promote it to ``todo``.
|
|
1218
1453
|
# Invoked by ``hermes kanban specify`` (single id or --all). Set a
|
|
@@ -1264,6 +1499,20 @@ DEFAULT_CONFIG = {
|
|
|
1264
1499
|
"timeout": 600,
|
|
1265
1500
|
"extra_body": {},
|
|
1266
1501
|
},
|
|
1502
|
+
# Monitor — urgency/importance classifier used by the important-mail
|
|
1503
|
+
# monitor catalog automation (cron/scripts/classify_items.py). Scores
|
|
1504
|
+
# candidate items 0-10 against the user's criteria so only above-
|
|
1505
|
+
# threshold items get delivered. "auto" = main chat model; override to
|
|
1506
|
+
# a cheap fast model (e.g. openrouter google/gemini-3-flash-preview,
|
|
1507
|
+
# haiku) since per-item scoring is high-volume and a small model is fine.
|
|
1508
|
+
"monitor": {
|
|
1509
|
+
"provider": "auto",
|
|
1510
|
+
"model": "",
|
|
1511
|
+
"base_url": "",
|
|
1512
|
+
"api_key": "",
|
|
1513
|
+
"timeout": 60,
|
|
1514
|
+
"extra_body": {},
|
|
1515
|
+
},
|
|
1267
1516
|
},
|
|
1268
1517
|
|
|
1269
1518
|
"display": {
|
|
@@ -1284,6 +1533,12 @@ DEFAULT_CONFIG = {
|
|
|
1284
1533
|
# behavior of showing tool-call summaries inline.
|
|
1285
1534
|
"resume_skip_tool_only": True,
|
|
1286
1535
|
"busy_input_mode": "interrupt", # interrupt | queue | steer
|
|
1536
|
+
# Which interface bare `hermes` (and `hermes chat`) launches by default:
|
|
1537
|
+
# "cli" — the classic prompt_toolkit REPL (default, preserves prior behavior)
|
|
1538
|
+
# "tui" — the modern Ink TUI (same as passing `--tui`)
|
|
1539
|
+
# Explicit flags always win over this setting: `--cli` forces the classic
|
|
1540
|
+
# REPL and `--tui` (or HERMES_TUI=1) forces the TUI regardless of config.
|
|
1541
|
+
"interface": "cli",
|
|
1287
1542
|
# When true, `hermes --tui` auto-resumes the most recent human-
|
|
1288
1543
|
# facing session on launch instead of forging a fresh one.
|
|
1289
1544
|
# Mirrors `hermes -c` muscle memory. Default off so existing
|
|
@@ -1296,6 +1551,12 @@ DEFAULT_CONFIG = {
|
|
|
1296
1551
|
"tui_agents_nudge": True,
|
|
1297
1552
|
"bell_on_complete": False,
|
|
1298
1553
|
"show_reasoning": False,
|
|
1554
|
+
# Background self-improvement review notifications surfaced in chat.
|
|
1555
|
+
# "off" — no chat notification (the review still runs and writes)
|
|
1556
|
+
# "on" — generic "💾 Memory updated" line (default)
|
|
1557
|
+
# "verbose" — include a compact content preview of what changed
|
|
1558
|
+
# Per-platform overrides via display.platforms.<platform>.memory_notifications.
|
|
1559
|
+
"memory_notifications": "on",
|
|
1299
1560
|
"streaming": False,
|
|
1300
1561
|
"timestamps": False, # Show [HH:MM] on user and assistant labels
|
|
1301
1562
|
"final_response_markdown": "strip", # render | strip | raw
|
|
@@ -1304,6 +1565,10 @@ DEFAULT_CONFIG = {
|
|
|
1304
1565
|
# behaves badly with replayed scrollback.
|
|
1305
1566
|
"persistent_output": True,
|
|
1306
1567
|
"persistent_output_max_lines": 200,
|
|
1568
|
+
# Print a one-line summary of resolved modal prompts (approval /
|
|
1569
|
+
# clarify) into scrollback so the question and decision survive the
|
|
1570
|
+
# panel repaint. Set false to keep scrollback untouched.
|
|
1571
|
+
"persist_prompts": True,
|
|
1307
1572
|
"inline_diffs": True, # Show inline diff previews for write actions (write_file, patch, skill_manage)
|
|
1308
1573
|
# File-mutation verifier footer. When true (default), the agent
|
|
1309
1574
|
# appends a one-line advisory to its final response whenever a
|
|
@@ -1313,6 +1578,11 @@ DEFAULT_CONFIG = {
|
|
|
1313
1578
|
# class of over-claim that otherwise forces users to run
|
|
1314
1579
|
# `git status` to verify edits landed. Set false to suppress.
|
|
1315
1580
|
"file_mutation_verifier": True,
|
|
1581
|
+
# Nous credits status-bar notices (usage bands, grant-spent, depleted /
|
|
1582
|
+
# restored). When false, no credits notices are emitted — balance data
|
|
1583
|
+
# is still captured and /usage keeps working. Off switch for sub +
|
|
1584
|
+
# top-up users who find the gauge noisy.
|
|
1585
|
+
"credits_notices": True,
|
|
1316
1586
|
# Turn-completion explainer. When true (default), the agent appends a
|
|
1317
1587
|
# one-line explanation to its final response whenever a turn ends
|
|
1318
1588
|
# abnormally with no usable reply — empty content after retries, a
|
|
@@ -1330,6 +1600,14 @@ DEFAULT_CONFIG = {
|
|
|
1330
1600
|
# TUI busy indicator style: kaomoji (default), emoji, unicode (braille
|
|
1331
1601
|
# spinner), or ascii. Live-swappable via `/indicator <style>`.
|
|
1332
1602
|
"tui_status_indicator": "kaomoji",
|
|
1603
|
+
# Seconds between prompt_toolkit redraws in the classic CLI when idle.
|
|
1604
|
+
# Default 1.0 keeps the wall-clock status-bar read-outs (idle-since-
|
|
1605
|
+
# last-turn) ticking and keeps the bottom chrome alive during idle —
|
|
1606
|
+
# without it prompt_toolkit stops repainting the status bar after a
|
|
1607
|
+
# turn and it can go stale/disappear (#45592).
|
|
1608
|
+
# Set 0 to disable the background refresh if it fights terminal
|
|
1609
|
+
# auto-scroll in non-fullscreen mode on some emulators (#48309).
|
|
1610
|
+
"cli_refresh_interval": 1.0,
|
|
1333
1611
|
"user_message_preview": { # CLI: how many submitted user-message lines to echo back in scrollback
|
|
1334
1612
|
"first_lines": 2,
|
|
1335
1613
|
"last_lines": 2,
|
|
@@ -1338,6 +1616,12 @@ DEFAULT_CONFIG = {
|
|
|
1338
1616
|
"tool_progress_command": False, # Enable /verbose command in messaging gateway
|
|
1339
1617
|
"tool_progress_overrides": {}, # DEPRECATED — use display.platforms instead
|
|
1340
1618
|
"tool_preview_length": 0, # Max chars for tool call previews (0 = no limit, show full paths/commands)
|
|
1619
|
+
# How gateway tool-progress is grouped on platforms that support message
|
|
1620
|
+
# editing: "accumulate" (default) edits one bubble in place; "separate"
|
|
1621
|
+
# sends one message per tool (the pre-v0.9 behavior, noisier). Only
|
|
1622
|
+
# applies where tool_progress is already enabled. Per-platform override
|
|
1623
|
+
# via display.platforms.<platform>.tool_progress_grouping.
|
|
1624
|
+
"tool_progress_grouping": "accumulate",
|
|
1341
1625
|
# Auto-delete system-notice replies (e.g. "✨ New session started!",
|
|
1342
1626
|
# "♻ Restarting gateway…", "⚡ Stopped…") after N seconds on platforms
|
|
1343
1627
|
# that support message deletion (currently Telegram; other platforms
|
|
@@ -1413,6 +1697,34 @@ DEFAULT_CONFIG = {
|
|
|
1413
1697
|
"client_id": "", # agent:{instance_id} — Portal provisions this
|
|
1414
1698
|
"portal_url": "", # blank → use plugin default (production Portal)
|
|
1415
1699
|
},
|
|
1700
|
+
# Username/password gate configuration — read by the bundled
|
|
1701
|
+
# ``dashboard_auth/basic`` plugin (a self-hosted "just put a
|
|
1702
|
+
# password on my dashboard" provider that needs no OAuth IDP).
|
|
1703
|
+
# The plugin registers a password provider when ``username`` plus
|
|
1704
|
+
# either ``password_hash`` (preferred — no plaintext at rest) or
|
|
1705
|
+
# ``password`` (plaintext, hashed in-memory at load) are set. Each
|
|
1706
|
+
# key is overridable by an env var
|
|
1707
|
+
# (``HERMES_DASHBOARD_BASIC_AUTH_USERNAME`` /
|
|
1708
|
+
# ``_PASSWORD_HASH`` / ``_PASSWORD`` / ``_SECRET`` /
|
|
1709
|
+
# ``_TTL_SECONDS``), env winning when non-empty. Leave ``username``
|
|
1710
|
+
# empty (the default) to keep the plugin a no-op — loopback /
|
|
1711
|
+
# ``--insecure`` operators and OAuth users are unaffected.
|
|
1712
|
+
#
|
|
1713
|
+
# ``secret`` is the HMAC key used to sign the stateless session
|
|
1714
|
+
# tokens this provider mints. When empty, a random per-process key
|
|
1715
|
+
# is generated — fine for a single process, but sessions then
|
|
1716
|
+
# don't survive a restart or span multiple workers. Set an
|
|
1717
|
+
# explicit ``secret`` (32+ random bytes, base64/hex/raw) for
|
|
1718
|
+
# stable multi-worker / restart-surviving sessions. Compute a
|
|
1719
|
+
# ``password_hash`` with
|
|
1720
|
+
# ``python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"``.
|
|
1721
|
+
"basic_auth": {
|
|
1722
|
+
"username": "", # blank → plugin no-op (no password provider)
|
|
1723
|
+
"password_hash": "", # scrypt$... (preferred — no plaintext at rest)
|
|
1724
|
+
"password": "", # plaintext fallback (hashed in-memory at load)
|
|
1725
|
+
"secret": "", # token-signing key; blank → random per-process
|
|
1726
|
+
"session_ttl_seconds": 0, # 0 → plugin default (12h)
|
|
1727
|
+
},
|
|
1416
1728
|
# Public URL override (env: ``HERMES_DASHBOARD_PUBLIC_URL``).
|
|
1417
1729
|
# When set, this is the complete authority — scheme + host +
|
|
1418
1730
|
# optional path prefix (e.g. ``https://example.com/hermes``) —
|
|
@@ -1445,7 +1757,7 @@ DEFAULT_CONFIG = {
|
|
|
1445
1757
|
# Each provider supports an optional `max_text_length:` override for the
|
|
1446
1758
|
# per-request input-character cap. Omit it to use the provider's documented
|
|
1447
1759
|
# limit (OpenAI 4096, xAI 15000, MiniMax 10000, ElevenLabs 5k-40k model-aware,
|
|
1448
|
-
# Gemini
|
|
1760
|
+
# Gemini 32000, Edge 5000, Mistral 4000, NeuTTS/KittenTTS 2000).
|
|
1449
1761
|
"tts": {
|
|
1450
1762
|
"provider": "edge", # "edge" (free) | "elevenlabs" (premium) | "openai" | "xai" | "minimax" | "mistral" | "gemini" | "neutts" (local) | "kittentts" (local) | "piper" (local)
|
|
1451
1763
|
"edge": {
|
|
@@ -1461,6 +1773,19 @@ DEFAULT_CONFIG = {
|
|
|
1461
1773
|
"voice": "alloy",
|
|
1462
1774
|
# Voices: alloy, echo, fable, onyx, nova, shimmer
|
|
1463
1775
|
},
|
|
1776
|
+
"gemini": {
|
|
1777
|
+
"model": "gemini-2.5-flash-preview-tts",
|
|
1778
|
+
"voice": "Kore",
|
|
1779
|
+
# When true, Gemini 3.1 TTS uses a hidden auxiliary-model rewrite
|
|
1780
|
+
# pass to insert freeform square-bracket audio tags into the TTS
|
|
1781
|
+
# script. Visible chat replies are unchanged.
|
|
1782
|
+
"audio_tags": False,
|
|
1783
|
+
# Optional local Markdown/text file with Gemini TTS performance
|
|
1784
|
+
# direction. It may include AUDIO PROFILE, SCENE, DIRECTOR'S NOTES,
|
|
1785
|
+
# SAMPLE CONTEXT, and either a `{transcript}` placeholder or no
|
|
1786
|
+
# transcript section; Hermes appends the live transcript when absent.
|
|
1787
|
+
"persona_prompt_file": "",
|
|
1788
|
+
},
|
|
1464
1789
|
"xai": {
|
|
1465
1790
|
"voice_id": "eve", # or custom voice ID — see https://docs.x.ai/developers/model-capabilities/audio/custom-voices
|
|
1466
1791
|
"language": "en",
|
|
@@ -1542,6 +1867,19 @@ DEFAULT_CONFIG = {
|
|
|
1542
1867
|
"memory": {
|
|
1543
1868
|
"memory_enabled": True,
|
|
1544
1869
|
"user_profile_enabled": True,
|
|
1870
|
+
# Approval gate for memory writes (add/replace/remove), applied to BOTH
|
|
1871
|
+
# foreground agent turns and the background self-improvement review fork
|
|
1872
|
+
# (the source of unprompted "wrong assumption" saves users reported).
|
|
1873
|
+
# false (default) — write freely; the gate is off (pre-gate behaviour)
|
|
1874
|
+
# true — require approval: foreground writes prompt inline
|
|
1875
|
+
# (entries are small enough to review in a chat
|
|
1876
|
+
# bubble); background-review writes are staged
|
|
1877
|
+
# instead of committed (a daemon thread cannot block
|
|
1878
|
+
# on a prompt). Review staged entries with
|
|
1879
|
+
# /memory pending, /memory approve <id>,
|
|
1880
|
+
# /memory reject <id>.
|
|
1881
|
+
# To disable memory entirely, use memory_enabled: false instead.
|
|
1882
|
+
"write_approval": False,
|
|
1545
1883
|
"memory_char_limit": 2200, # ~800 tokens at 2.75 chars/token
|
|
1546
1884
|
"user_char_limit": 1375, # ~500 tokens at 2.75 chars/token
|
|
1547
1885
|
# External memory provider plugin (empty = built-in only).
|
|
@@ -1572,17 +1910,19 @@ DEFAULT_CONFIG = {
|
|
|
1572
1910
|
"inherit_mcp_toolsets": True,
|
|
1573
1911
|
"max_iterations": 50, # per-subagent iteration cap (each subagent gets its own budget,
|
|
1574
1912
|
# independent of the parent's max_iterations)
|
|
1575
|
-
"child_timeout_seconds":
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1913
|
+
"child_timeout_seconds": 0, # optional wall-clock cap per child agent. 0 (default)
|
|
1914
|
+
# = no timeout: children fail only from real errors
|
|
1915
|
+
# (API, tools, iteration budget), never a delegation
|
|
1916
|
+
# stopwatch. Set a positive number of seconds
|
|
1917
|
+
# (floor 30s) to enforce a hard cap.
|
|
1579
1918
|
"reasoning_effort": "", # reasoning effort for subagents: "xhigh", "high", "medium",
|
|
1580
1919
|
# "low", "minimal", "none" (empty = inherit parent's level)
|
|
1581
1920
|
"max_concurrent_children": 3, # max parallel children per batch; floor of 1 enforced, no ceiling
|
|
1921
|
+
"max_async_children": 3, # max concurrent background (background=true) subagents; new dispatches rejected at capacity
|
|
1582
1922
|
# Orchestrator role controls (see tools/delegate_tool.py:_get_max_spawn_depth
|
|
1583
|
-
# and _get_orchestrator_enabled).
|
|
1584
|
-
#
|
|
1585
|
-
"max_spawn_depth": 1, # depth
|
|
1923
|
+
# and _get_orchestrator_enabled). Floored at 1, no upper ceiling —
|
|
1924
|
+
# raise deliberately, each level multiplies API cost.
|
|
1925
|
+
"max_spawn_depth": 1, # depth (1 = flat [default], 2 = orchestrator→leaf, 3+ = deeper)
|
|
1586
1926
|
"orchestrator_enabled": True, # kill switch for role="orchestrator"
|
|
1587
1927
|
# When a subagent hits a dangerous-command approval prompt, the parent's
|
|
1588
1928
|
# prompt_toolkit TUI owns stdin — a thread-local input() call from the
|
|
@@ -1646,6 +1986,18 @@ DEFAULT_CONFIG = {
|
|
|
1646
1986
|
# External hub installs (trusted/community sources) are always
|
|
1647
1987
|
# scanned regardless of this setting.
|
|
1648
1988
|
"guard_agent_created": False,
|
|
1989
|
+
# Approval gate for skill_manage (create/edit/patch/write_file/delete/
|
|
1990
|
+
# remove_file), applied to BOTH foreground agent turns and the
|
|
1991
|
+
# background self-improvement review fork.
|
|
1992
|
+
# false (default) — write freely; the gate is off (pre-gate behaviour)
|
|
1993
|
+
# true — require approval: stage the write for review
|
|
1994
|
+
# instead of committing (a SKILL.md is too large to
|
|
1995
|
+
# review inline, so skills always stage rather than
|
|
1996
|
+
# prompt). List with /skills pending, inspect with
|
|
1997
|
+
# /skills diff <id> (full diff — CLI/dashboard/file,
|
|
1998
|
+
# never crammed into a chat bubble), apply with
|
|
1999
|
+
# /skills approve <id> or drop with /skills reject <id>.
|
|
2000
|
+
"write_approval": False,
|
|
1649
2001
|
},
|
|
1650
2002
|
|
|
1651
2003
|
# Curator — background skill maintenance.
|
|
@@ -1669,6 +2021,14 @@ DEFAULT_CONFIG = {
|
|
|
1669
2021
|
# Archive a skill (move to skills/.archive/) after this many days
|
|
1670
2022
|
# without use. Archived skills are recoverable — no auto-deletion.
|
|
1671
2023
|
"archive_after_days": 90,
|
|
2024
|
+
# Run the LLM consolidation (umbrella-building) pass. OFF by default.
|
|
2025
|
+
# When off, a curator run does ONLY the deterministic inactivity prune
|
|
2026
|
+
# (mark stale / archive long-unused skills) and skips the forked
|
|
2027
|
+
# aux-model review entirely — no umbrella-building, no aux-model cost.
|
|
2028
|
+
# Set to true to opt back into merging overlapping skills into
|
|
2029
|
+
# class-level umbrellas. `hermes curator run --consolidate` overrides
|
|
2030
|
+
# this for a single invocation.
|
|
2031
|
+
"consolidate": False,
|
|
1672
2032
|
# Also prune (archive) bundled built-in skills after the inactivity
|
|
1673
2033
|
# period, not just agent-created ones. ON by default. Built-ins are
|
|
1674
2034
|
# normally restored on every `hermes update`, so pruning them only
|
|
@@ -1744,6 +2104,28 @@ DEFAULT_CONFIG = {
|
|
|
1744
2104
|
# real memory cost. Default 32 MiB matches the historical hardcoded
|
|
1745
2105
|
# cap. Set to 0 for no cap. Env override: DISCORD_MAX_ATTACHMENT_BYTES.
|
|
1746
2106
|
"max_attachment_bytes": 33554432,
|
|
2107
|
+
# Voice-channel audio effects (the continuous mixer). OFF by default.
|
|
2108
|
+
# When enabled, the bot installs a software mixer on the outgoing voice
|
|
2109
|
+
# stream so a low ambient "thinking" bed, verbal acknowledgements, and
|
|
2110
|
+
# TTS replies can OVERLAP (ducking the ambient under speech) instead of
|
|
2111
|
+
# stop-and-swap — the Grok-voice-mode feel. discord.py ships no mixer;
|
|
2112
|
+
# this is implemented in plugins/platforms/discord/voice_mixer.py.
|
|
2113
|
+
"voice_fx": {
|
|
2114
|
+
"enabled": False, # master switch for the mixer subsystem
|
|
2115
|
+
"ambient_enabled": True, # play the idle "thinking" bed while tools run
|
|
2116
|
+
"ambient_path": "", # custom loop audio file; "" = synthesised pad
|
|
2117
|
+
"ambient_gain": 0.18, # idle bed loudness, 0.0–1.0
|
|
2118
|
+
"duck_gain": 0.06, # ambient loudness while speech plays
|
|
2119
|
+
"speech_gain": 1.0, # TTS / ack loudness, 0.0–1.0
|
|
2120
|
+
"ack_enabled": True, # speak a short phrase before the first tool call
|
|
2121
|
+
"ack_phrases": [ # picked at random; set [] to disable phrases
|
|
2122
|
+
"Let me look into that.",
|
|
2123
|
+
"One moment.",
|
|
2124
|
+
"Checking on that now.",
|
|
2125
|
+
"Give me a sec.",
|
|
2126
|
+
"On it.",
|
|
2127
|
+
],
|
|
2128
|
+
},
|
|
1747
2129
|
},
|
|
1748
2130
|
|
|
1749
2131
|
# WhatsApp platform settings (gateway mode)
|
|
@@ -1759,6 +2141,9 @@ DEFAULT_CONFIG = {
|
|
|
1759
2141
|
"reactions": False, # Add 👀/✅/❌ reactions to messages during processing
|
|
1760
2142
|
"channel_prompts": {}, # Per-chat/topic ephemeral system prompts (topics inherit from parent group)
|
|
1761
2143
|
"allowed_chats": "", # If set, bot ONLY responds in these group/supergroup chat IDs (whitelist)
|
|
2144
|
+
"extra": {
|
|
2145
|
+
"rich_messages": True, # Bot API 10.1 rich messages (tables/task lists/details/math) render natively; set False to force legacy MarkdownV2
|
|
2146
|
+
},
|
|
1762
2147
|
},
|
|
1763
2148
|
|
|
1764
2149
|
# Mattermost platform settings (gateway mode)
|
|
@@ -1812,6 +2197,22 @@ DEFAULT_CONFIG = {
|
|
|
1812
2197
|
# User-defined quick commands that bypass the agent loop (type: exec only)
|
|
1813
2198
|
"quick_commands": {},
|
|
1814
2199
|
|
|
2200
|
+
# Per-platform system-prompt hint overrides. Lets an admin append to or
|
|
2201
|
+
# replace Hermes' built-in platform hint for a single messaging platform
|
|
2202
|
+
# (WhatsApp, Slack, Telegram, ...) without affecting other platforms.
|
|
2203
|
+
# Useful for enterprise/managed profiles that ship platform-aware skills.
|
|
2204
|
+
# Each key is a platform name; the value is either:
|
|
2205
|
+
# { "append": "extra text" } — keep the default hint, append text
|
|
2206
|
+
# { "replace": "full text" } — substitute the default hint entirely
|
|
2207
|
+
# "extra text" — shorthand for { "append": ... }
|
|
2208
|
+
# `replace` wins over `append` if both are given. Example:
|
|
2209
|
+
# platform_hints:
|
|
2210
|
+
# whatsapp:
|
|
2211
|
+
# append: >
|
|
2212
|
+
# When tabular output would be useful, invoke the
|
|
2213
|
+
# table_formatting skill instead of emitting a Markdown table.
|
|
2214
|
+
"platform_hints": {},
|
|
2215
|
+
|
|
1815
2216
|
# Shell-script hooks — declarative bridge that invokes shell scripts
|
|
1816
2217
|
# on plugin-hook events (pre_tool_call, post_tool_call, pre_llm_call,
|
|
1817
2218
|
# subagent_stop, etc.). Each entry maps an event name to a list of
|
|
@@ -1862,6 +2263,33 @@ DEFAULT_CONFIG = {
|
|
|
1862
2263
|
},
|
|
1863
2264
|
|
|
1864
2265
|
"cron": {
|
|
2266
|
+
# Active cron SCHEDULER provider (Axis B — the trigger that decides
|
|
2267
|
+
# WHEN a due job fires). Empty string = the built-in in-process 60s
|
|
2268
|
+
# ticker (default). Name an installed provider (plugins/cron/<name>/ or
|
|
2269
|
+
# $HERMES_HOME/plugins/<name>/) to relocate the trigger — e.g. "chronos",
|
|
2270
|
+
# the NAS-mediated managed-cron provider for scale-to-zero deployments.
|
|
2271
|
+
# An unknown or unavailable provider falls back to the built-in, so cron
|
|
2272
|
+
# never loses its trigger.
|
|
2273
|
+
"provider": "",
|
|
2274
|
+
# Chronos (NAS-mediated managed cron) settings. Only consulted when
|
|
2275
|
+
# provider == "chronos". All non-secret (URLs + the JWT audience): the
|
|
2276
|
+
# agent holds NO external-scheduler credentials. For hosted agents, NAS
|
|
2277
|
+
# sets these at provision time. The outbound provision call reuses the
|
|
2278
|
+
# agent's existing Nous Portal token — there is no token key here.
|
|
2279
|
+
"chronos": {
|
|
2280
|
+
# NAS / portal base URL the agent calls to arm/cancel one-shots
|
|
2281
|
+
# and that mints the inbound fire JWT (used as the expected issuer).
|
|
2282
|
+
"portal_url": "https://portal.nousresearch.com",
|
|
2283
|
+
# The agent's OWN publicly-reachable base URL for NAS→agent fires
|
|
2284
|
+
# (NAS POSTs {callback_url}/api/cron/fire). Empty → Chronos is
|
|
2285
|
+
# unavailable and the resolver falls back to the built-in ticker.
|
|
2286
|
+
"callback_url": "",
|
|
2287
|
+
# This agent's expected JWT audience (e.g. "agent:{instance_id}").
|
|
2288
|
+
"expected_audience": "",
|
|
2289
|
+
# NAS JWKS URL for verifying the inbound fire JWT's signature.
|
|
2290
|
+
# Empty → the fire endpoint refuses all tokens (no unsigned decode).
|
|
2291
|
+
"nas_jwks_url": "",
|
|
2292
|
+
},
|
|
1865
2293
|
# Wrap delivered cron responses with a header (task name) and footer
|
|
1866
2294
|
# ("The agent cannot see this message"). Set to false for clean output.
|
|
1867
2295
|
"wrap_response": True,
|
|
@@ -1897,11 +2325,11 @@ DEFAULT_CONFIG = {
|
|
|
1897
2325
|
# raise these to keep more early failure evidence.
|
|
1898
2326
|
"worker_log_rotate_bytes": 2 * 1024 * 1024,
|
|
1899
2327
|
"worker_log_backup_count": 1,
|
|
1900
|
-
# Profile
|
|
1901
|
-
# falls back to the default profile (the
|
|
1902
|
-
# no -p flag).
|
|
1903
|
-
#
|
|
1904
|
-
#
|
|
2328
|
+
# Profile assigned to the root/orchestration task after Triage
|
|
2329
|
+
# decomposition. When unset, falls back to the default profile (the
|
|
2330
|
+
# one `hermes` launches with no -p flag). This does not control the
|
|
2331
|
+
# decomposer prompt, model, or skills; configure that LLM path under
|
|
2332
|
+
# auxiliary.kanban_decomposer.
|
|
1905
2333
|
"orchestrator_profile": "",
|
|
1906
2334
|
# Where a child task lands if the orchestrator can't match an
|
|
1907
2335
|
# assignee to any installed profile. When unset, falls back to the
|
|
@@ -2019,6 +2447,17 @@ DEFAULT_CONFIG = {
|
|
|
2019
2447
|
# Gateway settings — control how messaging platforms (Telegram, Discord,
|
|
2020
2448
|
# Slack, etc.) deliver agent-produced files as native attachments.
|
|
2021
2449
|
"gateway": {
|
|
2450
|
+
# Inject a human-readable timestamp prefix (e.g.
|
|
2451
|
+
# "[Tue 2026-04-28 13:40:53 CEST]") onto user messages IN THE MODEL'S
|
|
2452
|
+
# CONTEXT so the agent has temporal awareness of when each message was
|
|
2453
|
+
# sent. Off by default — when off, the model sees clean message text.
|
|
2454
|
+
# Persisted transcripts always stay clean (the timestamp is stored as
|
|
2455
|
+
# message metadata regardless of this toggle), so turning it on later
|
|
2456
|
+
# surfaces send-times for past messages too.
|
|
2457
|
+
"message_timestamps": {
|
|
2458
|
+
"enabled": False,
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2022
2461
|
# When false (default), any file path the agent emits is delivered
|
|
2023
2462
|
# as a native attachment as long as it isn't under the credential /
|
|
2024
2463
|
# system-path denylist (/etc, /proc, ~/.ssh, ~/.aws, ~/.hermes/.env,
|
|
@@ -2094,7 +2533,7 @@ DEFAULT_CONFIG = {
|
|
|
2094
2533
|
# delivered as a fresh message if the preview has been visible at
|
|
2095
2534
|
# least this many seconds, so the platform timestamp reflects
|
|
2096
2535
|
# completion time. Telegram only; other platforms ignore it.
|
|
2097
|
-
"fresh_final_after_seconds":
|
|
2536
|
+
"fresh_final_after_seconds": 0.0,
|
|
2098
2537
|
},
|
|
2099
2538
|
|
|
2100
2539
|
# Session storage — controls automatic cleanup of ~/.hermes/state.db.
|
|
@@ -2139,23 +2578,48 @@ DEFAULT_CONFIG = {
|
|
|
2139
2578
|
# never fires again. Users can wipe the section to re-see all hints.
|
|
2140
2579
|
"onboarding": {
|
|
2141
2580
|
"seen": {},
|
|
2581
|
+
# Structured profile-build path offered on the very first gateway
|
|
2582
|
+
# message ever. "ask" (default) -> offer to build a user profile
|
|
2583
|
+
# (opt-in, consent-gated; the agent asks before any lookup and never
|
|
2584
|
+
# reads connected accounts silently). "off" -> plain intro only.
|
|
2585
|
+
# The offer fires at most once (latched under onboarding.seen).
|
|
2586
|
+
"profile_build": "ask",
|
|
2142
2587
|
},
|
|
2143
2588
|
|
|
2144
2589
|
# ``hermes update`` behaviour.
|
|
2145
2590
|
"updates": {
|
|
2146
2591
|
# Run a full ``hermes backup``-style zip of HERMES_HOME before every
|
|
2147
2592
|
# ``hermes update``. Backups land in ``<HERMES_HOME>/backups/`` and
|
|
2148
|
-
# can be restored with ``hermes import <path>``.
|
|
2149
|
-
#
|
|
2150
|
-
#
|
|
2151
|
-
#
|
|
2152
|
-
|
|
2593
|
+
# can be restored with ``hermes import <path>``. Defaults to true
|
|
2594
|
+
# after the #48200 incident: a ``hermes update --yes`` run that
|
|
2595
|
+
# computed a wrong path silently wiped the user's ``.env``,
|
|
2596
|
+
# ``MEMORY.md``, ``kanban.db``, custom skills, and scripts in one
|
|
2597
|
+
# go. The cost of a few minutes of zip time per update is
|
|
2598
|
+
# negligible compared to the alternative. Set to false to opt
|
|
2599
|
+
# out, or pass ``--no-backup`` for a single update run.
|
|
2600
|
+
"pre_update_backup": True,
|
|
2153
2601
|
# How many pre-update backup zips to retain. Older ones are pruned
|
|
2154
2602
|
# automatically after each successful backup. Values below 1 are
|
|
2155
2603
|
# floored to 1 — the backup just created is always preserved. To
|
|
2156
2604
|
# disable backups entirely, set ``pre_update_backup: false`` above
|
|
2157
2605
|
# rather than ``backup_keep: 0``.
|
|
2158
2606
|
"backup_keep": 5,
|
|
2607
|
+
# What `hermes update` does with uncommitted local changes to the
|
|
2608
|
+
# source tree when it runs NON-interactively — i.e. triggered from
|
|
2609
|
+
# the desktop/chat app or the gateway, where there's no TTY to answer
|
|
2610
|
+
# a restore prompt. Interactive (terminal) updates are unaffected:
|
|
2611
|
+
# they always stash the changes and ask whether to restore, exactly
|
|
2612
|
+
# as they always have.
|
|
2613
|
+
# "stash" — auto-stash the changes, pull, then auto-restore them
|
|
2614
|
+
# on top of the updated code (the safe default; nothing
|
|
2615
|
+
# is ever lost — conflicts are preserved in a git stash).
|
|
2616
|
+
# "discard" — auto-stash the changes and throw the stash away after
|
|
2617
|
+
# the pull. Use this only if you never intend to keep
|
|
2618
|
+
# local edits to the source tree on this machine.
|
|
2619
|
+
# Stash-and-drop (not `reset --hard` + `clean -fd`) so
|
|
2620
|
+
# ignored paths — node_modules, venv, build outputs —
|
|
2621
|
+
# are never touched.
|
|
2622
|
+
"non_interactive_local_changes": "stash",
|
|
2159
2623
|
},
|
|
2160
2624
|
|
|
2161
2625
|
# Language Server Protocol — semantic diagnostics from real
|
|
@@ -2285,7 +2749,7 @@ DEFAULT_CONFIG = {
|
|
|
2285
2749
|
|
|
2286
2750
|
|
|
2287
2751
|
# Config schema version - bump this when adding new required fields
|
|
2288
|
-
"_config_version":
|
|
2752
|
+
"_config_version": 30,
|
|
2289
2753
|
}
|
|
2290
2754
|
|
|
2291
2755
|
# =============================================================================
|
|
@@ -3016,6 +3480,7 @@ OPTIONAL_ENV_VARS = {
|
|
|
3016
3480
|
"Required scopes: chat:write, app_mentions:read, channels:history, groups:history, "
|
|
3017
3481
|
"im:history, im:read, im:write, users:read, files:read, files:write",
|
|
3018
3482
|
"prompt": "Slack Bot Token (xoxb-...)",
|
|
3483
|
+
"help": "In your Slack app, add the required bot scopes, install the app to the workspace, then copy OAuth & Permissions > Bot User OAuth Token.",
|
|
3019
3484
|
"url": "https://api.slack.com/apps",
|
|
3020
3485
|
"password": True,
|
|
3021
3486
|
"category": "messaging",
|
|
@@ -3025,10 +3490,19 @@ OPTIONAL_ENV_VARS = {
|
|
|
3025
3490
|
"App-Level Tokens. Also ensure Event Subscriptions include: message.im, "
|
|
3026
3491
|
"message.channels, message.groups, app_mention",
|
|
3027
3492
|
"prompt": "Slack App Token (xapp-...)",
|
|
3493
|
+
"help": "In your Slack app, enable Socket Mode, then create Basic Information > App-Level Tokens with the connections:write scope.",
|
|
3028
3494
|
"url": "https://api.slack.com/apps",
|
|
3029
3495
|
"password": True,
|
|
3030
3496
|
"category": "messaging",
|
|
3031
3497
|
},
|
|
3498
|
+
"SLACK_ALLOWED_USERS": {
|
|
3499
|
+
"description": "Comma-separated Slack member IDs allowed to use Hermes, e.g. U01ABC2DEF3. Without this, Slack may connect but deny messages by default.",
|
|
3500
|
+
"prompt": "Allowed Slack member IDs",
|
|
3501
|
+
"help": "In Slack, open your profile, choose More or the three-dot menu, then Copy member ID. Add multiple IDs comma-separated.",
|
|
3502
|
+
"url": "https://api.slack.com/apps",
|
|
3503
|
+
"password": False,
|
|
3504
|
+
"category": "messaging",
|
|
3505
|
+
},
|
|
3032
3506
|
"MATTERMOST_URL": {
|
|
3033
3507
|
"description": "Mattermost server URL (e.g. https://mm.example.com)",
|
|
3034
3508
|
"prompt": "Mattermost server URL",
|
|
@@ -3341,30 +3815,13 @@ OPTIONAL_ENV_VARS = {
|
|
|
3341
3815
|
"password": True,
|
|
3342
3816
|
"category": "setting",
|
|
3343
3817
|
},
|
|
3344
|
-
"HERMES_MAX_ITERATIONS": {
|
|
3345
|
-
"description": "Maximum tool-calling iterations per conversation (default: 90)",
|
|
3346
|
-
"prompt": "Max iterations",
|
|
3347
|
-
"url": None,
|
|
3348
|
-
"password": False,
|
|
3349
|
-
"category": "setting",
|
|
3350
|
-
},
|
|
3351
3818
|
# HERMES_TOOL_PROGRESS and HERMES_TOOL_PROGRESS_MODE are deprecated —
|
|
3352
3819
|
# now configured via display.tool_progress in config.yaml (off|new|all|verbose).
|
|
3353
|
-
#
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
"password": False,
|
|
3359
|
-
"category": "setting",
|
|
3360
|
-
},
|
|
3361
|
-
"HERMES_TOOL_PROGRESS_MODE": {
|
|
3362
|
-
"description": "(deprecated) Use display.tool_progress in config.yaml instead",
|
|
3363
|
-
"prompt": "Progress mode (deprecated — use config.yaml)",
|
|
3364
|
-
"url": None,
|
|
3365
|
-
"password": False,
|
|
3366
|
-
"category": "setting",
|
|
3367
|
-
},
|
|
3820
|
+
# The gateway still falls back to these env vars for backward compatibility,
|
|
3821
|
+
# so they live in _EXTRA_ENV_KEYS (known to .env sanitization/reload) but
|
|
3822
|
+
# are intentionally NOT listed here: OPTIONAL_ENV_VARS feeds user-facing
|
|
3823
|
+
# surfaces (dashboard keys page, setup checklists) and deprecated knobs
|
|
3824
|
+
# shouldn't be offered there.
|
|
3368
3825
|
"HERMES_PREFILL_MESSAGES_FILE": {
|
|
3369
3826
|
"description": "Path to JSON file with ephemeral prefill messages for few-shot priming",
|
|
3370
3827
|
"prompt": "Prefill messages file path",
|
|
@@ -3678,6 +4135,42 @@ def _normalize_custom_provider_entry(
|
|
|
3678
4135
|
return normalized
|
|
3679
4136
|
|
|
3680
4137
|
|
|
4138
|
+
def _custom_provider_entry_to_provider_config(
|
|
4139
|
+
entry: Any,
|
|
4140
|
+
*,
|
|
4141
|
+
provider_key: str = "",
|
|
4142
|
+
) -> Optional[Dict[str, Any]]:
|
|
4143
|
+
"""Translate a legacy custom provider entry to the v12 providers shape."""
|
|
4144
|
+
normalized = _normalize_custom_provider_entry(
|
|
4145
|
+
dict(entry) if isinstance(entry, dict) else entry,
|
|
4146
|
+
provider_key=provider_key,
|
|
4147
|
+
)
|
|
4148
|
+
if normalized is None:
|
|
4149
|
+
return None
|
|
4150
|
+
|
|
4151
|
+
provider_entry: Dict[str, Any] = {"api": normalized["base_url"]}
|
|
4152
|
+
|
|
4153
|
+
for field in (
|
|
4154
|
+
"name",
|
|
4155
|
+
"api_key",
|
|
4156
|
+
"key_env",
|
|
4157
|
+
"models",
|
|
4158
|
+
"context_length",
|
|
4159
|
+
"rate_limit_delay",
|
|
4160
|
+
"discover_models",
|
|
4161
|
+
"extra_body",
|
|
4162
|
+
):
|
|
4163
|
+
if field in normalized:
|
|
4164
|
+
provider_entry[field] = normalized[field]
|
|
4165
|
+
|
|
4166
|
+
if "model" in normalized:
|
|
4167
|
+
provider_entry["default_model"] = normalized["model"]
|
|
4168
|
+
if "api_mode" in normalized:
|
|
4169
|
+
provider_entry["transport"] = normalized["api_mode"]
|
|
4170
|
+
|
|
4171
|
+
return provider_entry
|
|
4172
|
+
|
|
4173
|
+
|
|
3681
4174
|
def providers_dict_to_custom_providers(providers_dict: Any) -> List[Dict[str, Any]]:
|
|
3682
4175
|
"""Normalize ``providers`` config entries into the legacy custom-provider shape."""
|
|
3683
4176
|
if not isinstance(providers_dict, dict):
|
|
@@ -3807,15 +4300,46 @@ def get_custom_provider_context_length(
|
|
|
3807
4300
|
return None
|
|
3808
4301
|
|
|
3809
4302
|
|
|
4303
|
+
def _coerce_config_version(value: Any) -> int:
|
|
4304
|
+
"""Return a safe integer config version, treating invalid values as legacy."""
|
|
4305
|
+
if isinstance(value, bool):
|
|
4306
|
+
return 0
|
|
4307
|
+
try:
|
|
4308
|
+
version = int(value)
|
|
4309
|
+
except (TypeError, ValueError):
|
|
4310
|
+
return 0
|
|
4311
|
+
return max(version, 0)
|
|
4312
|
+
|
|
4313
|
+
|
|
3810
4314
|
def check_config_version() -> Tuple[int, int]:
|
|
3811
4315
|
"""
|
|
3812
|
-
Check config version.
|
|
3813
|
-
|
|
4316
|
+
Check the raw on-disk config schema version.
|
|
4317
|
+
|
|
4318
|
+
``load_config()`` deliberately starts from ``DEFAULT_CONFIG`` and deep-merges
|
|
4319
|
+
the user's file, which is correct for runtime reads but wrong for deciding
|
|
4320
|
+
whether the user's persisted schema has been migrated. A config file with no
|
|
4321
|
+
raw ``_config_version`` must remain visible as legacy instead of inheriting
|
|
4322
|
+
the latest default version in memory.
|
|
4323
|
+
|
|
3814
4324
|
Returns (current_version, latest_version).
|
|
3815
4325
|
"""
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
4326
|
+
latest = _coerce_config_version(DEFAULT_CONFIG.get("_config_version", 1)) or 1
|
|
4327
|
+
config_path = get_config_path()
|
|
4328
|
+
if not config_path.exists():
|
|
4329
|
+
return latest, latest
|
|
4330
|
+
|
|
4331
|
+
try:
|
|
4332
|
+
with open(config_path, encoding="utf-8") as f:
|
|
4333
|
+
config = yaml.safe_load(f) or {}
|
|
4334
|
+
except Exception as e:
|
|
4335
|
+
# Invalid YAML needs a parse warning, not an automatic schema rewrite
|
|
4336
|
+
# that could replace the user's broken file with defaults.
|
|
4337
|
+
_warn_config_parse_failure(config_path, e)
|
|
4338
|
+
return latest, latest
|
|
4339
|
+
|
|
4340
|
+
if not isinstance(config, dict):
|
|
4341
|
+
config = {}
|
|
4342
|
+
current = _coerce_config_version(config.get("_config_version"))
|
|
3819
4343
|
return current, latest
|
|
3820
4344
|
|
|
3821
4345
|
|
|
@@ -3829,7 +4353,7 @@ _KNOWN_ROOT_KEYS = {
|
|
|
3829
4353
|
"fallback_providers", "credential_pool_strategies", "toolsets",
|
|
3830
4354
|
"agent", "terminal", "display", "compression", "delegation",
|
|
3831
4355
|
"auxiliary", "custom_providers", "context", "memory", "gateway",
|
|
3832
|
-
"sessions", "streaming",
|
|
4356
|
+
"sessions", "streaming", "updates", "mcp_servers",
|
|
3833
4357
|
}
|
|
3834
4358
|
|
|
3835
4359
|
# Valid fields inside a custom_providers list entry
|
|
@@ -4153,8 +4677,7 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
|
|
4153
4677
|
if not isinstance(entry, dict):
|
|
4154
4678
|
continue
|
|
4155
4679
|
old_name = entry.get("name", "")
|
|
4156
|
-
old_url = entry.get("base_url", "") or entry.get("url", "") or ""
|
|
4157
|
-
old_key = entry.get("api_key", "")
|
|
4680
|
+
old_url = entry.get("base_url", "") or entry.get("url", "") or entry.get("api", "") or ""
|
|
4158
4681
|
if not old_url:
|
|
4159
4682
|
continue # skip entries with no URL
|
|
4160
4683
|
|
|
@@ -4174,20 +4697,22 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
|
|
4174
4697
|
key = f"endpoint-{migrated_count}"
|
|
4175
4698
|
|
|
4176
4699
|
# Don't overwrite existing entries
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
if
|
|
4188
|
-
|
|
4189
|
-
if
|
|
4190
|
-
new_entry
|
|
4700
|
+
base_key = key
|
|
4701
|
+
suffix = migrated_count
|
|
4702
|
+
while key in providers_dict:
|
|
4703
|
+
key = f"{base_key}-{suffix}"
|
|
4704
|
+
suffix += 1
|
|
4705
|
+
|
|
4706
|
+
new_entry = _custom_provider_entry_to_provider_config(
|
|
4707
|
+
entry,
|
|
4708
|
+
provider_key=key,
|
|
4709
|
+
)
|
|
4710
|
+
if new_entry is None:
|
|
4711
|
+
continue
|
|
4712
|
+
if not old_name:
|
|
4713
|
+
new_entry.pop("name", None)
|
|
4714
|
+
if new_entry.get("api_key") in {"no-key", "no-key-required", ""}:
|
|
4715
|
+
new_entry.pop("api_key", None)
|
|
4191
4716
|
|
|
4192
4717
|
providers_dict[key] = new_entry
|
|
4193
4718
|
migrated_count += 1
|
|
@@ -4508,6 +5033,89 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
|
|
4508
5033
|
if not quiet:
|
|
4509
5034
|
print(" ✓ Lowered model_catalog.ttl_hours to 1 (hourly picker refresh)")
|
|
4510
5035
|
|
|
5036
|
+
# ── Version 28 → 29: rename memory/skills write_mode → write_approval ──
|
|
5037
|
+
# The tri-state write_mode (on|off|approve) was replaced by a clear boolean
|
|
5038
|
+
# write_approval (default false = gate off, writes flow freely; true =
|
|
5039
|
+
# require approval). Only an explicit "approve" carried gating intent, so
|
|
5040
|
+
# it maps to true; everything else (on/off/unset) → false. The old
|
|
5041
|
+
# "off = block all writes" mode is dropped — memory_enabled: false disables
|
|
5042
|
+
# memory entirely. Only rewrite a key the user actually persisted; never
|
|
5043
|
+
# invent one.
|
|
5044
|
+
if current_ver < 29:
|
|
5045
|
+
config = read_raw_config()
|
|
5046
|
+
touched = False
|
|
5047
|
+
for subsystem in ("memory", "skills"):
|
|
5048
|
+
sub = config.get(subsystem)
|
|
5049
|
+
if not isinstance(sub, dict) or "write_mode" not in sub:
|
|
5050
|
+
continue
|
|
5051
|
+
old = sub.pop("write_mode")
|
|
5052
|
+
old_norm = old.strip().lower() if isinstance(old, str) else old
|
|
5053
|
+
sub["write_approval"] = (old_norm == "approve")
|
|
5054
|
+
config[subsystem] = sub
|
|
5055
|
+
touched = True
|
|
5056
|
+
results["config_added"].append(
|
|
5057
|
+
f"{subsystem}.write_mode → write_approval={sub['write_approval']}"
|
|
5058
|
+
)
|
|
5059
|
+
if touched:
|
|
5060
|
+
save_config(config)
|
|
5061
|
+
if not quiet:
|
|
5062
|
+
print(" ✓ Renamed write_mode → write_approval (boolean gate)")
|
|
5063
|
+
|
|
5064
|
+
# ── Version 29 → 30: seed curator.consolidate (default false) ──
|
|
5065
|
+
# Consolidation (the LLM umbrella-building fork) is now an opt-in toggle,
|
|
5066
|
+
# OFF by default. The deterministic inactivity prune still runs whenever
|
|
5067
|
+
# the curator is enabled; only the opinionated, aux-model-cost LLM pass is
|
|
5068
|
+
# gated. The runtime deep-merge already supplies the default, but we seed
|
|
5069
|
+
# the key so it's visible/editable in config.yaml. Existing installs that
|
|
5070
|
+
# WANT the old always-consolidate behavior must set it to true explicitly.
|
|
5071
|
+
# Only add the key when a curator section exists and lacks it — never
|
|
5072
|
+
# clobber a value the user already set.
|
|
5073
|
+
if current_ver < 30:
|
|
5074
|
+
config = read_raw_config()
|
|
5075
|
+
raw_curator = config.get("curator")
|
|
5076
|
+
if isinstance(raw_curator, dict) and "consolidate" not in raw_curator:
|
|
5077
|
+
raw_curator["consolidate"] = False
|
|
5078
|
+
config["curator"] = raw_curator
|
|
5079
|
+
save_config(config)
|
|
5080
|
+
results["config_added"].append("curator.consolidate=false")
|
|
5081
|
+
if not quiet:
|
|
5082
|
+
print(
|
|
5083
|
+
" ✓ Seeded curator.consolidate: false "
|
|
5084
|
+
"(LLM consolidation is now opt-in; pruning stays on)"
|
|
5085
|
+
)
|
|
5086
|
+
|
|
5087
|
+
# ── Post-migration: disable exfiltration-shaped MCP stdio entries ──
|
|
5088
|
+
# Users can hand-edit mcp_servers, and older installs may already contain a
|
|
5089
|
+
# malicious entry. Preserve the stanza for auditability but mark it
|
|
5090
|
+
# disabled so the next startup will not spawn it. (#45620)
|
|
5091
|
+
config = read_raw_config()
|
|
5092
|
+
raw_mcp_servers = config.get("mcp_servers")
|
|
5093
|
+
if isinstance(raw_mcp_servers, dict):
|
|
5094
|
+
try:
|
|
5095
|
+
from hermes_cli.mcp_security import validate_mcp_server_entry as _validate_mcp_server_entry
|
|
5096
|
+
except Exception:
|
|
5097
|
+
_validate_mcp_server_entry = None
|
|
5098
|
+
if _validate_mcp_server_entry:
|
|
5099
|
+
mcp_touched = False
|
|
5100
|
+
for server_name, entry in raw_mcp_servers.items():
|
|
5101
|
+
if not isinstance(entry, dict):
|
|
5102
|
+
continue
|
|
5103
|
+
issues = _validate_mcp_server_entry(server_name, entry)
|
|
5104
|
+
if not issues:
|
|
5105
|
+
continue
|
|
5106
|
+
entry["enabled"] = False
|
|
5107
|
+
mcp_touched = True
|
|
5108
|
+
results["warnings"].append(
|
|
5109
|
+
f"Disabled suspicious MCP server '{server_name}'"
|
|
5110
|
+
)
|
|
5111
|
+
if not quiet:
|
|
5112
|
+
for issue in issues:
|
|
5113
|
+
print(f" ⚠ {issue}")
|
|
5114
|
+
print(f" ⚠ Disabled MCP server '{server_name}' pending review")
|
|
5115
|
+
if mcp_touched:
|
|
5116
|
+
config["mcp_servers"] = raw_mcp_servers
|
|
5117
|
+
save_config(config)
|
|
5118
|
+
|
|
4511
5119
|
if current_ver < latest_ver and not quiet:
|
|
4512
5120
|
print(f"Config version: {current_ver} → {latest_ver}")
|
|
4513
5121
|
|
|
@@ -4680,6 +5288,29 @@ def _deep_merge(base: dict, override: dict) -> dict:
|
|
|
4680
5288
|
return result
|
|
4681
5289
|
|
|
4682
5290
|
|
|
5291
|
+
def _strip_dotted_keys(cfg: dict, dotted_keys: set) -> Tuple[dict, set]:
|
|
5292
|
+
"""Remove the given dotted leaf keys from a nested config dict.
|
|
5293
|
+
|
|
5294
|
+
Returns ``(pruned_cfg, set_of_stripped_keys_that_were_present)``. Used by
|
|
5295
|
+
``save_config`` to drop managed-scope leaves before persisting, so a bulk
|
|
5296
|
+
write never writes a user value that would lose to the managed layer on the
|
|
5297
|
+
next load. Only keys actually present in ``cfg`` are reported as stripped.
|
|
5298
|
+
"""
|
|
5299
|
+
stripped: set = set()
|
|
5300
|
+
for dotted in dotted_keys:
|
|
5301
|
+
parts = dotted.split(".")
|
|
5302
|
+
node = cfg
|
|
5303
|
+
for p in parts[:-1]:
|
|
5304
|
+
if not isinstance(node, dict) or p not in node:
|
|
5305
|
+
node = None
|
|
5306
|
+
break
|
|
5307
|
+
node = node[p]
|
|
5308
|
+
if isinstance(node, dict) and parts[-1] in node:
|
|
5309
|
+
del node[parts[-1]]
|
|
5310
|
+
stripped.add(dotted)
|
|
5311
|
+
return cfg, stripped
|
|
5312
|
+
|
|
5313
|
+
|
|
4683
5314
|
def _expand_env_vars(obj):
|
|
4684
5315
|
"""Recursively expand ``${VAR}`` references in config values.
|
|
4685
5316
|
|
|
@@ -4950,6 +5581,94 @@ def load_config_readonly() -> Dict[str, Any]:
|
|
|
4950
5581
|
return _load_config_impl(want_deepcopy=False)
|
|
4951
5582
|
|
|
4952
5583
|
|
|
5584
|
+
TERMINAL_CONFIG_ENV_MAP = {
|
|
5585
|
+
"backend": "TERMINAL_ENV",
|
|
5586
|
+
"modal_mode": "TERMINAL_MODAL_MODE",
|
|
5587
|
+
"cwd": "TERMINAL_CWD",
|
|
5588
|
+
"timeout": "TERMINAL_TIMEOUT",
|
|
5589
|
+
"lifetime_seconds": "TERMINAL_LIFETIME_SECONDS",
|
|
5590
|
+
"docker_image": "TERMINAL_DOCKER_IMAGE",
|
|
5591
|
+
"docker_forward_env": "TERMINAL_DOCKER_FORWARD_ENV",
|
|
5592
|
+
"singularity_image": "TERMINAL_SINGULARITY_IMAGE",
|
|
5593
|
+
"modal_image": "TERMINAL_MODAL_IMAGE",
|
|
5594
|
+
"daytona_image": "TERMINAL_DAYTONA_IMAGE",
|
|
5595
|
+
"ssh_host": "TERMINAL_SSH_HOST",
|
|
5596
|
+
"ssh_user": "TERMINAL_SSH_USER",
|
|
5597
|
+
"ssh_port": "TERMINAL_SSH_PORT",
|
|
5598
|
+
"ssh_key": "TERMINAL_SSH_KEY",
|
|
5599
|
+
"container_cpu": "TERMINAL_CONTAINER_CPU",
|
|
5600
|
+
"container_memory": "TERMINAL_CONTAINER_MEMORY",
|
|
5601
|
+
"container_disk": "TERMINAL_CONTAINER_DISK",
|
|
5602
|
+
"container_persistent": "TERMINAL_CONTAINER_PERSISTENT",
|
|
5603
|
+
"docker_volumes": "TERMINAL_DOCKER_VOLUMES",
|
|
5604
|
+
"docker_env": "TERMINAL_DOCKER_ENV",
|
|
5605
|
+
"docker_mount_cwd_to_workspace": "TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE",
|
|
5606
|
+
"docker_extra_args": "TERMINAL_DOCKER_EXTRA_ARGS",
|
|
5607
|
+
"docker_run_as_host_user": "TERMINAL_DOCKER_RUN_AS_HOST_USER",
|
|
5608
|
+
"docker_persist_across_processes": "TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES",
|
|
5609
|
+
"docker_orphan_reaper": "TERMINAL_DOCKER_ORPHAN_REAPER",
|
|
5610
|
+
"sandbox_dir": "TERMINAL_SANDBOX_DIR",
|
|
5611
|
+
"persistent_shell": "TERMINAL_PERSISTENT_SHELL",
|
|
5612
|
+
}
|
|
5613
|
+
|
|
5614
|
+
|
|
5615
|
+
def _terminal_env_value(value: Any) -> str:
|
|
5616
|
+
if isinstance(value, (list, dict)):
|
|
5617
|
+
return json.dumps(value)
|
|
5618
|
+
return str(value)
|
|
5619
|
+
|
|
5620
|
+
|
|
5621
|
+
def terminal_config_env_var_for_key(key: str) -> Optional[str]:
|
|
5622
|
+
"""Return the env var mirrored by a ``terminal.*`` config key."""
|
|
5623
|
+
prefix = "terminal."
|
|
5624
|
+
if not key.startswith(prefix):
|
|
5625
|
+
return None
|
|
5626
|
+
return TERMINAL_CONFIG_ENV_MAP.get(key[len(prefix):])
|
|
5627
|
+
|
|
5628
|
+
|
|
5629
|
+
def apply_terminal_config_to_env(
|
|
5630
|
+
*,
|
|
5631
|
+
env: Optional[Dict[str, str]] = None,
|
|
5632
|
+
config: Optional[Dict[str, Any]] = None,
|
|
5633
|
+
override: Optional[bool] = None,
|
|
5634
|
+
) -> Dict[str, str]:
|
|
5635
|
+
"""Bridge ``terminal.*`` config into the env vars terminal tools read.
|
|
5636
|
+
|
|
5637
|
+
``tools.terminal_tool`` is intentionally environment-driven because it also
|
|
5638
|
+
runs in child processes (TUI, dashboard PTY, gateway workers). This helper
|
|
5639
|
+
gives those child-process launch paths the same config bridge as classic
|
|
5640
|
+
CLI without importing ``cli.py`` and paying for its startup side effects.
|
|
5641
|
+
|
|
5642
|
+
When the user config contains a ``terminal`` section, config.yaml is
|
|
5643
|
+
authoritative and overrides existing env values. Otherwise defaults only
|
|
5644
|
+
backfill missing env vars so exported/.env values keep working.
|
|
5645
|
+
"""
|
|
5646
|
+
target = os.environ if env is None else env
|
|
5647
|
+
|
|
5648
|
+
raw_config = read_raw_config()
|
|
5649
|
+
file_has_terminal_config = isinstance(raw_config.get("terminal"), dict)
|
|
5650
|
+
should_override = file_has_terminal_config if override is None else override
|
|
5651
|
+
|
|
5652
|
+
cfg = config if config is not None else load_config_readonly()
|
|
5653
|
+
terminal_cfg = cfg.get("terminal", {}) if isinstance(cfg, dict) else {}
|
|
5654
|
+
if not isinstance(terminal_cfg, dict):
|
|
5655
|
+
return target
|
|
5656
|
+
|
|
5657
|
+
for cfg_key, env_var in TERMINAL_CONFIG_ENV_MAP.items():
|
|
5658
|
+
if cfg_key not in terminal_cfg:
|
|
5659
|
+
continue
|
|
5660
|
+
value = terminal_cfg[cfg_key]
|
|
5661
|
+
if cfg_key == "cwd":
|
|
5662
|
+
raw_cwd = str(value or "").strip()
|
|
5663
|
+
if raw_cwd in {".", "auto", "cwd"}:
|
|
5664
|
+
continue
|
|
5665
|
+
if isinstance(value, str):
|
|
5666
|
+
value = os.path.expanduser(value)
|
|
5667
|
+
if should_override or env_var not in target:
|
|
5668
|
+
target[env_var] = _terminal_env_value(value)
|
|
5669
|
+
return target
|
|
5670
|
+
|
|
5671
|
+
|
|
4953
5672
|
def _load_config_impl(*, want_deepcopy: bool) -> Dict[str, Any]:
|
|
4954
5673
|
with _CONFIG_LOCK:
|
|
4955
5674
|
ensure_hermes_home()
|
|
@@ -4958,17 +5677,44 @@ def _load_config_impl(*, want_deepcopy: bool) -> Dict[str, Any]:
|
|
|
4958
5677
|
|
|
4959
5678
|
try:
|
|
4960
5679
|
st = config_path.stat()
|
|
4961
|
-
|
|
5680
|
+
user_sig: Optional[Tuple[int, int]] = (st.st_mtime_ns, st.st_size)
|
|
4962
5681
|
except FileNotFoundError:
|
|
4963
|
-
|
|
5682
|
+
user_sig = None
|
|
5683
|
+
|
|
5684
|
+
# Managed scope: fold the managed config file's (mtime, size) into the
|
|
5685
|
+
# cache signature so editing /etc/hermes/config.yaml invalidates the
|
|
5686
|
+
# cached merged result. (0, 0) means "no managed config file".
|
|
5687
|
+
from hermes_cli import managed_scope
|
|
5688
|
+
|
|
5689
|
+
managed_dir = managed_scope.get_managed_dir()
|
|
5690
|
+
managed_cfg_path = (managed_dir / "config.yaml") if managed_dir else None
|
|
5691
|
+
try:
|
|
5692
|
+
mst = managed_cfg_path.stat() if managed_cfg_path else None
|
|
5693
|
+
managed_sig = (mst.st_mtime_ns, mst.st_size) if mst else (0, 0)
|
|
5694
|
+
except OSError:
|
|
5695
|
+
managed_sig = (0, 0)
|
|
5696
|
+
|
|
5697
|
+
# Combined cache signature: user file + managed file. None only when the
|
|
5698
|
+
# user config is absent AND no managed file exists (nothing to cache on).
|
|
5699
|
+
if user_sig is not None:
|
|
5700
|
+
cache_sig: Optional[Tuple[int, int, int, int]] = (
|
|
5701
|
+
user_sig[0],
|
|
5702
|
+
user_sig[1],
|
|
5703
|
+
managed_sig[0],
|
|
5704
|
+
managed_sig[1],
|
|
5705
|
+
)
|
|
5706
|
+
elif managed_sig != (0, 0):
|
|
5707
|
+
cache_sig = (0, 0, managed_sig[0], managed_sig[1])
|
|
5708
|
+
else:
|
|
5709
|
+
cache_sig = None
|
|
4964
5710
|
|
|
4965
5711
|
cached = _LOAD_CONFIG_CACHE.get(path_key)
|
|
4966
|
-
if cached is not None and
|
|
4967
|
-
return copy.deepcopy(cached[
|
|
5712
|
+
if cached is not None and cache_sig is not None and cached[:4] == cache_sig:
|
|
5713
|
+
return copy.deepcopy(cached[4]) if want_deepcopy else cached[4]
|
|
4968
5714
|
|
|
4969
5715
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
|
4970
5716
|
|
|
4971
|
-
if
|
|
5717
|
+
if user_sig is not None:
|
|
4972
5718
|
try:
|
|
4973
5719
|
with open(config_path, encoding="utf-8") as f:
|
|
4974
5720
|
user_config = yaml.safe_load(f) or {}
|
|
@@ -4986,14 +5732,24 @@ def _load_config_impl(*, want_deepcopy: bool) -> Dict[str, Any]:
|
|
|
4986
5732
|
|
|
4987
5733
|
normalized = _normalize_root_model_keys(_normalize_max_turns_config(config))
|
|
4988
5734
|
expanded = _expand_env_vars(normalized)
|
|
5735
|
+
# Managed scope wins at the leaf. Applied AFTER user expansion so a user
|
|
5736
|
+
# ${VAR} cannot shadow a managed literal: managed values are expanded only
|
|
5737
|
+
# against the process environment, never against user-config-defined refs.
|
|
5738
|
+
# This deliberately inverts the usual env-over-config precedence for the
|
|
5739
|
+
# keys the managed layer pins — see docs/design/managed-scope.md §4.1.
|
|
5740
|
+
managed_config = managed_scope.load_managed_config()
|
|
5741
|
+
if managed_config:
|
|
5742
|
+
managed_expanded = _expand_env_vars(managed_config)
|
|
5743
|
+
expanded = _deep_merge(expanded, managed_expanded)
|
|
4989
5744
|
_LAST_EXPANDED_CONFIG_BY_PATH[path_key] = copy.deepcopy(expanded)
|
|
4990
|
-
if
|
|
5745
|
+
if cache_sig is not None:
|
|
4991
5746
|
# Cache stores a separate deepcopy so subsequent ``load_config()``
|
|
4992
5747
|
# (deepcopy=True) callers can mutate freely without affecting the
|
|
4993
5748
|
# cached value, and ``load_config_readonly()`` (deepcopy=False)
|
|
4994
|
-
# callers all see the same stable cached object.
|
|
5749
|
+
# callers all see the same stable cached object. The cached tuple is
|
|
5750
|
+
# (user_mtime, user_size, managed_mtime, managed_size, value).
|
|
4995
5751
|
cached_copy = copy.deepcopy(expanded)
|
|
4996
|
-
_LOAD_CONFIG_CACHE[path_key] = (
|
|
5752
|
+
_LOAD_CONFIG_CACHE[path_key] = (*cache_sig, cached_copy)
|
|
4997
5753
|
# On the readonly path return the same cached object subsequent
|
|
4998
5754
|
# calls will see — keeps "two readonly calls return the same
|
|
4999
5755
|
# object" invariant that callers may rely on for identity checks.
|
|
@@ -5090,6 +5846,22 @@ def save_config(config: Dict[str, Any]):
|
|
|
5090
5846
|
if is_managed():
|
|
5091
5847
|
managed_error("save configuration")
|
|
5092
5848
|
return
|
|
5849
|
+
# Managed scope: strip any leaf the managed layer pins, so a bulk write
|
|
5850
|
+
# (wizard / programmatic save) never persists a user value that would
|
|
5851
|
+
# silently lose to managed on the next load. Single-key `config set`
|
|
5852
|
+
# hard-rejects (see set_config_value); this is the mechanical safety net
|
|
5853
|
+
# for bulk writes so the unmanaged remainder still lands.
|
|
5854
|
+
from hermes_cli import managed_scope
|
|
5855
|
+
|
|
5856
|
+
managed_keys = managed_scope.managed_config_keys()
|
|
5857
|
+
if managed_keys:
|
|
5858
|
+
config, _stripped = _strip_dotted_keys(copy.deepcopy(config), managed_keys)
|
|
5859
|
+
if _stripped:
|
|
5860
|
+
print(
|
|
5861
|
+
f"Note: {len(_stripped)} managed setting(s) were not saved "
|
|
5862
|
+
f"(managed by your administrator): {', '.join(sorted(_stripped))}",
|
|
5863
|
+
file=sys.stderr,
|
|
5864
|
+
)
|
|
5093
5865
|
from utils import atomic_yaml_write
|
|
5094
5866
|
|
|
5095
5867
|
ensure_hermes_home()
|
|
@@ -5356,6 +6128,19 @@ def save_env_value(key: str, value: str):
|
|
|
5356
6128
|
if is_managed():
|
|
5357
6129
|
managed_error(f"set {key}")
|
|
5358
6130
|
return
|
|
6131
|
+
# Managed scope guard: a managed env key can't be set by the user — the
|
|
6132
|
+
# managed .env wins at load anyway. Distinct from is_managed() above.
|
|
6133
|
+
from hermes_cli import managed_scope
|
|
6134
|
+
|
|
6135
|
+
if managed_scope.is_env_managed(key):
|
|
6136
|
+
managed_dir = managed_scope.get_managed_dir()
|
|
6137
|
+
src = (managed_dir / ".env") if managed_dir else "the managed scope"
|
|
6138
|
+
print(
|
|
6139
|
+
f"Cannot set {key}: it is managed by your administrator ({src}) "
|
|
6140
|
+
f"and cannot be changed.",
|
|
6141
|
+
file=sys.stderr,
|
|
6142
|
+
)
|
|
6143
|
+
return
|
|
5359
6144
|
if not _ENV_VAR_NAME_RE.match(key):
|
|
5360
6145
|
raise ValueError(f"Invalid environment variable name: {key!r}")
|
|
5361
6146
|
_reject_denylisted_env_var(key)
|
|
@@ -5405,19 +6190,21 @@ def save_env_value(key: str, value: str):
|
|
|
5405
6190
|
f.flush()
|
|
5406
6191
|
os.fsync(f.fileno())
|
|
5407
6192
|
atomic_replace(tmp_path, env_path)
|
|
5408
|
-
#
|
|
6193
|
+
# Preserve the original file mode (e.g. 0640 for Docker volume mounts)
|
|
6194
|
+
# instead of letting _secure_file unconditionally tighten to 0600.
|
|
5409
6195
|
if original_mode is not None:
|
|
5410
6196
|
try:
|
|
5411
6197
|
os.chmod(env_path, original_mode)
|
|
5412
6198
|
except OSError:
|
|
5413
6199
|
pass
|
|
6200
|
+
else:
|
|
6201
|
+
_secure_file(env_path)
|
|
5414
6202
|
except BaseException:
|
|
5415
6203
|
try:
|
|
5416
6204
|
os.unlink(tmp_path)
|
|
5417
6205
|
except OSError:
|
|
5418
6206
|
pass
|
|
5419
6207
|
raise
|
|
5420
|
-
_secure_file(env_path)
|
|
5421
6208
|
|
|
5422
6209
|
os.environ[key] = value
|
|
5423
6210
|
invalidate_env_cache()
|
|
@@ -5431,6 +6218,18 @@ def remove_env_value(key: str) -> bool:
|
|
|
5431
6218
|
if is_managed():
|
|
5432
6219
|
managed_error(f"remove {key}")
|
|
5433
6220
|
return False
|
|
6221
|
+
# Managed scope guard: a managed env key can't be removed by the user.
|
|
6222
|
+
from hermes_cli import managed_scope
|
|
6223
|
+
|
|
6224
|
+
if managed_scope.is_env_managed(key):
|
|
6225
|
+
managed_dir = managed_scope.get_managed_dir()
|
|
6226
|
+
src = (managed_dir / ".env") if managed_dir else "the managed scope"
|
|
6227
|
+
print(
|
|
6228
|
+
f"Cannot remove {key}: it is managed by your administrator ({src}) "
|
|
6229
|
+
f"and cannot be changed.",
|
|
6230
|
+
file=sys.stderr,
|
|
6231
|
+
)
|
|
6232
|
+
return False
|
|
5434
6233
|
if not _ENV_VAR_NAME_RE.match(key):
|
|
5435
6234
|
raise ValueError(f"Invalid environment variable name: {key!r}")
|
|
5436
6235
|
env_path = get_env_path()
|
|
@@ -5462,18 +6261,22 @@ def remove_env_value(key: str) -> bool:
|
|
|
5462
6261
|
f.flush()
|
|
5463
6262
|
os.fsync(f.fileno())
|
|
5464
6263
|
atomic_replace(tmp_path, env_path)
|
|
6264
|
+
# Preserve the original file mode (e.g. 0640 for Docker volume
|
|
6265
|
+
# mounts) instead of letting _secure_file unconditionally tighten
|
|
6266
|
+
# to 0600. Mirrors save_env_value().
|
|
5465
6267
|
if original_mode is not None:
|
|
5466
6268
|
try:
|
|
5467
6269
|
os.chmod(env_path, original_mode)
|
|
5468
6270
|
except OSError:
|
|
5469
6271
|
pass
|
|
6272
|
+
else:
|
|
6273
|
+
_secure_file(env_path)
|
|
5470
6274
|
except BaseException:
|
|
5471
6275
|
try:
|
|
5472
6276
|
os.unlink(tmp_path)
|
|
5473
6277
|
except OSError:
|
|
5474
6278
|
pass
|
|
5475
6279
|
raise
|
|
5476
|
-
_secure_file(env_path)
|
|
5477
6280
|
|
|
5478
6281
|
os.environ.pop(key, None)
|
|
5479
6282
|
invalidate_env_cache()
|
|
@@ -5561,12 +6364,38 @@ def redact_key(key: str) -> str:
|
|
|
5561
6364
|
def show_config():
|
|
5562
6365
|
"""Display current configuration."""
|
|
5563
6366
|
config = load_config()
|
|
5564
|
-
|
|
6367
|
+
|
|
5565
6368
|
print()
|
|
5566
6369
|
print(color("┌─────────────────────────────────────────────────────────┐", Colors.CYAN))
|
|
5567
6370
|
print(color("│ ⚕ Hermes Configuration │", Colors.CYAN))
|
|
5568
6371
|
print(color("└─────────────────────────────────────────────────────────┘", Colors.CYAN))
|
|
5569
|
-
|
|
6372
|
+
|
|
6373
|
+
# Managed scope: surface that some settings are administrator-pinned so the
|
|
6374
|
+
# user understands why their config.yaml value may not be the effective one.
|
|
6375
|
+
from hermes_cli import managed_scope
|
|
6376
|
+
|
|
6377
|
+
_managed_keys = managed_scope.managed_config_keys()
|
|
6378
|
+
_managed_env = managed_scope.load_managed_env()
|
|
6379
|
+
if _managed_keys or _managed_env:
|
|
6380
|
+
_managed_dir = managed_scope.get_managed_dir()
|
|
6381
|
+
print()
|
|
6382
|
+
print(color(
|
|
6383
|
+
f" ⚷ Some settings are managed by your administrator ({_managed_dir}) "
|
|
6384
|
+
f"and cannot be changed",
|
|
6385
|
+
Colors.YELLOW,
|
|
6386
|
+
Colors.BOLD,
|
|
6387
|
+
))
|
|
6388
|
+
if _managed_keys:
|
|
6389
|
+
print(color(
|
|
6390
|
+
f" Managed config keys: {', '.join(sorted(_managed_keys))}",
|
|
6391
|
+
Colors.YELLOW,
|
|
6392
|
+
))
|
|
6393
|
+
if _managed_env:
|
|
6394
|
+
print(color(
|
|
6395
|
+
f" Managed env keys: {', '.join(sorted(_managed_env))}",
|
|
6396
|
+
Colors.YELLOW,
|
|
6397
|
+
))
|
|
6398
|
+
|
|
5570
6399
|
# Paths
|
|
5571
6400
|
print()
|
|
5572
6401
|
print(color("◆ Paths", Colors.CYAN, Colors.BOLD))
|
|
@@ -5601,7 +6430,21 @@ def show_config():
|
|
|
5601
6430
|
print()
|
|
5602
6431
|
print(color("◆ Model", Colors.CYAN, Colors.BOLD))
|
|
5603
6432
|
print(f" Model: {config.get('model', 'not set')}")
|
|
5604
|
-
|
|
6433
|
+
_cfg_max_turns = config.get('agent', {}).get('max_turns', DEFAULT_CONFIG['agent']['max_turns'])
|
|
6434
|
+
print(f" Max turns: {_cfg_max_turns}")
|
|
6435
|
+
# Warn on stale HERMES_MAX_ITERATIONS ghost in .env that disagrees with
|
|
6436
|
+
# config.yaml (issue #17534). Read the .env FILE directly so we catch the
|
|
6437
|
+
# ghost even when the gateway bridge already overrode os.environ.
|
|
6438
|
+
try:
|
|
6439
|
+
_env_ghost = load_env().get("HERMES_MAX_ITERATIONS")
|
|
6440
|
+
if _env_ghost is not None and str(_env_ghost).strip() != str(_cfg_max_turns).strip():
|
|
6441
|
+
print(color(
|
|
6442
|
+
f" ⚠ .env has stale HERMES_MAX_ITERATIONS={_env_ghost} "
|
|
6443
|
+
f"(run 'hermes doctor --fix' to remove)",
|
|
6444
|
+
Colors.YELLOW,
|
|
6445
|
+
))
|
|
6446
|
+
except Exception:
|
|
6447
|
+
pass
|
|
5605
6448
|
|
|
5606
6449
|
# Display
|
|
5607
6450
|
print()
|
|
@@ -5770,6 +6613,22 @@ def set_config_value(key: str, value: str):
|
|
|
5770
6613
|
if is_managed():
|
|
5771
6614
|
managed_error("set configuration values")
|
|
5772
6615
|
return
|
|
6616
|
+
# Managed scope guard (D2): a key pinned by the managed layer cannot be set by
|
|
6617
|
+
# the user — the next load would override it anyway. Hard-reject and name the
|
|
6618
|
+
# source. Distinct from is_managed() above (the package-manager write-lock).
|
|
6619
|
+
# Env-shaped keys (API keys / tokens) route to save_env_value below, which has
|
|
6620
|
+
# its own managed-env-key guard; this catches the config.yaml keys.
|
|
6621
|
+
from hermes_cli import managed_scope
|
|
6622
|
+
|
|
6623
|
+
if managed_scope.is_key_managed(key):
|
|
6624
|
+
managed_dir = managed_scope.get_managed_dir()
|
|
6625
|
+
src = (managed_dir / "config.yaml") if managed_dir else "the managed scope"
|
|
6626
|
+
print(
|
|
6627
|
+
f"Cannot set '{key}': it is managed by your administrator ({src}) "
|
|
6628
|
+
f"and cannot be changed. Contact your administrator to modify it.",
|
|
6629
|
+
file=sys.stderr,
|
|
6630
|
+
)
|
|
6631
|
+
sys.exit(1)
|
|
5773
6632
|
# Check if it's an API key (goes to .env)
|
|
5774
6633
|
api_keys = [
|
|
5775
6634
|
'OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'VOICE_TOOLS_OPENAI_KEY',
|
|
@@ -5824,31 +6683,9 @@ def set_config_value(key: str, value: str):
|
|
|
5824
6683
|
|
|
5825
6684
|
# Keep .env in sync for keys that terminal_tool reads directly from env vars.
|
|
5826
6685
|
# config.yaml is authoritative, but terminal_tool only reads TERMINAL_ENV etc.
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
"terminal.docker_image": "TERMINAL_DOCKER_IMAGE",
|
|
5831
|
-
"terminal.singularity_image": "TERMINAL_SINGULARITY_IMAGE",
|
|
5832
|
-
"terminal.modal_image": "TERMINAL_MODAL_IMAGE",
|
|
5833
|
-
"terminal.daytona_image": "TERMINAL_DAYTONA_IMAGE",
|
|
5834
|
-
"terminal.docker_mount_cwd_to_workspace": "TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE",
|
|
5835
|
-
"terminal.docker_run_as_host_user": "TERMINAL_DOCKER_RUN_AS_HOST_USER",
|
|
5836
|
-
"terminal.docker_persist_across_processes": "TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSES",
|
|
5837
|
-
"terminal.docker_orphan_reaper": "TERMINAL_DOCKER_ORPHAN_REAPER",
|
|
5838
|
-
"terminal.docker_env": "TERMINAL_DOCKER_ENV",
|
|
5839
|
-
# terminal.cwd intentionally excluded — CLI resolves at runtime,
|
|
5840
|
-
# gateway bridges it in gateway/run.py. Persisting to .env causes
|
|
5841
|
-
# stale values to poison child processes.
|
|
5842
|
-
"terminal.timeout": "TERMINAL_TIMEOUT",
|
|
5843
|
-
"terminal.sandbox_dir": "TERMINAL_SANDBOX_DIR",
|
|
5844
|
-
"terminal.persistent_shell": "TERMINAL_PERSISTENT_SHELL",
|
|
5845
|
-
"terminal.container_cpu": "TERMINAL_CONTAINER_CPU",
|
|
5846
|
-
"terminal.container_memory": "TERMINAL_CONTAINER_MEMORY",
|
|
5847
|
-
"terminal.container_disk": "TERMINAL_CONTAINER_DISK",
|
|
5848
|
-
"terminal.container_persistent": "TERMINAL_CONTAINER_PERSISTENT",
|
|
5849
|
-
}
|
|
5850
|
-
if key in _config_to_env_sync:
|
|
5851
|
-
save_env_value(_config_to_env_sync[key], str(value))
|
|
6686
|
+
env_var = terminal_config_env_var_for_key(key)
|
|
6687
|
+
if env_var and key != "terminal.cwd":
|
|
6688
|
+
save_env_value(env_var, _terminal_env_value(value))
|
|
5852
6689
|
|
|
5853
6690
|
print(f"✓ Set {key} = {value} in {config_path}")
|
|
5854
6691
|
|