@clawpump/claw-agent 0.1.5 → 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 +2177 -3162
- 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/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +215 -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/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 +4 -2
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
- 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 +3 -1
- 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 +300 -684
- 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
package/agent/run_agent.py
CHANGED
|
@@ -45,7 +45,7 @@ import tempfile
|
|
|
45
45
|
import time
|
|
46
46
|
import threading
|
|
47
47
|
import uuid
|
|
48
|
-
from typing import List, Dict, Any, Optional
|
|
48
|
+
from typing import List, Dict, Any, Optional, Callable
|
|
49
49
|
# NOTE: `from openai import OpenAI` is deliberately NOT at module top — the
|
|
50
50
|
# SDK pulls ~240 ms of imports. We expose `OpenAI` as a thin proxy object
|
|
51
51
|
# that imports the SDK on first call/isinstance check. This preserves:
|
|
@@ -60,9 +60,35 @@ from typing import List, Dict, Any, Optional
|
|
|
60
60
|
# ModuleNotFoundError on broken/partial installs where `fire` isn't present.
|
|
61
61
|
from datetime import datetime
|
|
62
62
|
from pathlib import Path
|
|
63
|
+
from types import SimpleNamespace
|
|
63
64
|
|
|
64
65
|
from hermes_constants import get_hermes_home
|
|
65
66
|
|
|
67
|
+
|
|
68
|
+
def _launch_cwd_for_session(source: str) -> Optional[str]:
|
|
69
|
+
"""Working directory to stamp on a new session row, or None.
|
|
70
|
+
|
|
71
|
+
Only local CLI sessions get a recorded cwd: the directory the process was
|
|
72
|
+
launched from is meaningful for ``hermes -c`` / ``--resume`` (relaunch
|
|
73
|
+
where you left off). Gateway/cron/remote-backend sessions have no stable
|
|
74
|
+
host cwd to restore, so they record nothing.
|
|
75
|
+
|
|
76
|
+
``TERMINAL_ENV`` is set by the CLI's config bridge (``load_cli_config``);
|
|
77
|
+
a non-"local" backend (docker/ssh/modal/...) means the host cwd is
|
|
78
|
+
irrelevant to the agent's tools, so we skip it there too.
|
|
79
|
+
"""
|
|
80
|
+
if source != "cli":
|
|
81
|
+
return None
|
|
82
|
+
backend = (os.environ.get("TERMINAL_ENV") or "local").strip().lower()
|
|
83
|
+
if backend and backend != "local":
|
|
84
|
+
return None
|
|
85
|
+
try:
|
|
86
|
+
return os.getcwd()
|
|
87
|
+
except OSError:
|
|
88
|
+
# cwd was unlinked out from under us — nothing meaningful to record.
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
66
92
|
# OpenAI lazy proxy + safe stdio + proxy URL helpers — see agent/process_bootstrap.py.
|
|
67
93
|
# `OpenAI` is re-exported here so `patch("run_agent.OpenAI", ...)` in tests works.
|
|
68
94
|
# The other `# noqa: F401` re-exports below cover names accessed via
|
|
@@ -143,7 +169,7 @@ from agent.codex_responses_adapter import (
|
|
|
143
169
|
_derive_responses_function_call_id as _codex_derive_responses_function_call_id,
|
|
144
170
|
_deterministic_call_id as _codex_deterministic_call_id,
|
|
145
171
|
_split_responses_tool_id as _codex_split_responses_tool_id,
|
|
146
|
-
_summarize_user_message_for_log, #
|
|
172
|
+
_summarize_user_message_for_log, # also used by _sync_external_memory_for_turn (memory boundary)
|
|
147
173
|
)
|
|
148
174
|
from agent.tool_guardrails import (
|
|
149
175
|
ToolGuardrailDecision,
|
|
@@ -170,7 +196,7 @@ from agent.tool_dispatch_helpers import (
|
|
|
170
196
|
_extract_error_preview,
|
|
171
197
|
_trajectory_normalize_msg, # noqa: F401 # re-exported for tests that `from run_agent import _trajectory_normalize_msg`
|
|
172
198
|
)
|
|
173
|
-
from utils import atomic_json_write, base_url_host_matches, base_url_hostname
|
|
199
|
+
from utils import atomic_json_write, base_url_host_matches, base_url_hostname, is_truthy_value, model_forces_max_completion_tokens
|
|
174
200
|
|
|
175
201
|
|
|
176
202
|
|
|
@@ -332,6 +358,7 @@ class AIAgent:
|
|
|
332
358
|
save_trajectories: bool = False,
|
|
333
359
|
verbose_logging: bool = False,
|
|
334
360
|
quiet_mode: bool = False,
|
|
361
|
+
tool_progress_mode: str = "all",
|
|
335
362
|
ephemeral_system_prompt: str = None,
|
|
336
363
|
log_prefix_chars: int = 100,
|
|
337
364
|
log_prefix: str = "",
|
|
@@ -349,11 +376,15 @@ class AIAgent:
|
|
|
349
376
|
thinking_callback: callable = None,
|
|
350
377
|
reasoning_callback: callable = None,
|
|
351
378
|
clarify_callback: callable = None,
|
|
379
|
+
read_terminal_callback: callable = None,
|
|
352
380
|
step_callback: callable = None,
|
|
353
381
|
stream_delta_callback: callable = None,
|
|
354
382
|
interim_assistant_callback: callable = None,
|
|
355
383
|
tool_gen_callback: callable = None,
|
|
356
384
|
status_callback: callable = None,
|
|
385
|
+
notice_callback: callable = None,
|
|
386
|
+
notice_clear_callback: callable = None,
|
|
387
|
+
event_callback: Optional[Callable[[str, dict], None]] = None,
|
|
357
388
|
max_tokens: int = None,
|
|
358
389
|
reasoning_config: Dict[str, Any] = None,
|
|
359
390
|
service_tier: str = None,
|
|
@@ -402,6 +433,7 @@ class AIAgent:
|
|
|
402
433
|
save_trajectories=save_trajectories,
|
|
403
434
|
verbose_logging=verbose_logging,
|
|
404
435
|
quiet_mode=quiet_mode,
|
|
436
|
+
tool_progress_mode=tool_progress_mode,
|
|
405
437
|
ephemeral_system_prompt=ephemeral_system_prompt,
|
|
406
438
|
log_prefix_chars=log_prefix_chars,
|
|
407
439
|
log_prefix=log_prefix,
|
|
@@ -419,11 +451,15 @@ class AIAgent:
|
|
|
419
451
|
thinking_callback=thinking_callback,
|
|
420
452
|
reasoning_callback=reasoning_callback,
|
|
421
453
|
clarify_callback=clarify_callback,
|
|
454
|
+
read_terminal_callback=read_terminal_callback,
|
|
422
455
|
step_callback=step_callback,
|
|
423
456
|
stream_delta_callback=stream_delta_callback,
|
|
424
457
|
interim_assistant_callback=interim_assistant_callback,
|
|
425
458
|
tool_gen_callback=tool_gen_callback,
|
|
426
459
|
status_callback=status_callback,
|
|
460
|
+
notice_callback=notice_callback,
|
|
461
|
+
notice_clear_callback=notice_clear_callback,
|
|
462
|
+
event_callback=event_callback,
|
|
427
463
|
max_tokens=max_tokens,
|
|
428
464
|
reasoning_config=reasoning_config,
|
|
429
465
|
service_tier=service_tier,
|
|
@@ -476,15 +512,17 @@ class AIAgent:
|
|
|
476
512
|
"""Create session DB row on first use. Disables _session_db on failure."""
|
|
477
513
|
if self._session_db_created or not self._session_db:
|
|
478
514
|
return
|
|
515
|
+
source = self.platform or os.environ.get("HERMES_SESSION_SOURCE", "cli")
|
|
479
516
|
try:
|
|
480
517
|
self._session_db.create_session(
|
|
481
518
|
session_id=self.session_id,
|
|
482
|
-
source=
|
|
519
|
+
source=source,
|
|
483
520
|
model=self.model,
|
|
484
521
|
model_config=self._session_init_model_config,
|
|
485
522
|
system_prompt=self._cached_system_prompt,
|
|
486
523
|
user_id=None,
|
|
487
524
|
parent_session_id=self._parent_session_id,
|
|
525
|
+
cwd=_launch_cwd_for_session(source),
|
|
488
526
|
)
|
|
489
527
|
self._session_db_created = True
|
|
490
528
|
except Exception as e:
|
|
@@ -768,6 +806,27 @@ class AIAgent:
|
|
|
768
806
|
except Exception:
|
|
769
807
|
logger.debug("status_callback error in _emit_warning", exc_info=True)
|
|
770
808
|
|
|
809
|
+
def _emit_notice(self, notice) -> None:
|
|
810
|
+
"""Fire a structured ``AgentNotice`` to the active driver (TUI / CLI).
|
|
811
|
+
|
|
812
|
+
Driver-agnostic: the bound ``notice_callback`` renders it however that
|
|
813
|
+
driver does (TUI status-bar override, CLI console line). Swallows all
|
|
814
|
+
callback errors — a notice must NEVER break the agent loop (D-D fail-open).
|
|
815
|
+
"""
|
|
816
|
+
if self.notice_callback:
|
|
817
|
+
try:
|
|
818
|
+
self.notice_callback(notice)
|
|
819
|
+
except Exception:
|
|
820
|
+
logger.debug("notice_callback error in _emit_notice", exc_info=True)
|
|
821
|
+
|
|
822
|
+
def _emit_notice_clear(self, key: str) -> None:
|
|
823
|
+
"""Clear a previously-fired sticky notice by ``key`` (e.g. on recovery)."""
|
|
824
|
+
if self.notice_clear_callback:
|
|
825
|
+
try:
|
|
826
|
+
self.notice_clear_callback(key)
|
|
827
|
+
except Exception:
|
|
828
|
+
logger.debug("notice_clear_callback error in _emit_notice_clear", exc_info=True)
|
|
829
|
+
|
|
771
830
|
# ── Buffered retry/fallback status ────────────────────────────────────
|
|
772
831
|
# Retry and fallback chains were flooding the CLI/gateway with status
|
|
773
832
|
# noise that users found confusing: a single transient 429 could produce
|
|
@@ -1196,13 +1255,24 @@ class AIAgent:
|
|
|
1196
1255
|
def _max_tokens_param(self, value: int) -> dict:
|
|
1197
1256
|
"""Return the correct max tokens kwarg for the current provider.
|
|
1198
1257
|
|
|
1199
|
-
OpenAI's newer models (gpt-4o,
|
|
1200
|
-
'max_completion_tokens'. Azure OpenAI also
|
|
1201
|
-
'max_completion_tokens' for
|
|
1202
|
-
OpenAI-compatible
|
|
1258
|
+
OpenAI's newer models (gpt-4o, gpt-4.1, gpt-5+, o-series) require
|
|
1259
|
+
'max_completion_tokens'. Azure OpenAI and GitHub Copilot also require
|
|
1260
|
+
'max_completion_tokens' for those families served via their
|
|
1261
|
+
OpenAI-compatible endpoints. OpenRouter, local models, and older
|
|
1203
1262
|
OpenAI models use 'max_tokens'.
|
|
1263
|
+
|
|
1264
|
+
The check is URL-first (api.openai.com / Azure / Copilot all use the
|
|
1265
|
+
new kwarg), then falls back to a model-name check so third-party
|
|
1266
|
+
OpenAI-compatible endpoints fronting those models are recognised —
|
|
1267
|
+
URL-only detection misses that case and silently sends the wrong
|
|
1268
|
+
kwarg, which the upstream model rejects with a 400.
|
|
1204
1269
|
"""
|
|
1205
|
-
if
|
|
1270
|
+
if (
|
|
1271
|
+
self._is_direct_openai_url()
|
|
1272
|
+
or self._is_azure_openai_url()
|
|
1273
|
+
or self._is_github_copilot_url()
|
|
1274
|
+
or model_forces_max_completion_tokens(self.model)
|
|
1275
|
+
):
|
|
1206
1276
|
return {"max_completion_tokens": value}
|
|
1207
1277
|
return {"max_tokens": value}
|
|
1208
1278
|
|
|
@@ -1343,10 +1413,15 @@ class AIAgent:
|
|
|
1343
1413
|
def _summarize_background_review_actions(
|
|
1344
1414
|
review_messages: List[Dict],
|
|
1345
1415
|
prior_snapshot: List[Dict],
|
|
1416
|
+
notification_mode: str = "on",
|
|
1346
1417
|
) -> List[str]:
|
|
1347
1418
|
"""Forwarder — see ``agent.background_review.summarize_background_review_actions``."""
|
|
1348
1419
|
from agent.background_review import summarize_background_review_actions
|
|
1349
|
-
return summarize_background_review_actions(
|
|
1420
|
+
return summarize_background_review_actions(
|
|
1421
|
+
review_messages,
|
|
1422
|
+
prior_snapshot,
|
|
1423
|
+
notification_mode=notification_mode,
|
|
1424
|
+
)
|
|
1350
1425
|
|
|
1351
1426
|
def _spawn_background_review(
|
|
1352
1427
|
self,
|
|
@@ -1397,16 +1472,29 @@ class AIAgent:
|
|
|
1397
1472
|
that synthetic text leak into persisted transcripts or resumed session
|
|
1398
1473
|
history. When an override is configured for the active turn, mutate the
|
|
1399
1474
|
in-memory messages list in place so both persistence and returned
|
|
1400
|
-
history stay clean.
|
|
1475
|
+
history stay clean. A paired timestamp override preserves the platform
|
|
1476
|
+
event time as message metadata, rather than embedding it in content.
|
|
1401
1477
|
"""
|
|
1402
1478
|
idx = getattr(self, "_persist_user_message_idx", None)
|
|
1403
1479
|
override = getattr(self, "_persist_user_message_override", None)
|
|
1404
|
-
|
|
1480
|
+
timestamp = getattr(self, "_persist_user_message_timestamp", None)
|
|
1481
|
+
if idx is None or (override is None and timestamp is None):
|
|
1405
1482
|
return
|
|
1406
1483
|
if 0 <= idx < len(messages):
|
|
1407
1484
|
msg = messages[idx]
|
|
1408
1485
|
if isinstance(msg, dict) and msg.get("role") == "user":
|
|
1409
|
-
|
|
1486
|
+
# Text-only call paths may pass a synthetic API-facing prompt
|
|
1487
|
+
# and a cleaner transcript string separately. Multimodal
|
|
1488
|
+
# turns, however, keep image/audio blocks in the live
|
|
1489
|
+
# messages list that is still used for the API request after
|
|
1490
|
+
# early crash-resilience persistence. Do not replace those
|
|
1491
|
+
# blocks with the text-only persistence override before the
|
|
1492
|
+
# model call is built. The paired timestamp override still
|
|
1493
|
+
# applies — it is metadata, not content.
|
|
1494
|
+
if override is not None and not isinstance(msg.get("content"), list):
|
|
1495
|
+
msg["content"] = override
|
|
1496
|
+
if timestamp is not None:
|
|
1497
|
+
msg["timestamp"] = timestamp
|
|
1410
1498
|
|
|
1411
1499
|
def _persist_session(self, messages: List[Dict], conversation_history: List[Dict] = None):
|
|
1412
1500
|
"""Save session state to both JSON log and SQLite on any exit path.
|
|
@@ -1480,9 +1568,10 @@ class AIAgent:
|
|
|
1480
1568
|
def _flush_messages_to_session_db(self, messages: List[Dict], conversation_history: List[Dict] = None):
|
|
1481
1569
|
"""Persist any un-flushed messages to the SQLite session store.
|
|
1482
1570
|
|
|
1483
|
-
Uses
|
|
1484
|
-
|
|
1485
|
-
|
|
1571
|
+
Uses per-session message identity tracking so repeated calls (from
|
|
1572
|
+
multiple exit paths) only write truly new messages — preventing the
|
|
1573
|
+
duplicate-write bug (#860) without relying on positional slices that
|
|
1574
|
+
can drift after message-sequence repair.
|
|
1486
1575
|
"""
|
|
1487
1576
|
if not self._session_db:
|
|
1488
1577
|
return
|
|
@@ -1491,9 +1580,41 @@ class AIAgent:
|
|
|
1491
1580
|
# Retry row creation if the earlier attempt failed transiently.
|
|
1492
1581
|
if not self._session_db_created:
|
|
1493
1582
|
self._ensure_db_session()
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1583
|
+
# Positional flushing used to slice at
|
|
1584
|
+
# max(len(conversation_history), _last_flushed_db_idx). That
|
|
1585
|
+
# assumes the live `messages` list is the original history plus a
|
|
1586
|
+
# new tail. repair_message_sequence can shrink/merge the history
|
|
1587
|
+
# copy before the final flush, making len(conversation_history)
|
|
1588
|
+
# larger than len(messages); the slice is then empty and delivered
|
|
1589
|
+
# assistant responses never reach state.db (#46053).
|
|
1590
|
+
#
|
|
1591
|
+
# Track object identities instead. `messages` is a shallow copy of
|
|
1592
|
+
# `conversation_history`, so history dicts are skipped by identity,
|
|
1593
|
+
# and new dicts appended during this turn are written once even if
|
|
1594
|
+
# repair compacts the list around them.
|
|
1595
|
+
current_session_id = getattr(self, "session_id", None)
|
|
1596
|
+
flushed_session_id = getattr(self, "_flushed_db_message_session_id", None)
|
|
1597
|
+
if flushed_session_id != current_session_id or self._last_flushed_db_idx == 0:
|
|
1598
|
+
self._flushed_db_message_ids = set()
|
|
1599
|
+
self._flushed_db_message_session_id = current_session_id
|
|
1600
|
+
flushed_ids = getattr(self, "_flushed_db_message_ids", None)
|
|
1601
|
+
if not isinstance(flushed_ids, set):
|
|
1602
|
+
flushed_ids = set()
|
|
1603
|
+
self._flushed_db_message_ids = flushed_ids
|
|
1604
|
+
history_ids = {
|
|
1605
|
+
id(item) for item in (conversation_history or [])
|
|
1606
|
+
if isinstance(item, dict)
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
for msg in messages:
|
|
1610
|
+
if not isinstance(msg, dict):
|
|
1611
|
+
continue
|
|
1612
|
+
msg_id = id(msg)
|
|
1613
|
+
if msg_id in flushed_ids:
|
|
1614
|
+
continue
|
|
1615
|
+
if msg_id in history_ids:
|
|
1616
|
+
flushed_ids.add(msg_id)
|
|
1617
|
+
continue
|
|
1497
1618
|
role = msg.get("role", "unknown")
|
|
1498
1619
|
content = msg.get("content")
|
|
1499
1620
|
# Persist multimodal tool results as their text summary only —
|
|
@@ -1531,7 +1652,9 @@ class AIAgent:
|
|
|
1531
1652
|
reasoning_details=msg.get("reasoning_details") if role == "assistant" else None,
|
|
1532
1653
|
codex_reasoning_items=msg.get("codex_reasoning_items") if role == "assistant" else None,
|
|
1533
1654
|
codex_message_items=msg.get("codex_message_items") if role == "assistant" else None,
|
|
1655
|
+
timestamp=msg.get("timestamp"),
|
|
1534
1656
|
)
|
|
1657
|
+
flushed_ids.add(msg_id)
|
|
1535
1658
|
self._last_flushed_db_idx = len(messages)
|
|
1536
1659
|
except Exception as e:
|
|
1537
1660
|
logger.warning("Session DB append_message failed: %s", e)
|
|
@@ -1717,6 +1840,35 @@ class AIAgent:
|
|
|
1717
1840
|
return detail
|
|
1718
1841
|
return f"{detail}{hint}"
|
|
1719
1842
|
|
|
1843
|
+
@staticmethod
|
|
1844
|
+
def _coerce_api_error_detail(value: Any) -> str:
|
|
1845
|
+
"""Return a display-safe string for structured provider error fields."""
|
|
1846
|
+
if isinstance(value, str):
|
|
1847
|
+
return value
|
|
1848
|
+
if isinstance(value, dict):
|
|
1849
|
+
for key in ("message", "detail", "error", "code", "type"):
|
|
1850
|
+
nested = value.get(key)
|
|
1851
|
+
if isinstance(nested, str) and nested.strip():
|
|
1852
|
+
return nested
|
|
1853
|
+
for key in ("message", "detail", "error", "code", "type"):
|
|
1854
|
+
if key in value:
|
|
1855
|
+
nested_detail = AIAgent._coerce_api_error_detail(value[key])
|
|
1856
|
+
if nested_detail:
|
|
1857
|
+
return nested_detail
|
|
1858
|
+
try:
|
|
1859
|
+
return json.dumps(value, ensure_ascii=False, sort_keys=True)
|
|
1860
|
+
except TypeError:
|
|
1861
|
+
return str(value)
|
|
1862
|
+
if isinstance(value, (list, tuple)):
|
|
1863
|
+
parts = [
|
|
1864
|
+
AIAgent._coerce_api_error_detail(item)
|
|
1865
|
+
for item in value
|
|
1866
|
+
]
|
|
1867
|
+
return "; ".join(part for part in parts if part)
|
|
1868
|
+
if value is None:
|
|
1869
|
+
return ""
|
|
1870
|
+
return str(value)
|
|
1871
|
+
|
|
1720
1872
|
@staticmethod
|
|
1721
1873
|
def _summarize_api_error(error: Exception) -> str:
|
|
1722
1874
|
"""Extract a human-readable one-liner from an API error.
|
|
@@ -1756,6 +1908,7 @@ class AIAgent:
|
|
|
1756
1908
|
if msg:
|
|
1757
1909
|
status_code = getattr(error, "status_code", None)
|
|
1758
1910
|
prefix = f"HTTP {status_code}: " if status_code else ""
|
|
1911
|
+
msg = AIAgent._coerce_api_error_detail(msg)
|
|
1759
1912
|
return AIAgent._decorate_xai_entitlement_error(f"{prefix}{msg[:300]}")
|
|
1760
1913
|
|
|
1761
1914
|
# Fallback: truncate the raw string but give more room than 200 chars
|
|
@@ -1822,6 +1975,254 @@ class AIAgent:
|
|
|
1822
1975
|
summary["total_tokens"] = cu.total_tokens
|
|
1823
1976
|
return summary
|
|
1824
1977
|
|
|
1978
|
+
@staticmethod
|
|
1979
|
+
def _hook_payload_max_chars() -> int:
|
|
1980
|
+
raw = os.getenv("HERMES_PLUGIN_PAYLOAD_MAX_CHARS", "50000")
|
|
1981
|
+
try:
|
|
1982
|
+
return max(1000, int(raw))
|
|
1983
|
+
except (TypeError, ValueError):
|
|
1984
|
+
return 50000
|
|
1985
|
+
|
|
1986
|
+
@staticmethod
|
|
1987
|
+
def _is_sensitive_hook_key(key: Any) -> bool:
|
|
1988
|
+
if not isinstance(key, str):
|
|
1989
|
+
return False
|
|
1990
|
+
lowered = key.lower().replace("-", "_")
|
|
1991
|
+
exact = {
|
|
1992
|
+
"api_key",
|
|
1993
|
+
"authorization",
|
|
1994
|
+
"proxy_authorization",
|
|
1995
|
+
"cookie",
|
|
1996
|
+
"set_cookie",
|
|
1997
|
+
}
|
|
1998
|
+
return lowered in exact or lowered.endswith("_api_key")
|
|
1999
|
+
|
|
2000
|
+
@classmethod
|
|
2001
|
+
def _hook_jsonable(
|
|
2002
|
+
cls,
|
|
2003
|
+
value: Any,
|
|
2004
|
+
*,
|
|
2005
|
+
depth: int = 0,
|
|
2006
|
+
max_depth: int = 8,
|
|
2007
|
+
max_string: int = 8000,
|
|
2008
|
+
max_sequence: int = 200,
|
|
2009
|
+
) -> Any:
|
|
2010
|
+
if depth > max_depth:
|
|
2011
|
+
return f"<{type(value).__name__} depth limit>"
|
|
2012
|
+
if value is None or isinstance(value, (bool, int, float)):
|
|
2013
|
+
return value
|
|
2014
|
+
if isinstance(value, str):
|
|
2015
|
+
if len(value) > max_string:
|
|
2016
|
+
return value[:max_string] + f"...[truncated {len(value) - max_string} chars]"
|
|
2017
|
+
return value
|
|
2018
|
+
if isinstance(value, (bytes, bytearray)):
|
|
2019
|
+
return f"<{len(value)} bytes>"
|
|
2020
|
+
if isinstance(value, dict):
|
|
2021
|
+
out: Dict[str, Any] = {}
|
|
2022
|
+
for idx, (key, item) in enumerate(value.items()):
|
|
2023
|
+
if idx >= max_sequence:
|
|
2024
|
+
out["_truncated_items"] = len(value) - max_sequence
|
|
2025
|
+
break
|
|
2026
|
+
str_key = str(key)
|
|
2027
|
+
if cls._is_sensitive_hook_key(str_key):
|
|
2028
|
+
out[str_key] = "<redacted>"
|
|
2029
|
+
else:
|
|
2030
|
+
out[str_key] = cls._hook_jsonable(
|
|
2031
|
+
item,
|
|
2032
|
+
depth=depth + 1,
|
|
2033
|
+
max_depth=max_depth,
|
|
2034
|
+
max_string=max_string,
|
|
2035
|
+
max_sequence=max_sequence,
|
|
2036
|
+
)
|
|
2037
|
+
return out
|
|
2038
|
+
if isinstance(value, (list, tuple, set)):
|
|
2039
|
+
seq = list(value)
|
|
2040
|
+
out = [
|
|
2041
|
+
cls._hook_jsonable(
|
|
2042
|
+
item,
|
|
2043
|
+
depth=depth + 1,
|
|
2044
|
+
max_depth=max_depth,
|
|
2045
|
+
max_string=max_string,
|
|
2046
|
+
max_sequence=max_sequence,
|
|
2047
|
+
)
|
|
2048
|
+
for item in seq[:max_sequence]
|
|
2049
|
+
]
|
|
2050
|
+
if len(seq) > max_sequence:
|
|
2051
|
+
out.append({"_truncated_items": len(seq) - max_sequence})
|
|
2052
|
+
return out
|
|
2053
|
+
try:
|
|
2054
|
+
if hasattr(value, "model_dump"):
|
|
2055
|
+
try:
|
|
2056
|
+
dumped = value.model_dump(mode="json")
|
|
2057
|
+
except TypeError:
|
|
2058
|
+
dumped = value.model_dump()
|
|
2059
|
+
return cls._hook_jsonable(
|
|
2060
|
+
dumped,
|
|
2061
|
+
depth=depth + 1,
|
|
2062
|
+
max_depth=max_depth,
|
|
2063
|
+
max_string=max_string,
|
|
2064
|
+
max_sequence=max_sequence,
|
|
2065
|
+
)
|
|
2066
|
+
except Exception:
|
|
2067
|
+
pass
|
|
2068
|
+
try:
|
|
2069
|
+
from dataclasses import asdict, is_dataclass
|
|
2070
|
+
if is_dataclass(value):
|
|
2071
|
+
return cls._hook_jsonable(
|
|
2072
|
+
asdict(value),
|
|
2073
|
+
depth=depth + 1,
|
|
2074
|
+
max_depth=max_depth,
|
|
2075
|
+
max_string=max_string,
|
|
2076
|
+
max_sequence=max_sequence,
|
|
2077
|
+
)
|
|
2078
|
+
except Exception:
|
|
2079
|
+
pass
|
|
2080
|
+
if isinstance(value, SimpleNamespace):
|
|
2081
|
+
return cls._hook_jsonable(
|
|
2082
|
+
vars(value),
|
|
2083
|
+
depth=depth + 1,
|
|
2084
|
+
max_depth=max_depth,
|
|
2085
|
+
max_string=max_string,
|
|
2086
|
+
max_sequence=max_sequence,
|
|
2087
|
+
)
|
|
2088
|
+
if hasattr(value, "__dict__"):
|
|
2089
|
+
try:
|
|
2090
|
+
public_attrs = {
|
|
2091
|
+
k: v
|
|
2092
|
+
for k, v in vars(value).items()
|
|
2093
|
+
if not str(k).startswith("_")
|
|
2094
|
+
}
|
|
2095
|
+
return cls._hook_jsonable(
|
|
2096
|
+
public_attrs,
|
|
2097
|
+
depth=depth + 1,
|
|
2098
|
+
max_depth=max_depth,
|
|
2099
|
+
max_string=max_string,
|
|
2100
|
+
max_sequence=max_sequence,
|
|
2101
|
+
)
|
|
2102
|
+
except Exception:
|
|
2103
|
+
pass
|
|
2104
|
+
return str(value)[:max_string]
|
|
2105
|
+
|
|
2106
|
+
@classmethod
|
|
2107
|
+
def _sanitize_hook_payload(cls, value: Any) -> Any:
|
|
2108
|
+
payload = cls._hook_jsonable(value)
|
|
2109
|
+
limit = cls._hook_payload_max_chars()
|
|
2110
|
+
try:
|
|
2111
|
+
encoded = json.dumps(payload, ensure_ascii=False, default=str)
|
|
2112
|
+
except Exception:
|
|
2113
|
+
return str(payload)[:limit]
|
|
2114
|
+
if len(encoded) <= limit:
|
|
2115
|
+
return payload
|
|
2116
|
+
payload = cls._hook_jsonable(value, max_string=1000, max_sequence=50)
|
|
2117
|
+
try:
|
|
2118
|
+
encoded = json.dumps(payload, ensure_ascii=False, default=str)
|
|
2119
|
+
except Exception:
|
|
2120
|
+
return str(payload)[:limit]
|
|
2121
|
+
if len(encoded) <= limit:
|
|
2122
|
+
return payload
|
|
2123
|
+
return {
|
|
2124
|
+
"_truncated": True,
|
|
2125
|
+
"original_type": type(value).__name__,
|
|
2126
|
+
"preview": encoded[:limit],
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
def _api_request_payload_for_hook(self, api_kwargs: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
2130
|
+
body = {
|
|
2131
|
+
key: value
|
|
2132
|
+
for key, value in (api_kwargs or {}).items()
|
|
2133
|
+
if key not in {"timeout", "http_client"}
|
|
2134
|
+
}
|
|
2135
|
+
return self._sanitize_hook_payload(
|
|
2136
|
+
{
|
|
2137
|
+
"method": "POST",
|
|
2138
|
+
"body": body,
|
|
2139
|
+
}
|
|
2140
|
+
)
|
|
2141
|
+
|
|
2142
|
+
def _api_response_payload_for_hook(
|
|
2143
|
+
self,
|
|
2144
|
+
response: Any,
|
|
2145
|
+
assistant_message: Any,
|
|
2146
|
+
*,
|
|
2147
|
+
finish_reason: Optional[str],
|
|
2148
|
+
) -> Dict[str, Any]:
|
|
2149
|
+
# ``tool_calls`` is the raw list of provider SDK objects (e.g.
|
|
2150
|
+
# OpenAI ``ChatCompletionMessageToolCall``). We deliberately hand
|
|
2151
|
+
# the raw objects to ``_sanitize_hook_payload`` and rely on
|
|
2152
|
+
# ``_hook_jsonable`` to normalise them via ``model_dump`` /
|
|
2153
|
+
# ``__dict__`` / dataclass introspection — a future refactor of
|
|
2154
|
+
# the sanitiser MUST preserve that capability or hook subscribers
|
|
2155
|
+
# will receive opaque ``str(obj)`` blobs here.
|
|
2156
|
+
tool_calls = getattr(assistant_message, "tool_calls", None) or []
|
|
2157
|
+
return self._sanitize_hook_payload(
|
|
2158
|
+
{
|
|
2159
|
+
"model": getattr(response, "model", None),
|
|
2160
|
+
"finish_reason": finish_reason,
|
|
2161
|
+
"assistant_message": {
|
|
2162
|
+
"role": getattr(assistant_message, "role", "assistant"),
|
|
2163
|
+
"content": getattr(assistant_message, "content", None),
|
|
2164
|
+
"tool_calls": tool_calls,
|
|
2165
|
+
},
|
|
2166
|
+
"usage": self._usage_summary_for_api_request_hook(response),
|
|
2167
|
+
}
|
|
2168
|
+
)
|
|
2169
|
+
|
|
2170
|
+
def _invoke_api_request_error_hook(
|
|
2171
|
+
self,
|
|
2172
|
+
*,
|
|
2173
|
+
task_id: str,
|
|
2174
|
+
turn_id: str,
|
|
2175
|
+
api_request_id: str,
|
|
2176
|
+
api_call_count: int,
|
|
2177
|
+
api_start_time: float,
|
|
2178
|
+
api_kwargs: Optional[Dict[str, Any]],
|
|
2179
|
+
error_type: str,
|
|
2180
|
+
error_message: str,
|
|
2181
|
+
status_code: Optional[int] = None,
|
|
2182
|
+
retry_count: Optional[int] = None,
|
|
2183
|
+
max_retries: Optional[int] = None,
|
|
2184
|
+
retryable: Optional[bool] = None,
|
|
2185
|
+
reason: Optional[str] = None,
|
|
2186
|
+
) -> None:
|
|
2187
|
+
# Lazy module import (not from-import) so tests that
|
|
2188
|
+
# ``monkeypatch.setattr("hermes_cli.plugins.has_hook", ...)`` still
|
|
2189
|
+
# take effect on this call site. After first call the import is a
|
|
2190
|
+
# ``sys.modules`` dict lookup, so retries don't repay any real cost.
|
|
2191
|
+
try:
|
|
2192
|
+
from hermes_cli import plugins as _plugins
|
|
2193
|
+
|
|
2194
|
+
if not _plugins.has_hook("api_request_error"):
|
|
2195
|
+
return
|
|
2196
|
+
ended_at = time.time()
|
|
2197
|
+
_plugins.invoke_hook(
|
|
2198
|
+
"api_request_error",
|
|
2199
|
+
task_id=task_id,
|
|
2200
|
+
turn_id=turn_id,
|
|
2201
|
+
api_request_id=api_request_id,
|
|
2202
|
+
session_id=self.session_id or "",
|
|
2203
|
+
platform=self.platform or "",
|
|
2204
|
+
model=self.model,
|
|
2205
|
+
provider=self.provider,
|
|
2206
|
+
base_url=self.base_url,
|
|
2207
|
+
api_mode=self.api_mode,
|
|
2208
|
+
api_call_count=api_call_count,
|
|
2209
|
+
api_duration=ended_at - api_start_time,
|
|
2210
|
+
started_at=api_start_time,
|
|
2211
|
+
ended_at=ended_at,
|
|
2212
|
+
status_code=status_code,
|
|
2213
|
+
retry_count=retry_count,
|
|
2214
|
+
max_retries=max_retries,
|
|
2215
|
+
retryable=retryable,
|
|
2216
|
+
reason=reason,
|
|
2217
|
+
error={
|
|
2218
|
+
"type": error_type,
|
|
2219
|
+
"message": error_message,
|
|
2220
|
+
},
|
|
2221
|
+
request=self._api_request_payload_for_hook(api_kwargs),
|
|
2222
|
+
)
|
|
2223
|
+
except Exception:
|
|
2224
|
+
pass
|
|
2225
|
+
|
|
1825
2226
|
def _dump_api_request_debug(
|
|
1826
2227
|
self,
|
|
1827
2228
|
api_kwargs: Dict[str, Any],
|
|
@@ -2418,6 +2819,157 @@ class AIAgent:
|
|
|
2418
2819
|
"""Return the last captured RateLimitState, or None."""
|
|
2419
2820
|
return self._rate_limit_state
|
|
2420
2821
|
|
|
2822
|
+
def _capture_credits(self, http_response: Any) -> None:
|
|
2823
|
+
"""Parse x-nous-credits-* headers, cache CreditsState, fire threshold notices.
|
|
2824
|
+
|
|
2825
|
+
Fail-open throughout — header issues never break the agent loop. The PARSE is
|
|
2826
|
+
swallowed (any error → treated as a miss → keep last-known). The notice
|
|
2827
|
+
EVALUATION/EMIT is a SEPARATE block that WARNS on failure (R1-M2): a bug in the
|
|
2828
|
+
depletion-notice path must not vanish silently under the parse swallow.
|
|
2829
|
+
"""
|
|
2830
|
+
# Dev test fixture (HERMES_DEV_CREDITS_FIXTURE): inject a chosen notice state
|
|
2831
|
+
# each turn for repeatable testing, bypassing real headers. Throwaway scaffolding.
|
|
2832
|
+
try:
|
|
2833
|
+
from agent.credits_tracker import dev_fixture_credits_state
|
|
2834
|
+
_fixture = dev_fixture_credits_state()
|
|
2835
|
+
except Exception:
|
|
2836
|
+
_fixture = None
|
|
2837
|
+
if _fixture is not None:
|
|
2838
|
+
self._credits_state = _fixture
|
|
2839
|
+
if self._credits_session_start_micros is None:
|
|
2840
|
+
self._credits_session_start_micros = _fixture.remaining_micros
|
|
2841
|
+
_latch = getattr(self, "_credits_latch", None)
|
|
2842
|
+
if isinstance(_latch, dict):
|
|
2843
|
+
_latch["seen_below_90"] = True # let warn90 fire without a real crossing
|
|
2844
|
+
_used = _fixture.used_fraction
|
|
2845
|
+
logger.info(
|
|
2846
|
+
"credits ▸ [FIXTURE] remaining=%d (%s) · paid=%s · denom=%s · used=%s "
|
|
2847
|
+
"(real headers bypassed — `echo clear` / unset HERMES_DEV_CREDITS_FIXTURE to restore)",
|
|
2848
|
+
_fixture.remaining_micros,
|
|
2849
|
+
_fixture.remaining_usd or "?",
|
|
2850
|
+
_fixture.paid_access,
|
|
2851
|
+
_fixture.denominator_kind,
|
|
2852
|
+
("%.0f%%" % (_used * 100)) if _used is not None else "n/a",
|
|
2853
|
+
)
|
|
2854
|
+
self._emit_credits_notices()
|
|
2855
|
+
return
|
|
2856
|
+
if http_response is None:
|
|
2857
|
+
return
|
|
2858
|
+
headers = getattr(http_response, "headers", None)
|
|
2859
|
+
if not headers:
|
|
2860
|
+
return
|
|
2861
|
+
_dev = is_truthy_value(os.environ.get("HERMES_DEV_CREDITS"))
|
|
2862
|
+
|
|
2863
|
+
# ── Parse (fail-open → miss; never overwrite good state with None) ──
|
|
2864
|
+
try:
|
|
2865
|
+
from agent.credits_tracker import parse_credits_headers
|
|
2866
|
+
state = parse_credits_headers(headers, provider=self.provider)
|
|
2867
|
+
except Exception:
|
|
2868
|
+
return # parse error → treat as a miss, keep last-known
|
|
2869
|
+
if state is None:
|
|
2870
|
+
if _dev:
|
|
2871
|
+
logger.info(
|
|
2872
|
+
"credits ▸ response had no valid x-nous-credits-* headers "
|
|
2873
|
+
"(miss — producer off / non-Nous path / >TTL stale)"
|
|
2874
|
+
)
|
|
2875
|
+
return
|
|
2876
|
+
|
|
2877
|
+
# retain-last-known: only overwrite on a fresh valid parse
|
|
2878
|
+
self._credits_state = state
|
|
2879
|
+
# Latch session-start remaining the first time we ever see a header
|
|
2880
|
+
if self._credits_session_start_micros is None:
|
|
2881
|
+
self._credits_session_start_micros = state.remaining_micros
|
|
2882
|
+
if _dev:
|
|
2883
|
+
# HERMES_DEV_CREDITS: stream each capture to agent.log — watch live with
|
|
2884
|
+
# `hermes logs -f` (grep 'credits ▸'). Dev-only; silent for normal users.
|
|
2885
|
+
spent = self.get_credits_spent_micros()
|
|
2886
|
+
used = state.used_fraction
|
|
2887
|
+
logger.info(
|
|
2888
|
+
"credits ▸ remaining=%d (%s) · paid=%s · denom=%s · used=%s "
|
|
2889
|
+
"· Δspent=%s · age=%s%s",
|
|
2890
|
+
state.remaining_micros,
|
|
2891
|
+
state.remaining_usd or "?",
|
|
2892
|
+
state.paid_access,
|
|
2893
|
+
state.denominator_kind,
|
|
2894
|
+
("%.0f%%" % (used * 100)) if used is not None else "n/a",
|
|
2895
|
+
("%.1f¢" % (spent / 10000)) if spent is not None else "n/a",
|
|
2896
|
+
("%.0fs" % state.age_seconds) if state.age_seconds != float("inf") else "n/a",
|
|
2897
|
+
(" · disabled=%s" % state.disabled_reason) if state.disabled_reason else "",
|
|
2898
|
+
)
|
|
2899
|
+
|
|
2900
|
+
# Threshold notices — shared with the cold-start seed (see _emit_credits_notices).
|
|
2901
|
+
self._emit_credits_notices()
|
|
2902
|
+
|
|
2903
|
+
def _emit_credits_notices(self) -> None:
|
|
2904
|
+
"""Run the threshold policy on the current credits state and emit notices.
|
|
2905
|
+
|
|
2906
|
+
Shared by the warm path (_capture_credits) and the L3 cold-start seed, so a
|
|
2907
|
+
session that opens already depleted warns immediately — not only after the first
|
|
2908
|
+
inference header. Runs only when a notice consumer is bound (messaging binds none
|
|
2909
|
+
→ state still cached for /usage, no policy). WARNS on failure rather than
|
|
2910
|
+
swallowing (R1-M2): a depletion-path bug must not vanish silently. Emits clears
|
|
2911
|
+
FIRST, then shows (so depleted lands last in a latest-wins slot).
|
|
2912
|
+
"""
|
|
2913
|
+
if getattr(self, "notice_callback", None) is None and getattr(self, "notice_clear_callback", None) is None:
|
|
2914
|
+
return
|
|
2915
|
+
if not self._credits_notices_enabled():
|
|
2916
|
+
return
|
|
2917
|
+
state = getattr(self, "_credits_state", None)
|
|
2918
|
+
if state is None:
|
|
2919
|
+
return
|
|
2920
|
+
try:
|
|
2921
|
+
from agent.credits_tracker import evaluate_credits_notices, is_free_tier_model
|
|
2922
|
+
latch = getattr(self, "_credits_latch", None)
|
|
2923
|
+
if latch is None:
|
|
2924
|
+
latch = self._credits_latch = {"active": set(), "seen_below_90": False, "usage_band": None}
|
|
2925
|
+
# Free-model gate: a depleted account on a free model can still
|
|
2926
|
+
# inference, so the depleted error banner is suppressed. Local-data
|
|
2927
|
+
# only (":free" suffix + pricing-cache peek) — never a network call.
|
|
2928
|
+
model_is_free = is_free_tier_model(
|
|
2929
|
+
getattr(self, "model", "") or "",
|
|
2930
|
+
getattr(self, "base_url", "") or "",
|
|
2931
|
+
)
|
|
2932
|
+
to_show, to_clear = evaluate_credits_notices(state, latch, model_is_free=model_is_free)
|
|
2933
|
+
for key in to_clear: # clears FIRST …
|
|
2934
|
+
self._emit_notice_clear(key)
|
|
2935
|
+
for notice in to_show: # … then shows (depleted lands last in a latest-wins slot)
|
|
2936
|
+
self._emit_notice(notice)
|
|
2937
|
+
except Exception:
|
|
2938
|
+
logger.warning("credits notice evaluation/emit failed", exc_info=True)
|
|
2939
|
+
|
|
2940
|
+
def _credits_notices_enabled(self) -> bool:
|
|
2941
|
+
"""Whether credits notices are enabled (config display.credits_notices).
|
|
2942
|
+
|
|
2943
|
+
Read once per agent and cached — the policy runs after every API
|
|
2944
|
+
response, and the setting governs UI noise, not correctness, so a
|
|
2945
|
+
config flip applying on the next session is fine. Fail-open True
|
|
2946
|
+
(preserve current behaviour) on any config error.
|
|
2947
|
+
"""
|
|
2948
|
+
cached = getattr(self, "_credits_notices_enabled_cache", None)
|
|
2949
|
+
if cached is not None:
|
|
2950
|
+
return cached
|
|
2951
|
+
enabled = True
|
|
2952
|
+
try:
|
|
2953
|
+
from hermes_cli.config import load_config as _load_config
|
|
2954
|
+
_cfg = _load_config() or {}
|
|
2955
|
+
_display = _cfg.get("display") if isinstance(_cfg, dict) else None
|
|
2956
|
+
if isinstance(_display, dict) and "credits_notices" in _display:
|
|
2957
|
+
enabled = bool(_display.get("credits_notices"))
|
|
2958
|
+
except Exception:
|
|
2959
|
+
enabled = True
|
|
2960
|
+
self._credits_notices_enabled_cache = enabled
|
|
2961
|
+
return enabled
|
|
2962
|
+
|
|
2963
|
+
def get_credits_state(self):
|
|
2964
|
+
"""Return the last captured CreditsState, or None."""
|
|
2965
|
+
return self._credits_state
|
|
2966
|
+
|
|
2967
|
+
def get_credits_spent_micros(self):
|
|
2968
|
+
"""Session-cumulative micros spent = first_seen_remaining - current_remaining. None if no data."""
|
|
2969
|
+
if self._credits_session_start_micros is None or self._credits_state is None:
|
|
2970
|
+
return None
|
|
2971
|
+
return self._credits_session_start_micros - self._credits_state.remaining_micros
|
|
2972
|
+
|
|
2421
2973
|
def _check_openrouter_cache_status(self, http_response: Any) -> None:
|
|
2422
2974
|
"""Read X-OpenRouter-Cache-Status from response headers and log it.
|
|
2423
2975
|
|
|
@@ -2548,17 +3100,24 @@ class AIAgent:
|
|
|
2548
3100
|
return
|
|
2549
3101
|
if not (self._memory_manager and final_response and original_user_message):
|
|
2550
3102
|
return
|
|
3103
|
+
# Multimodal turns carry content as a list of typed parts; providers
|
|
3104
|
+
# expect plain strings, so flatten to text first (newline-joined for
|
|
3105
|
+
# memory, vs the default space-join used for log/trajectory previews).
|
|
3106
|
+
user_text = _summarize_user_message_for_log(original_user_message, sep="\n")
|
|
3107
|
+
response_text = _summarize_user_message_for_log(final_response, sep="\n")
|
|
3108
|
+
if not (user_text and response_text):
|
|
3109
|
+
return
|
|
2551
3110
|
try:
|
|
2552
3111
|
sync_kwargs = {"session_id": self.session_id or ""}
|
|
2553
3112
|
if messages is not None:
|
|
2554
3113
|
sync_kwargs["messages"] = messages
|
|
2555
3114
|
self._memory_manager.sync_all(
|
|
2556
|
-
|
|
2557
|
-
|
|
3115
|
+
user_text,
|
|
3116
|
+
response_text,
|
|
2558
3117
|
**sync_kwargs,
|
|
2559
3118
|
)
|
|
2560
3119
|
self._memory_manager.queue_prefetch_all(
|
|
2561
|
-
|
|
3120
|
+
user_text,
|
|
2562
3121
|
session_id=self.session_id or "",
|
|
2563
3122
|
)
|
|
2564
3123
|
except Exception:
|
|
@@ -2667,6 +3226,17 @@ class AIAgent:
|
|
|
2667
3226
|
except Exception:
|
|
2668
3227
|
pass
|
|
2669
3228
|
|
|
3229
|
+
# 6. Free conversation history. Mirrors _release_evicted_agent_soft's
|
|
3230
|
+
# soft-eviction clear — close() is the hard teardown for true session
|
|
3231
|
+
# boundaries (/new, /reset, session expiry), so the message list won't
|
|
3232
|
+
# be reused. Drops the reference proactively rather than waiting for
|
|
3233
|
+
# the agent object itself to be collected, which matters when a caller
|
|
3234
|
+
# still holds the closed agent (e.g. a draining background task).
|
|
3235
|
+
try:
|
|
3236
|
+
self._session_messages = []
|
|
3237
|
+
except Exception:
|
|
3238
|
+
pass
|
|
3239
|
+
|
|
2670
3240
|
def _hydrate_todo_store(self, history: List[Dict[str, Any]]) -> None:
|
|
2671
3241
|
"""
|
|
2672
3242
|
Recover todo state from conversation history.
|
|
@@ -2756,7 +3326,11 @@ class AIAgent:
|
|
|
2756
3326
|
return sanitize_api_messages(messages)
|
|
2757
3327
|
|
|
2758
3328
|
@staticmethod
|
|
2759
|
-
def _is_thinking_only_assistant(
|
|
3329
|
+
def _is_thinking_only_assistant(
|
|
3330
|
+
msg: Dict[str, Any],
|
|
3331
|
+
*,
|
|
3332
|
+
drop_codex_reasoning_items: bool = True,
|
|
3333
|
+
) -> bool:
|
|
2760
3334
|
"""Return True if ``msg`` is an assistant turn whose only payload is reasoning.
|
|
2761
3335
|
|
|
2762
3336
|
"Thinking-only" means the model emitted reasoning (``reasoning`` or
|
|
@@ -2807,15 +3381,30 @@ class AIAgent:
|
|
|
2807
3381
|
rd = msg.get("reasoning_details")
|
|
2808
3382
|
if isinstance(rd, list) and rd:
|
|
2809
3383
|
return True
|
|
3384
|
+
# Codex Responses stores encrypted reasoning state under a separate
|
|
3385
|
+
# assistant-message key. Treat only real reasoning items as
|
|
3386
|
+
# thinking-only; empty/junk lists should fall through to the generic
|
|
3387
|
+
# empty-turn handling instead of being dropped here.
|
|
3388
|
+
codex_items = msg.get("codex_reasoning_items")
|
|
3389
|
+
if drop_codex_reasoning_items and isinstance(codex_items, list):
|
|
3390
|
+
return any(
|
|
3391
|
+
isinstance(item, dict) and item.get("type") == "reasoning"
|
|
3392
|
+
for item in codex_items
|
|
3393
|
+
)
|
|
2810
3394
|
return False
|
|
2811
3395
|
|
|
2812
3396
|
@staticmethod
|
|
2813
3397
|
def _drop_thinking_only_and_merge_users(
|
|
2814
3398
|
messages: List[Dict[str, Any]],
|
|
3399
|
+
*,
|
|
3400
|
+
drop_codex_reasoning_items: bool = True,
|
|
2815
3401
|
) -> List[Dict[str, Any]]:
|
|
2816
3402
|
"""Forwarder — see ``agent.agent_runtime_helpers.drop_thinking_only_and_merge_users``."""
|
|
2817
3403
|
from agent.agent_runtime_helpers import drop_thinking_only_and_merge_users
|
|
2818
|
-
return drop_thinking_only_and_merge_users(
|
|
3404
|
+
return drop_thinking_only_and_merge_users(
|
|
3405
|
+
messages,
|
|
3406
|
+
drop_codex_reasoning_items=drop_codex_reasoning_items,
|
|
3407
|
+
)
|
|
2819
3408
|
|
|
2820
3409
|
@staticmethod
|
|
2821
3410
|
def _cap_delegate_task_calls(tool_calls: list) -> list:
|
|
@@ -3389,6 +3978,41 @@ class AIAgent:
|
|
|
3389
3978
|
else:
|
|
3390
3979
|
self._client_kwargs.pop("default_headers", None)
|
|
3391
3980
|
|
|
3981
|
+
# User-configured overrides win over URL/profile defaults — keep them
|
|
3982
|
+
# applied across credential swaps and client rebuilds, not just at
|
|
3983
|
+
# first construction.
|
|
3984
|
+
self._apply_user_default_headers()
|
|
3985
|
+
|
|
3986
|
+
def _apply_user_default_headers(self) -> None:
|
|
3987
|
+
"""Merge user-configured request headers onto the OpenAI client.
|
|
3988
|
+
|
|
3989
|
+
Reads ``model.default_headers`` from config.yaml and merges it onto
|
|
3990
|
+
``self._client_kwargs["default_headers"]``, with user values taking
|
|
3991
|
+
precedence over provider- and SDK-supplied defaults.
|
|
3992
|
+
|
|
3993
|
+
This exists for ``custom`` OpenAI-compatible endpoints sitting behind
|
|
3994
|
+
a gateway/WAF that rejects the OpenAI Python SDK's identifying headers
|
|
3995
|
+
(``User-Agent: OpenAI/Python ...``, ``X-Stainless-*``). Setting e.g.
|
|
3996
|
+
``model.default_headers: {User-Agent: curl/8.7.1}`` lets the request
|
|
3997
|
+
reach such an upstream instead of failing with an opaque 4xx/502 even
|
|
3998
|
+
though the same body works under ``curl``. (#40033)
|
|
3999
|
+
|
|
4000
|
+
Delegates the config read + merge to
|
|
4001
|
+
``agent.auxiliary_client._apply_user_default_headers`` so the main and
|
|
4002
|
+
auxiliary clients can never drift on precedence or value handling.
|
|
4003
|
+
|
|
4004
|
+
No-op for Anthropic/Bedrock modes, which don't use the OpenAI client,
|
|
4005
|
+
and when no overrides are configured.
|
|
4006
|
+
"""
|
|
4007
|
+
if self.api_mode in ("anthropic_messages", "bedrock_converse"):
|
|
4008
|
+
return
|
|
4009
|
+
from agent.auxiliary_client import (
|
|
4010
|
+
_apply_user_default_headers as _merge_user_headers,
|
|
4011
|
+
)
|
|
4012
|
+
merged = _merge_user_headers(self._client_kwargs.get("default_headers"))
|
|
4013
|
+
if merged:
|
|
4014
|
+
self._client_kwargs["default_headers"] = merged
|
|
4015
|
+
|
|
3392
4016
|
def _swap_credential(self, entry) -> None:
|
|
3393
4017
|
runtime_key = getattr(entry, "runtime_api_key", None) or getattr(entry, "access_token", "")
|
|
3394
4018
|
runtime_base = getattr(entry, "runtime_base_url", None) or getattr(entry, "base_url", None) or self.base_url
|
|
@@ -3449,7 +4073,16 @@ class AIAgent:
|
|
|
3449
4073
|
def _anthropic_messages_create(self, api_kwargs: dict):
|
|
3450
4074
|
if self.api_mode == "anthropic_messages":
|
|
3451
4075
|
self._try_refresh_anthropic_client_credentials()
|
|
3452
|
-
|
|
4076
|
+
# Defensive: strip Responses-only kwargs that can leak in under an
|
|
4077
|
+
# api_mode-flip race (the Anthropic SDK raises a non-retryable
|
|
4078
|
+
# TypeError on them). See #31673.
|
|
4079
|
+
from agent.anthropic_adapter import create_anthropic_message
|
|
4080
|
+
return create_anthropic_message(
|
|
4081
|
+
self._anthropic_client,
|
|
4082
|
+
api_kwargs,
|
|
4083
|
+
log_prefix=getattr(self, "log_prefix", ""),
|
|
4084
|
+
prefer_stream=not bool(getattr(self, "_disable_streaming", False)),
|
|
4085
|
+
)
|
|
3453
4086
|
|
|
3454
4087
|
def _rebuild_anthropic_client(self) -> None:
|
|
3455
4088
|
"""Rebuild the Anthropic client after an interrupt or stale call.
|
|
@@ -3800,6 +4433,23 @@ class AIAgent:
|
|
|
3800
4433
|
except Exception:
|
|
3801
4434
|
return False
|
|
3802
4435
|
|
|
4436
|
+
def _provider_supports_vision_tool_messages(self) -> bool:
|
|
4437
|
+
"""Return True if the active provider accepts list-type tool content.
|
|
4438
|
+
|
|
4439
|
+
Some providers (e.g. Xiaomi MiMo) support multimodal user messages
|
|
4440
|
+
but reject list-type tool message content with 400 errors. This
|
|
4441
|
+
checks the provider profile's ``supports_vision_tool_messages`` field.
|
|
4442
|
+
"""
|
|
4443
|
+
try:
|
|
4444
|
+
from providers import get_provider_profile
|
|
4445
|
+
provider = (getattr(self, "provider", "") or "").strip()
|
|
4446
|
+
profile = get_provider_profile(provider)
|
|
4447
|
+
if profile is not None:
|
|
4448
|
+
return getattr(profile, "supports_vision_tool_messages", True)
|
|
4449
|
+
except Exception:
|
|
4450
|
+
pass
|
|
4451
|
+
return True # default: assume compatible
|
|
4452
|
+
|
|
3803
4453
|
def _preprocess_anthropic_content(self, content: Any, role: str) -> Any:
|
|
3804
4454
|
if not self._content_has_image_parts(content):
|
|
3805
4455
|
return content
|
|
@@ -3939,13 +4589,17 @@ class AIAgent:
|
|
|
3939
4589
|
return content
|
|
3940
4590
|
|
|
3941
4591
|
if self._model_supports_vision():
|
|
3942
|
-
# Vision-capable on paper — but if
|
|
3943
|
-
#
|
|
3944
|
-
#
|
|
3945
|
-
#
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4592
|
+
# Vision-capable on paper — but if the provider rejects list-type
|
|
4593
|
+
# tool content (e.g. Xiaomi MiMo's 400 "text is not set"), or if
|
|
4594
|
+
# we've already learned this lesson in-session, short-circuit to
|
|
4595
|
+
# a text summary so we don't burn a round-trip relearning it.
|
|
4596
|
+
if not self._provider_supports_vision_tool_messages():
|
|
4597
|
+
logger.debug(
|
|
4598
|
+
"Tool %s: provider %s does not accept list-type tool "
|
|
4599
|
+
"content — sending text summary",
|
|
4600
|
+
tool_name, getattr(self, "provider", ""),
|
|
4601
|
+
)
|
|
4602
|
+
return _multimodal_text_summary(result)
|
|
3949
4603
|
key = (
|
|
3950
4604
|
(getattr(self, "provider", "") or "").strip().lower(),
|
|
3951
4605
|
(getattr(self, "model", "") or "").strip(),
|
|
@@ -3981,10 +4635,18 @@ class AIAgent:
|
|
|
3981
4635
|
)
|
|
3982
4636
|
return summary
|
|
3983
4637
|
|
|
3984
|
-
def _try_shrink_image_parts_in_messages(
|
|
4638
|
+
def _try_shrink_image_parts_in_messages(
|
|
4639
|
+
self,
|
|
4640
|
+
api_messages: list,
|
|
4641
|
+
*,
|
|
4642
|
+
max_dimension: int = 8000,
|
|
4643
|
+
) -> bool:
|
|
3985
4644
|
"""Forwarder — see ``agent.conversation_compression.try_shrink_image_parts_in_messages``."""
|
|
3986
4645
|
from agent.conversation_compression import try_shrink_image_parts_in_messages
|
|
3987
|
-
return try_shrink_image_parts_in_messages(
|
|
4646
|
+
return try_shrink_image_parts_in_messages(
|
|
4647
|
+
api_messages,
|
|
4648
|
+
max_dimension=max_dimension,
|
|
4649
|
+
)
|
|
3988
4650
|
|
|
3989
4651
|
def _try_strip_image_parts_from_tool_messages(self, api_messages: list) -> bool:
|
|
3990
4652
|
"""Downgrade list-type tool messages to text summaries in-place.
|
|
@@ -4379,7 +5041,7 @@ class AIAgent:
|
|
|
4379
5041
|
return reapply_reasoning_echo_for_provider(self, api_messages)
|
|
4380
5042
|
|
|
4381
5043
|
@staticmethod
|
|
4382
|
-
def _sanitize_tool_calls_for_strict_api(api_msg: dict) -> dict:
|
|
5044
|
+
def _sanitize_tool_calls_for_strict_api(api_msg: dict, model: "str | None" = None) -> dict:
|
|
4383
5045
|
"""Strip Codex Responses API fields from tool_calls for strict providers.
|
|
4384
5046
|
|
|
4385
5047
|
Providers like Mistral, Fireworks, and other strict OpenAI-compatible APIs
|
|
@@ -4388,17 +5050,26 @@ class AIAgent:
|
|
|
4388
5050
|
the internal message history — this method only modifies the outgoing
|
|
4389
5051
|
API copy.
|
|
4390
5052
|
|
|
5053
|
+
``extra_content`` (Gemini thought_signature) is also stripped — strict
|
|
5054
|
+
providers reject it with "Extra inputs are not permitted" — UNLESS the
|
|
5055
|
+
outgoing ``model`` is itself Gemini-family, in which case it must be
|
|
5056
|
+
replayed (Gemini 3 thinking models 400 without it). Defaults to
|
|
5057
|
+
stripping when no model is supplied.
|
|
5058
|
+
|
|
4391
5059
|
Creates new tool_call dicts rather than mutating in-place, so the
|
|
4392
5060
|
original messages list retains call_id/response_item_id for Codex
|
|
4393
5061
|
Responses API compatibility (e.g. if the session falls back to a
|
|
4394
5062
|
Codex provider later).
|
|
4395
5063
|
|
|
4396
|
-
Fields stripped: call_id, response_item_id
|
|
5064
|
+
Fields stripped: call_id, response_item_id, extra_content (model-gated)
|
|
4397
5065
|
"""
|
|
4398
5066
|
tool_calls = api_msg.get("tool_calls")
|
|
4399
5067
|
if not isinstance(tool_calls, list):
|
|
4400
5068
|
return api_msg
|
|
5069
|
+
from agent.transports.chat_completions import _model_consumes_thought_signature
|
|
4401
5070
|
_STRIP_KEYS = {"call_id", "response_item_id"}
|
|
5071
|
+
if not _model_consumes_thought_signature(model):
|
|
5072
|
+
_STRIP_KEYS = _STRIP_KEYS | {"extra_content"}
|
|
4402
5073
|
api_msg["tool_calls"] = [
|
|
4403
5074
|
{k: v for k, v in tc.items() if k not in _STRIP_KEYS}
|
|
4404
5075
|
if isinstance(tc, dict) else tc
|
|
@@ -4522,15 +5193,28 @@ class AIAgent:
|
|
|
4522
5193
|
acp_command=function_args.get("acp_command"),
|
|
4523
5194
|
acp_args=function_args.get("acp_args"),
|
|
4524
5195
|
role=function_args.get("role"),
|
|
5196
|
+
background=function_args.get("background"),
|
|
4525
5197
|
parent_agent=self,
|
|
4526
5198
|
)
|
|
4527
5199
|
|
|
4528
5200
|
def _invoke_tool(self, function_name: str, function_args: dict, effective_task_id: str,
|
|
4529
5201
|
tool_call_id: Optional[str] = None, messages: list = None,
|
|
4530
|
-
pre_tool_block_checked: bool = False
|
|
5202
|
+
pre_tool_block_checked: bool = False,
|
|
5203
|
+
skip_tool_request_middleware: bool = False,
|
|
5204
|
+
tool_request_middleware_trace: Optional[list[dict[str, Any]]] = None) -> str:
|
|
4531
5205
|
"""Forwarder — see ``agent.agent_runtime_helpers.invoke_tool``."""
|
|
4532
5206
|
from agent.agent_runtime_helpers import invoke_tool
|
|
4533
|
-
return invoke_tool(
|
|
5207
|
+
return invoke_tool(
|
|
5208
|
+
self,
|
|
5209
|
+
function_name,
|
|
5210
|
+
function_args,
|
|
5211
|
+
effective_task_id,
|
|
5212
|
+
tool_call_id,
|
|
5213
|
+
messages,
|
|
5214
|
+
pre_tool_block_checked,
|
|
5215
|
+
skip_tool_request_middleware,
|
|
5216
|
+
tool_request_middleware_trace,
|
|
5217
|
+
)
|
|
4534
5218
|
|
|
4535
5219
|
@staticmethod
|
|
4536
5220
|
def _wrap_verbose(label: str, text: str, indent: str = " ") -> str:
|
|
@@ -4580,10 +5264,20 @@ class AIAgent:
|
|
|
4580
5264
|
task_id: str = None,
|
|
4581
5265
|
stream_callback: Optional[callable] = None,
|
|
4582
5266
|
persist_user_message: Optional[str] = None,
|
|
5267
|
+
persist_user_timestamp: Optional[float] = None,
|
|
4583
5268
|
) -> Dict[str, Any]:
|
|
4584
5269
|
"""Forwarder — see ``agent.conversation_loop.run_conversation``."""
|
|
4585
5270
|
from agent.conversation_loop import run_conversation
|
|
4586
|
-
return run_conversation(
|
|
5271
|
+
return run_conversation(
|
|
5272
|
+
self,
|
|
5273
|
+
user_message,
|
|
5274
|
+
system_message,
|
|
5275
|
+
conversation_history,
|
|
5276
|
+
task_id,
|
|
5277
|
+
stream_callback,
|
|
5278
|
+
persist_user_message,
|
|
5279
|
+
persist_user_timestamp,
|
|
5280
|
+
)
|
|
4587
5281
|
|
|
4588
5282
|
def chat(self, message: str, stream_callback: Optional[callable] = None) -> str:
|
|
4589
5283
|
"""
|